From ec6eeae24c7fca4c0a0c110fc2c13c3a6b48398e Mon Sep 17 00:00:00 2001 From: nex3 Date: Sat, 2 Dec 2006 06:46:55 +0000 Subject: [PATCH 002/125] Fixed issue with class variable assignment in partials. git-svn-id: svn://hamptoncatlin.com/haml/branches/1.0rc@181 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/template.rb | 23 +++++++++++------------ test/results/partials.xhtml | 20 ++++++++++++++++++++ test/template_test.rb | 7 ++++--- test/templates/_partial.haml | 7 +++++++ test/templates/partials.haml | 12 ++++++++++++ 5 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 test/results/partials.xhtml create mode 100644 test/templates/_partial.haml create mode 100644 test/templates/partials.haml diff --git a/lib/haml/template.rb b/lib/haml/template.rb index ccdf0f17..2e280344 100644 --- a/lib/haml/template.rb +++ b/lib/haml/template.rb @@ -47,19 +47,18 @@ module Haml # with local_assigns available as local variables within the template. # Returns the result as a string. 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 + unless @view.instance_variable_get("@assigns_added") + assigns = @view.assigns.dup + + # 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 end + + @view.instance_variable_set("@assigns_added", true) end options = @@options.dup diff --git a/test/results/partials.xhtml b/test/results/partials.xhtml new file mode 100644 index 00000000..3c8c437e --- /dev/null +++ b/test/results/partials.xhtml @@ -0,0 +1,20 @@ +

+ @foo = + value one +

+

+ @foo = + value two +

+

+ @foo = + value two +

+

+ @foo = + value three +

+

+ @foo = + value three +

diff --git a/test/template_test.rb b/test/template_test.rb index 00e57d77..74268b62 100644 --- a/test/template_test.rb +++ b/test/template_test.rb @@ -9,14 +9,15 @@ require File.dirname(__FILE__) + '/../lib/haml/template' require File.dirname(__FILE__) + '/mocks/article' class TemplateTest < Test::Unit::TestCase - @@templates = %w{ very_basic standard helpers - whitespace_handling original_engine list helpful - silent_script tag_parsing just_stuff} + @@templates = %w{ very_basic standard helpers + whitespace_handling original_engine list helpful + silent_script tag_parsing just_stuff partials } 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) + @base.instance_variable_set("@foo", 'value one') end def render(text) diff --git a/test/templates/_partial.haml b/test/templates/_partial.haml new file mode 100644 index 00000000..00e701db --- /dev/null +++ b/test/templates/_partial.haml @@ -0,0 +1,7 @@ +%p + @foo = + = @foo +- @foo = 'value three' +%p + @foo = + = @foo diff --git a/test/templates/partials.haml b/test/templates/partials.haml new file mode 100644 index 00000000..3fab791d --- /dev/null +++ b/test/templates/partials.haml @@ -0,0 +1,12 @@ +%p + @foo = + = @foo +- @foo = 'value two' +%p + @foo = + = @foo += render :file => "_partial.haml" +%p + @foo = + = @foo +- @foo = 'value one' From 72315473a337bd362c480a58e839a6cb75382637 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sun, 10 Dec 2006 21:40:03 +0000 Subject: [PATCH 003/125] Added failing test for fix to attribute-quote bug. git-svn-id: svn://hamptoncatlin.com/haml/branches/1.0rc@204 7063305b-7217-0410-af8c-cdc13e5119b9 --- test/results/just_stuff.xhtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/results/just_stuff.xhtml b/test/results/just_stuff.xhtml index 7e458470..67e18277 100644 --- a/test/results/just_stuff.xhtml +++ b/test/results/just_stuff.xhtml @@ -6,7 +6,7 @@ -Boo! +Boo!
wow!

Escape From ab9c210dacb60234ea22f8462b88526b5d75ee6f Mon Sep 17 00:00:00 2001 From: nex3 Date: Sun, 10 Dec 2006 21:46:06 +0000 Subject: [PATCH 004/125] Fix for quote bug (1.0rc). git-svn-id: svn://hamptoncatlin.com/haml/branches/1.0rc@205 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/buffer.rb | 7 ++++++- test/engine_test.rb | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/haml/buffer.rb b/lib/haml/buffer.rb index 724df01c..ed23a237 100644 --- a/lib/haml/buffer.rb +++ b/lib/haml/buffer.rb @@ -23,6 +23,7 @@ module Haml def initialize(options = {}) @options = options @quote_escape = options[:attr_wrapper] == '"' ? """ : "'" + @other_quote_char = options[:attr_wrapper] == '"' ? "'" : '"' @buffer = "" @one_liner_pending = false @tabulation = 0 @@ -172,7 +173,11 @@ module Haml v = v.to_s attr_wrapper = @options[:attr_wrapper] if v.include? attr_wrapper - v = v.gsub(attr_wrapper, @quote_escape) + if v.include? @other_quote_char + v = v.gsub(attr_wrapper, @quote_escape) + else + attr_wrapper = @other_quote_char + end end " #{a}=#{attr_wrapper}#{v}#{attr_wrapper}" end diff --git a/test/engine_test.rb b/test/engine_test.rb index e77a40cf..5b8cd5b0 100644 --- a/test/engine_test.rb +++ b/test/engine_test.rb @@ -60,7 +60,8 @@ class EngineTest < Test::Unit::TestCase def test_attr_wrapper assert_equal("

\n

\n", render("%p{ :strange => 'attrs'}", :attr_wrapper => '*')) - assert_equal("

\n

\n", render("%p{ :escaped => 'quo\"te'}", :attr_wrapper => '"')) + assert_equal("

\n

\n", render("%p{ :escaped => 'quo\"te'}", :attr_wrapper => '"')) + assert_equal("

\n

\n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"')) assert_equal("\n", render("!!! XML", :attr_wrapper => '"')) end From 263f535445c21fc96471477d1703912915628e89 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sun, 10 Dec 2006 22:01:48 +0000 Subject: [PATCH 005/125] Non-colliding Rakefile in 1.0rc. git-svn-id: svn://hamptoncatlin.com/haml/branches/1.0rc@207 7063305b-7217-0410-af8c-cdc13e5119b9 --- Rakefile | 139 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 80 insertions(+), 59 deletions(-) diff --git a/Rakefile b/Rakefile index eac4a761..fa324a09 100644 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,5 @@ require 'rubygems' require 'rake' -require 'rake/testtask' -require 'rake/rdoctask' volatile_requires = ['rcov/rcovtask'] not_loaded = [] @@ -13,94 +11,117 @@ volatile_requires.each do |file| end end +# For some crazy reason, +# some Rake tasks interfere with others +# (specifically, benchmarking). +# Thus, it's advantageous to only show +# the task currently being used. +def is_task?(*tasks) + ARGV[0].nil? || tasks.include?(ARGV[0]) +end + # ----- Default: Testing ------ desc 'Default: run unit tests.' task :default => :test -desc 'Test the HAML plugin' -Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.pattern = 'test/**/*_test.rb' - t.verbose = true +if is_task?('test', 'default') + require 'rake/testtask' + + desc 'Test the Haml plugin' + Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true + end end # ----- Benchmarking ----- -temp_desc = < Date: Mon, 11 Dec 2006 15:26:19 +0000 Subject: [PATCH 006/125] Fixing VERSION files in development branches. git-svn-id: svn://hamptoncatlin.com/haml/branches/1.0rc@208 7063305b-7217-0410-af8c-cdc13e5119b9 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 39f3bf5c..b0bb8785 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.0dev +0.9.5 From fb4703df458316957d3e3ea08131493827eafdec Mon Sep 17 00:00:00 2001 From: nex3 Date: Tue, 12 Dec 2006 06:28:14 +0000 Subject: [PATCH 007/125] Added task for creating a Haml gem. git-svn-id: svn://hamptoncatlin.com/haml/branches/1.0rc@209 7063305b-7217-0410-af8c-cdc13e5119b9 --- Rakefile | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Rakefile b/Rakefile index fa324a09..e2a93845 100644 --- a/Rakefile +++ b/Rakefile @@ -36,6 +36,46 @@ if is_task?('test', 'default') end end +# ----- Packaging ----- + +if is_task?('package', 'repackage', 'clobber_package') + require 'rake/gempackagetask' + + spec = Gem::Specification.new do |spec| + spec.name = 'haml' + spec.summary = 'An elegant, structured XHTML/XML templating engine.' + spec.version = File.read('VERSION').strip + spec.author = 'Hampton Catlin' + spec.email = 'haml@googlegroups.com' + spec.description = <<-END + Haml (HTML Abstraction Markup Language) is a layer on top of XHTML or XML + that's designed to express the structure of XHTML or XML documents + in a non-repetitive, elegant, easy way, + using indentation rather than closing tags + and allowing Ruby to be embedded with ease. + It was originally envisioned as a plugin for Ruby on Rails, + but it can function as a stand-alone templating engine. + END + + readmes = FileList.new('*') { |list| list.exclude(/[a-z]/) }.to_a + spec.executables = ['haml'] + spec.files = FileList['lib/**/*', 'bin/*', 'test/**/*', 'Rakefile'].to_a + readmes + spec.homepage = 'http://haml.hamptoncatlin.com/' + spec.has_rdoc = true + spec.extra_rdoc_files = readmes + spec.rdoc_options += [ + '--title', 'Haml', + '--main', 'REFERENCE', + '--exclude', 'lib/haml/buffer.rb', + '--line-numbers', + '--inline-source' + ] + spec.test_files = FileList['test/**/*_test.rb'].to_a + end + + Rake::GemPackageTask.new(spec) { |pkg| } +end + # ----- Benchmarking ----- if is_task?('benchmark') From e8b28cd3c0033c1d2eb73c564dd0a1238f2b2e05 Mon Sep 17 00:00:00 2001 From: nex3 Date: Fri, 15 Dec 2006 16:18:53 +0000 Subject: [PATCH 008/125] Migrating to new version; preparing for reorganization of repository. git-svn-id: svn://hamptoncatlin.com/haml/tags/rel_1-0-0@211 7063305b-7217-0410-af8c-cdc13e5119b9 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index b0bb8785..3eefcb9d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.5 +1.0.0 From d78fa94a704cfd0b3391cfd136d726836dcf2bc1 Mon Sep 17 00:00:00 2001 From: nex3 Date: Tue, 26 Dec 2006 16:57:10 +0000 Subject: [PATCH 010/125] Bugfix for http://groups-beta.google.com/group/haml/t/529d9e922327aa91, version 1.0.1 created. git-svn-id: svn://hamptoncatlin.com/haml/tags/rel_1-0-1@267 7063305b-7217-0410-af8c-cdc13e5119b9 --- VERSION | 2 +- lib/haml/engine.rb | 2 +- test/results/just_stuff.xhtml | 1 + test/templates/just_stuff.haml | 2 ++ 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index 3eefcb9d..7dea76ed 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0 +1.0.1 diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index bc90849e..230795fd 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -158,7 +158,7 @@ module Haml line = line.strip if old_line - block_opened = tabs > old_tabs + block_opened = tabs > old_tabs && !line.empty? suppress_render = handle_multiline(old_tabs, old_line, old_index) diff --git a/test/results/just_stuff.xhtml b/test/results/just_stuff.xhtml index 67e18277..286e1833 100644 --- a/test/results/just_stuff.xhtml +++ b/test/results/just_stuff.xhtml @@ -8,6 +8,7 @@ Boo!
wow!
+stuff followed by whitespace

Escape - character diff --git a/test/templates/just_stuff.haml b/test/templates/just_stuff.haml index 4fd369ba..7b3f8ed0 100644 --- a/test/templates/just_stuff.haml +++ b/test/templates/just_stuff.haml @@ -8,6 +8,8 @@ !!! FRAMESET %strong{:apos => "Foo's bar!"} Boo! .render= render :inline => "%em= 'wow!'", :type => :haml += "stuff followed by whitespace" + %p \Escape \- character From 970ba243ee5d902b09830a860abe17fea0b8c86c Mon Sep 17 00:00:00 2001 From: nex3 Date: Wed, 3 Jan 2007 07:31:54 +0000 Subject: [PATCH 011/125] Fix for previous bug in stable tag. git-svn-id: svn://hamptoncatlin.com/haml/tags/rel_1-0-2@274 7063305b-7217-0410-af8c-cdc13e5119b9 --- VERSION | 2 +- lib/haml/engine.rb | 46 +++++++++++++++----------- test/results/just_stuff.xhtml | 1 + test/results/whitespace_handling.xhtml | 2 +- test/templates/just_stuff.haml | 3 ++ 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/VERSION b/VERSION index 7dea76ed..6d7de6e6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.1 +1.0.2 diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index 230795fd..529dd221 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -103,7 +103,7 @@ module Haml }.merge options @precompiled = @options[:precompiled] - @template = template #String + @template = template.strip #String @to_close_stack = [] @output_tabs = 0 @template_tabs = 0 @@ -153,32 +153,38 @@ module Haml old_index = nil old_spaces = nil old_tabs = nil - (@template + "\n\n").each_with_index do |line, index| + (@template + "\n-#").each_with_index do |line, index| spaces, tabs = count_soft_tabs(line) line = line.strip - if old_line - block_opened = tabs > old_tabs && !line.empty? - - suppress_render = handle_multiline(old_tabs, old_line, old_index) - - if !suppress_render - line_empty = old_line.empty? - process_indent(old_tabs, old_line) unless line_empty - flat = @flat_spaces != -1 + if !line.empty? + if old_line + block_opened = tabs > old_tabs && !line.empty? + + suppress_render = handle_multiline(old_tabs, old_line, old_index) + + if !suppress_render + line_empty = old_line.empty? + process_indent(old_tabs, old_line) unless line_empty + flat = @flat_spaces != -1 - if flat - push_flat(old_line, old_spaces) - elsif !line_empty - process_line(old_line, old_index, block_opened) + if flat + push_flat(old_line, old_spaces) + elsif !line_empty + process_line(old_line, old_index, block_opened) + end end end + + old_line = line + old_index = index + old_spaces = spaces + old_tabs = tabs + elsif @flat_spaces != -1 + push_flat(old_line, old_spaces) + old_line = '' + old_spaces = 0 end - - old_line = line - old_index = index - old_spaces = spaces - old_tabs = tabs end # Close all the open tags diff --git a/test/results/just_stuff.xhtml b/test/results/just_stuff.xhtml index 286e1833..c644cd0f 100644 --- a/test/results/just_stuff.xhtml +++ b/test/results/just_stuff.xhtml @@ -9,6 +9,7 @@ Boo!

wow!
stuff followed by whitespace +block with whitespace

Escape - character diff --git a/test/results/whitespace_handling.xhtml b/test/results/whitespace_handling.xhtml index bb470cf4..97d22bff 100644 --- a/test/results/whitespace_handling.xhtml +++ b/test/results/whitespace_handling.xhtml @@ -36,7 +36,7 @@

                                                 ___
                                              ,o88888
                                           ,o8888888'
                     ,:o:o:oooo.        ,8O88Pd8888"
                 ,.::.::o:ooooOoOoO. ,oO8O8Pd888'"
               ,.:.::o:ooOoOoOO8O8OOo.8OOPd8O8O"
              , ..:.::o:ooOoOOOO8OOOOo.FdO8O8"
             , ..:.::o:ooOoOO8O888O8O,COCOO"
            , . ..:.::o:ooOoOOOO8OOOOCOCO"
             . ..:.::o:ooOoOoOO8O8OCCCC"o
                . ..:.::o:ooooOoCoCCC"o:o
                . ..:.::o:o:,cooooCo"oo:o:
             `   . . ..:.:cocoooo"'o:o:::'
             .`   . ..::ccccoc"'o:o:o:::'
            :.:.    ,c:cccc"':.:.:.:.:.'
          ..:.:"'`::::c:"'..:.:.:.:.:.'  http://www.chris.com/ASCII/
        ...:.'.:.::::"'    . . . . .'
       .. . ....:."' `   .  . . ''
     . . . ...."'
     .. . ."'     -hrr-
    .


                                              It's a planet!
%strong This shouldn't be bold!
This should! diff --git a/test/templates/just_stuff.haml b/test/templates/just_stuff.haml index 7b3f8ed0..42eb5823 100644 --- a/test/templates/just_stuff.haml +++ b/test/templates/just_stuff.haml @@ -10,6 +10,9 @@ .render= render :inline => "%em= 'wow!'", :type => :haml = "stuff followed by whitespace" +- if true + + %strong block with whitespace %p \Escape \- character From e4ea3b8e064812b68e83f241f6654c2db4ad064e Mon Sep 17 00:00:00 2001 From: nex3 Date: Fri, 5 Jan 2007 16:47:11 +0000 Subject: [PATCH 012/125] Have the Rake GemPackageTask also create archives. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@278 7063305b-7217-0410-af8c-cdc13e5119b9 --- Rakefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index e2a93845..c5427af1 100644 --- a/Rakefile +++ b/Rakefile @@ -73,7 +73,11 @@ if is_task?('package', 'repackage', 'clobber_package') spec.test_files = FileList['test/**/*_test.rb'].to_a end - Rake::GemPackageTask.new(spec) { |pkg| } + Rake::GemPackageTask.new(spec) do |pkg| + pkg.need_zip = true + pkg.need_tar_gz = true + pkg.need_tar_bz2 = true + end end # ----- Benchmarking ----- From 79e583d519aa1090a8709be4c1b1e1742b5ec275 Mon Sep 17 00:00:00 2001 From: nex3 Date: Fri, 19 Jan 2007 03:39:45 +0000 Subject: [PATCH 013/125] Releasing version 1.0.3 - now compatible with Rails 1.2. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@290 7063305b-7217-0410-af8c-cdc13e5119b9 --- VERSION | 2 +- lib/haml/helpers/action_view_mods.rb | 1 + test/helper_test.rb | 2 +- test/results/silent_script.xhtml | 2 +- test/templates/silent_script.haml | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 6d7de6e6..21e8796a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.2 +1.0.3 diff --git a/lib/haml/helpers/action_view_mods.rb b/lib/haml/helpers/action_view_mods.rb index 4d93bba3..d0e20134 100644 --- a/lib/haml/helpers/action_view_mods.rb +++ b/lib/haml/helpers/action_view_mods.rb @@ -1,6 +1,7 @@ begin require 'rubygems' require 'active_support' + require 'action_controller' require 'action_view' action_view_included = true rescue LoadError diff --git a/test/helper_test.rb b/test/helper_test.rb index ccc76b3f..691030d3 100644 --- a/test/helper_test.rb +++ b/test/helper_test.rb @@ -93,7 +93,7 @@ class HelperTest < Test::Unit::TestCase # two behaviors. result = render("- form_tag 'foo' do\n %p bar\n %strong baz", :action_view) - new_rails = "
\n

foo

\n
\n" + new_rails = "
\n

bar

\n baz\n
" old_rails = "" assert(result == new_rails || result == old_rails) end diff --git a/test/results/silent_script.xhtml b/test/results/silent_script.xhtml index e646942a..76e90e0b 100644 --- a/test/results/silent_script.xhtml +++ b/test/results/silent_script.xhtml @@ -50,7 +50,7 @@
  • z
  • I can catch errors!

    - Oh no! "uninitialized constant Foo" happened! + Oh no! "undefined method `silly' for String:Class" happened!

    "false" is: false diff --git a/test/templates/silent_script.haml b/test/templates/silent_script.haml index fc6efa03..45199f0b 100644 --- a/test/templates/silent_script.haml +++ b/test/templates/silent_script.haml @@ -8,7 +8,7 @@ %li= i %h1 I can catch errors! - begin - - Foo.silly + - String.silly - rescue NameError => e = "Oh no! \"#{e}\" happened!" %p From 67bad5ed18ffd86475bf28294250604158bf8a96 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sat, 20 Jan 2007 01:27:54 +0000 Subject: [PATCH 014/125] Version 1.0.4 - small fixes for documentation and packaging. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@294 7063305b-7217-0410-af8c-cdc13e5119b9 --- REFERENCE | 2 +- Rakefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/REFERENCE b/REFERENCE index 7c81a4b3..1d6c6bc9 100644 --- a/REFERENCE +++ b/REFERENCE @@ -154,7 +154,7 @@ causes the tag to be self-closed. For example: %br/ - %meta{:http-equiv => 'Content-Type', :content => 'text/html'}/ + %meta{'http-equiv' => 'Content-Type', :content => 'text/html'}/ is compiled to: diff --git a/Rakefile b/Rakefile index c5427af1..7cc392c2 100644 --- a/Rakefile +++ b/Rakefile @@ -59,7 +59,7 @@ if is_task?('package', 'repackage', 'clobber_package') readmes = FileList.new('*') { |list| list.exclude(/[a-z]/) }.to_a spec.executables = ['haml'] - spec.files = FileList['lib/**/*', 'bin/*', 'test/**/*', 'Rakefile'].to_a + readmes + spec.files = FileList['lib/**/*', 'bin/*', 'test/**/*', 'Rakefile', 'init.rb'].to_a + readmes spec.homepage = 'http://haml.hamptoncatlin.com/' spec.has_rdoc = true spec.extra_rdoc_files = readmes From 267659d6018eaac0dca8601b94751a5c222490f1 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sat, 20 Jan 2007 01:28:49 +0000 Subject: [PATCH 015/125] Updating VERSION files. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@295 7063305b-7217-0410-af8c-cdc13e5119b9 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 21e8796a..ee90284c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.3 +1.0.4 From e155f8c5d7d18d65daa34fd83af07b7891bd3d25 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sat, 20 Jan 2007 20:42:00 +0000 Subject: [PATCH 016/125] Documentation fix. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@299 7063305b-7217-0410-af8c-cdc13e5119b9 --- REFERENCE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/REFERENCE b/REFERENCE index 1d6c6bc9..29cb6984 100644 --- a/REFERENCE +++ b/REFERENCE @@ -171,7 +171,7 @@ by chaining the class names together with periods. They are placed immediately after the tag and before an attributes hash. For example: - div#things + %div#things %span#rice Chicken Fried %p.beans{ :food => 'true' } The magical fruit %h1.class.otherclass#id La La La @@ -181,7 +181,7 @@ is compiled to:

    Chicken Fried

    The magical fruit

    -

    La La La

    +

    La La La

    And, From 9d73e9259079fc7bf7bffc83507ba226581c3c3f Mon Sep 17 00:00:00 2001 From: nex3 Date: Fri, 26 Jan 2007 04:30:38 +0000 Subject: [PATCH 017/125] Version 1.0.5 released. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@306 7063305b-7217-0410-af8c-cdc13e5119b9 --- VERSION | 2 +- lib/haml/helpers.rb | 15 +++++++++++++++ lib/haml/helpers/action_view_mods.rb | 8 ++++++-- test/results/standard.xhtml | 2 +- test/rhtml/standard.rhtml | 1 + test/template_test.rb | 8 ++++++++ test/templates/standard.haml | 1 + 7 files changed, 33 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index ee90284c..90a27f9c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.4 +1.0.5 diff --git a/lib/haml/helpers.rb b/lib/haml/helpers.rb index df6a29dd..7fba846f 100644 --- a/lib/haml/helpers.rb +++ b/lib/haml/helpers.rb @@ -214,7 +214,22 @@ module Haml end result.to_s end + + # Returns whether or not the current template is a Haml template. + # + # This function, unlike other Haml::Helpers functions, + # also works in other ActionView templates, + # where it will always return false. + def is_haml? + @haml_stack ? @haml_stack.size > 0 : false + end include ActionViewMods if self.const_defined? "ActionViewMods" end end + +class ActionView::Base # :nodoc: + def is_haml? + false + end +end diff --git a/lib/haml/helpers/action_view_mods.rb b/lib/haml/helpers/action_view_mods.rb index d0e20134..3a17b9f8 100644 --- a/lib/haml/helpers/action_view_mods.rb +++ b/lib/haml/helpers/action_view_mods.rb @@ -32,11 +32,15 @@ if action_view_included end def concat(string, binding = nil) # :nodoc: - buffer.buffer.concat(string) + if is_haml? + buffer.buffer.concat(string) + else + old_concat(string, binding) + end end def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &proc) # :nodoc: - if block_given? + if block_given? && is_haml? oldproc = proc proc = bind_proc do |*args| concat "\n" diff --git a/test/results/standard.xhtml b/test/results/standard.xhtml index 400e0b35..c94ca1b2 100644 --- a/test/results/standard.xhtml +++ b/test/results/standard.xhtml @@ -6,7 +6,7 @@ -
    +Foo
    Yes, ladies and gentileman. He is just that egotistical. Fantastic! This should be multi-line output The question is if this would translate! Ahah! diff --git a/test/rhtml/standard.rhtml b/test/rhtml/standard.rhtml index a81053f6..78bdd471 100644 --- a/test/rhtml/standard.rhtml +++ b/test/rhtml/standard.rhtml @@ -6,6 +6,7 @@ + <% concat "Foo", Proc.new {} %>
    Yes, ladies and gentileman. He is just that egotistical. Fantastic! This should be multi-line output diff --git a/test/template_test.rb b/test/template_test.rb index 74268b62..ad969222 100644 --- a/test/template_test.rb +++ b/test/template_test.rb @@ -80,6 +80,14 @@ class TemplateTest < Test::Unit::TestCase end def test_rhtml_still_renders + # Make sure it renders normally + res = @base.render("../rhtml/standard") + assert !(res.nil? || res.empty?) + + # Register Haml stuff in @base... + @base.render("standard") + + # Does it still render? res = @base.render("../rhtml/standard") assert !(res.nil? || res.empty?) end diff --git a/test/templates/standard.haml b/test/templates/standard.haml index 72d238b5..6a220e6c 100644 --- a/test/templates/standard.haml +++ b/test/templates/standard.haml @@ -5,6 +5,7 @@ %meta{"http-equiv" => "Content-Type", :content => "text/html; charset=utf-8"}/ %body / You're In my house now! + - concat "Foo" .header Yes, ladies and gentileman. He is just that egotistical. Fantastic! This should be multi-line output From cf849ec495f7e1ea6e4344fc3cef4a94dffd5dcf Mon Sep 17 00:00:00 2001 From: nex3 Date: Wed, 7 Mar 2007 07:32:09 +0000 Subject: [PATCH 019/125] Tagged 1.5. git-svn-id: svn://hamptoncatlin.com/haml/tags/rel_1-5-0@395 7063305b-7217-0410-af8c-cdc13e5119b9 --- MIT-LICENSE | 20 + README | 229 ++++++ Rakefile | 167 +++++ TODO | 16 + VERSION | 1 + bin/haml | 8 + bin/html2haml | 89 +++ bin/sass | 8 + init.rb | 7 + lib/haml.rb | 643 ++++++++++++++++ lib/haml/buffer.rb | 227 ++++++ lib/haml/engine.rb | 740 +++++++++++++++++++ lib/haml/error.rb | 43 ++ lib/haml/exec.rb | 181 +++++ lib/haml/filters.rb | 89 +++ lib/haml/helpers.rb | 249 +++++++ lib/haml/helpers/action_view_mods.rb | 82 ++ lib/haml/template.rb | 124 ++++ lib/sass.rb | 418 +++++++++++ lib/sass/constant.rb | 190 +++++ lib/sass/constant/color.rb | 77 ++ lib/sass/constant/literal.rb | 51 ++ lib/sass/constant/number.rb | 87 +++ lib/sass/constant/operation.rb | 30 + lib/sass/constant/string.rb | 18 + lib/sass/engine.rb | 179 +++++ lib/sass/error.rb | 35 + lib/sass/plugin.rb | 119 +++ lib/sass/tree/attr_node.rb | 44 ++ lib/sass/tree/node.rb | 29 + lib/sass/tree/rule_node.rb | 47 ++ lib/sass/tree/value_node.rb | 12 + test/benchmark.rb | 59 ++ test/haml/engine_test.rb | 220 ++++++ test/haml/helper_test.rb | 106 +++ test/haml/mocks/article.rb | 6 + test/haml/results/content_for_layout.xhtml | 16 + test/haml/results/eval_suppressed.xhtml | 2 + test/haml/results/filters.xhtml | 57 ++ test/haml/results/helpers.xhtml | 60 ++ test/haml/results/helpful.xhtml | 8 + test/haml/results/just_stuff.xhtml | 43 ++ test/haml/results/list.xhtml | 12 + test/haml/results/original_engine.xhtml | 24 + test/haml/results/partials.xhtml | 20 + test/haml/results/silent_script.xhtml | 74 ++ test/haml/results/standard.xhtml | 43 ++ test/haml/results/tag_parsing.xhtml | 28 + test/haml/results/very_basic.xhtml | 7 + test/haml/results/whitespace_handling.xhtml | 104 +++ test/haml/rhtml/standard.rhtml | 55 ++ test/haml/runner.rb | 15 + test/haml/template_test.rb | 150 ++++ test/haml/templates/_partial.haml | 7 + test/haml/templates/_text_area.haml | 3 + test/haml/templates/breakage.haml | 8 + test/haml/templates/content_for_layout.haml | 10 + test/haml/templates/eval_suppressed.haml | 5 + test/haml/templates/filters.haml | 53 ++ test/haml/templates/helpers.haml | 48 ++ test/haml/templates/helpful.haml | 9 + test/haml/templates/just_stuff.haml | 41 + test/haml/templates/list.haml | 12 + test/haml/templates/original_engine.haml | 17 + test/haml/templates/partialize.haml | 1 + test/haml/templates/partials.haml | 12 + test/haml/templates/silent_script.haml | 40 + test/haml/templates/standard.haml | 43 ++ test/haml/templates/tag_parsing.haml | 24 + test/haml/templates/very_basic.haml | 4 + test/haml/templates/whitespace_handling.haml | 137 ++++ test/profile.rb | 63 ++ test/sass/engine_test.rb | 87 +++ test/sass/plugin_test.rb | 103 +++ test/sass/results/basic.css | 9 + test/sass/results/compact.css | 5 + test/sass/results/complex.css | 86 +++ test/sass/results/constants.css | 12 + test/sass/results/expanded.css | 18 + test/sass/results/nested.css | 14 + test/sass/templates/basic.sass | 23 + test/sass/templates/bork.sass | 2 + test/sass/templates/compact.sass | 15 + test/sass/templates/complex.sass | 291 ++++++++ test/sass/templates/constants.sass | 80 ++ test/sass/templates/expanded.sass | 15 + test/sass/templates/nested.sass | 15 + 87 files changed, 6680 insertions(+) create mode 100644 MIT-LICENSE create mode 100644 README create mode 100644 Rakefile create mode 100644 TODO create mode 100644 VERSION create mode 100755 bin/haml create mode 100755 bin/html2haml create mode 100755 bin/sass create mode 100644 init.rb create mode 100644 lib/haml.rb create mode 100644 lib/haml/buffer.rb create mode 100644 lib/haml/engine.rb create mode 100644 lib/haml/error.rb create mode 100644 lib/haml/exec.rb create mode 100644 lib/haml/filters.rb create mode 100644 lib/haml/helpers.rb create mode 100644 lib/haml/helpers/action_view_mods.rb create mode 100644 lib/haml/template.rb create mode 100644 lib/sass.rb create mode 100644 lib/sass/constant.rb create mode 100644 lib/sass/constant/color.rb create mode 100644 lib/sass/constant/literal.rb create mode 100644 lib/sass/constant/number.rb create mode 100644 lib/sass/constant/operation.rb create mode 100644 lib/sass/constant/string.rb create mode 100644 lib/sass/engine.rb create mode 100644 lib/sass/error.rb create mode 100644 lib/sass/plugin.rb create mode 100644 lib/sass/tree/attr_node.rb create mode 100644 lib/sass/tree/node.rb create mode 100644 lib/sass/tree/rule_node.rb create mode 100644 lib/sass/tree/value_node.rb create mode 100644 test/benchmark.rb create mode 100644 test/haml/engine_test.rb create mode 100644 test/haml/helper_test.rb create mode 100644 test/haml/mocks/article.rb create mode 100644 test/haml/results/content_for_layout.xhtml create mode 100644 test/haml/results/eval_suppressed.xhtml create mode 100644 test/haml/results/filters.xhtml create mode 100644 test/haml/results/helpers.xhtml create mode 100644 test/haml/results/helpful.xhtml create mode 100644 test/haml/results/just_stuff.xhtml create mode 100644 test/haml/results/list.xhtml create mode 100644 test/haml/results/original_engine.xhtml create mode 100644 test/haml/results/partials.xhtml create mode 100644 test/haml/results/silent_script.xhtml create mode 100644 test/haml/results/standard.xhtml create mode 100644 test/haml/results/tag_parsing.xhtml create mode 100644 test/haml/results/very_basic.xhtml create mode 100644 test/haml/results/whitespace_handling.xhtml create mode 100644 test/haml/rhtml/standard.rhtml create mode 100644 test/haml/runner.rb create mode 100644 test/haml/template_test.rb create mode 100644 test/haml/templates/_partial.haml create mode 100644 test/haml/templates/_text_area.haml create mode 100644 test/haml/templates/breakage.haml create mode 100644 test/haml/templates/content_for_layout.haml create mode 100644 test/haml/templates/eval_suppressed.haml create mode 100644 test/haml/templates/filters.haml create mode 100644 test/haml/templates/helpers.haml create mode 100644 test/haml/templates/helpful.haml create mode 100644 test/haml/templates/just_stuff.haml create mode 100644 test/haml/templates/list.haml create mode 100644 test/haml/templates/original_engine.haml create mode 100644 test/haml/templates/partialize.haml create mode 100644 test/haml/templates/partials.haml create mode 100644 test/haml/templates/silent_script.haml create mode 100644 test/haml/templates/standard.haml create mode 100644 test/haml/templates/tag_parsing.haml create mode 100644 test/haml/templates/very_basic.haml create mode 100644 test/haml/templates/whitespace_handling.haml create mode 100644 test/profile.rb create mode 100644 test/sass/engine_test.rb create mode 100644 test/sass/plugin_test.rb create mode 100644 test/sass/results/basic.css create mode 100644 test/sass/results/compact.css create mode 100644 test/sass/results/complex.css create mode 100644 test/sass/results/constants.css create mode 100644 test/sass/results/expanded.css create mode 100644 test/sass/results/nested.css create mode 100644 test/sass/templates/basic.sass create mode 100644 test/sass/templates/bork.sass create mode 100644 test/sass/templates/compact.sass create mode 100644 test/sass/templates/complex.sass create mode 100644 test/sass/templates/constants.sass create mode 100644 test/sass/templates/expanded.sass create mode 100644 test/sass/templates/nested.sass diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 00000000..f054f683 --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2006 Hampton Catlin + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README b/README new file mode 100644 index 00000000..c4dfd1f7 --- /dev/null +++ b/README @@ -0,0 +1,229 @@ += Haml and Sass + +Haml and Sass are templating engines +for the two most common types of documents on the web: +HTML and CSS, respectively. +They are designed to make it both easier and more pleasant +to code HTML and CSS documents, +by eliminating redundancy, +reflecting the underlying structure that the document represents, +and providing elegant, easily understandable, and powerful syntax. + +== Using + +There are two ways to use Haml and Sass. +The easiest is as a Rails plugin: +Simply type ./script/plugin install http://hamptoncatlin.com/haml/stable +and both Haml and Sass will be installed. +Views with the .haml extension will automatically use Haml. +Sass is a little more complicated; +.sass files should be placed in public/stylesheets/sass, +where they'll be automatically compiled +to corresponding CSS files in public/stylesheets when needed +(the Sass template directory is customizable... +see the Sass module docs for details). + +== Formatting + +=== Haml + +The most basic element of Haml +is a shorthand for creating HTML tags: + + %tagname{ :attr1 => 'value1', :attr2 => 'value2' } Contents + +No end-tag is needed; Haml handles that automatically. +Adding class and id attributes is even easier. +Haml uses the same syntax as the CSS that styles the document: + + %tagname#id.class + +In fact, when you're using the
    tag, +it becomes even easier. +Because
    is such a common element, +a tag without a name defaults to a div. So + + #foo Hello! + +becomes + +
    Hello! + +Haml uses indentation +to bring the individual elements to represent the HTML structure. +A tag's children are indented two spaces more than the parent tag. +Again, a closing tag is automatically added. +For example: + + %ul + %li Salt + %li Pepper + +becomes: + +
      +
    • Salt
    • +
    • Pepper
    • +
    + +You can also put plain text as a child of an element: + + %p + Hello, + World! + +It's even possible to embed Ruby code into Haml documents. +An equals sign, =, will output the result of the code. +A hyphen, -, will run the code but not output the result. +You can even use control statements +like if and while: + + %p + Date/Time: + - now = DateTime.now + %strong= now + - if now > DateTime.parse("December 31, 2006") + = "Happy new " + "year!" + +Haml provides far more tools than those presented here. +Check out the reference documentation in the Haml module. + +=== Sass + +At its most basic, +Sass is just another way of writing CSS. +Although it's very much like normal CSS, +the basic syntax offers a few helpful features: +tabulation (using *two spaces*) +indicates the attributes in a rule, +rather than non-DRY brackets; +and newlines indicate the end of an attribute, +rather than a semicolon. +For example: + + #main + :background-color #f00 + :width 98% + +becomes: + + #main { + background-color: #f00; + width: 98% } + +However, Sass provides much more than a way to make CSS look nice. +In CSS, it's important to have accurate selectors, +so your styles don't just apply to everything. +However, in order to do this, +you need to use nested element selectors. +These get very ugly very quickly. +I'm sure everyone's had to write something like +"#main .sidebar .top p h1 a", +followed by +"#main .sidebar .top p h1 a:visited" and +"#main .sidebar .top p h1 a:hover". +Well, Sass gets rid of that. +Like Haml, it uses indentation to indicate the structure of the document. +So, what was: + + #main { + width: 90%; + } + #main p { + border-style: solid; + border-width: 1px; + border-color: #00f; + } + #main p a { + text-decoration: none; + font-weight: bold; + } + #main p a:hover { + text-decoration: underline; + } + +becomes: + + #main + :width 90% + p + :border-style solid + :border-width 1px + :border-color #00f + a + :text-decoration none + :font-weight bold + a:hover + :text-decoration underline + +Pretty nice, no? Well, it gets better. +One of the main complaints against CSS is that it doesn't allow constants. +What if have a color or a width you re-use all the time? +In CSS, you just have to re-type it each time, +which is a nightmare when you decide to change it later. +Not so for Sass! +You can use the "!" character to set constants. +Then, if you put "=" after your attribute name, +you can set it to a constant. +For example: + + !note_bg= #55aaff + + #main + :width 70% + .note + :background-color= !note_bg + p + :width 5em + :background-color= !note_bg + +becomes: + + #main { + width: 70%; } + #main .note { + background-color: #55aaff; } + #main p { + width: 5em; + background-color: #55aaff; } + +You can even do simple arithmetic operations with constants, +adding numbers and even colors together: + + !main_bg= #46ar12 + !main_width= 40em + + #main + :background-color= !main_bg + :width= !main_width + .sidebar + :background-color= !main_bg + #333333 + :width= !main_width - 25em + +becomes: + + #main { + background-color: #46a312; + width: 40em; } + #main .sidebar { + background-color: #79d645; + width: 15em; } + +A comprehensive list of features is in +the documentation for the Sass module. + +== Authors + +Haml and Sass are designed by Hampton Catlin (hcatlin). +Help with the Ruby On Rails implementation and much of the documentation +by Jeff Hardy (packagethief). + +Nathan Weizenbaum (Nex3) contributed the buffered-engine code to Haml, +along with many other enhancements +(including the silent-line syntax: "-"). +He continues to actively work on both Haml and Sass. + +If you use this software, you must pay Hampton a compliment. +Say something nice about it. +Beyond that, the implementation is licensed under the MIT License. +Ok, fine, I guess that means compliments aren't *required*. diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..fd89be6c --- /dev/null +++ b/Rakefile @@ -0,0 +1,167 @@ +require 'rubygems' +require 'rake' + +volatile_requires = ['rcov/rcovtask'] +not_loaded = [] +volatile_requires.each do |file| + begin + require file + rescue LoadError + not_loaded.push file + end +end + +# ----- Benchmarking ----- + +temp_desc = < :test + + require 'rake/testtask' + + desc 'Test the Haml plugin' + Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true + end + + # ----- Packaging ----- + + require 'rake/gempackagetask' + + spec = Gem::Specification.new do |spec| + spec.name = 'haml' + spec.summary = "An elegant, structured XHTML/XML templating engine.\nComes with Sass, a similar CSS templating engine." + spec.version = File.read('VERSION').strip + spec.author = 'Hampton Catlin' + spec.email = 'haml@googlegroups.com' + spec.description = <<-END + Haml (HTML Abstraction Markup Language) is a layer on top of XHTML or XML + that's designed to express the structure of XHTML or XML documents + in a non-repetitive, elegant, easy way, + using indentation rather than closing tags + and allowing Ruby to be embedded with ease. + It was originally envisioned as a plugin for Ruby on Rails, + but it can function as a stand-alone templating engine. + END + + readmes = FileList.new('*') do |list| + list.exclude(/[a-z]/) + list.exclude('TODO') + end.to_a + spec.executables = ['haml', 'html2haml', 'sass'] + spec.files = FileList['lib/**/*', 'bin/*', 'test/**/*', 'Rakefile', 'init.rb'].to_a + readmes + spec.homepage = 'http://haml.hamptoncatlin.com/' + spec.has_rdoc = true + spec.extra_rdoc_files = readmes + spec.rdoc_options += [ + '--title', 'Haml', + '--main', 'README', + '--exclude', 'lib/haml/buffer.rb', + '--line-numbers', + '--inline-source' + ] + spec.test_files = FileList['test/**/*_test.rb'].to_a + end + + Rake::GemPackageTask.new(spec) do |pkg| + pkg.need_zip = true + pkg.need_tar_gz = true + pkg.need_tar_bz2 = true + end + + # ----- Documentation ----- + + require 'rake/rdoctask' + + rdoc_task = Proc.new do |rdoc| + rdoc.title = 'Haml/Sass' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') + rdoc.rdoc_files.exclude('lib/haml/buffer.rb') + rdoc.rdoc_files.exclude('lib/sass/tree/*') + end + + Rake::RDocTask.new do |rdoc| + rdoc_task.call(rdoc) + rdoc.rdoc_dir = 'rdoc' + end + + Rake::RDocTask.new(:rdoc_devel) do |rdoc| + rdoc_task.call(rdoc) + rdoc.rdoc_dir = 'rdoc_devel' + rdoc.options << '--all' + rdoc.rdoc_files.include('test/*.rb') + + # Get rid of exclusion rules + rdoc.rdoc_files = Rake::FileList.new(*rdoc.rdoc_files.to_a) + rdoc.rdoc_files.include('lib/haml/buffer.rb') + rdoc.rdoc_files.include('lib/sass/tree/*') + end + + # ----- Coverage ----- + + unless not_loaded.include? 'rcov/rcovtask' + Rcov::RcovTask.new do |t| + t.libs << "test" + t.test_files = FileList['test/**/*_test.rb'] + t.rcov_opts << '-x' << '"^\/"' + if ENV['NON_NATIVE'] + t.rcov_opts << "--no-rcovrt" + end + t.verbose = true + end + end + + # ----- Profiling ----- + + temp_desc = <<-END + Run a profile of haml. + ENGINE=str sets the engine to be profiled (Haml or Sass). + TIMES=n sets the number of runs. Defaults to 100. + FILE=n sets the file to profile. Defaults to 'standard'. + END + desc temp_desc.chomp + task :profile do + require 'test/profile' + + engine = ENV['ENGINE'] && ENV['ENGINE'].downcase == 'sass' ? Sass : Haml + + puts '-'*51, "Profiling #{engine}", '-'*51 + + args = [] + args.push ENV['TIMES'].to_i if ENV['TIMES'] + args.push ENV['FILE'] if ENV['FILE'] + + profiler = engine::Profiler.new + res = profiler.profile(*args) + puts res + + puts '-'*51 + end + +end diff --git a/TODO b/TODO new file mode 100644 index 00000000..32d8a6d0 --- /dev/null +++ b/TODO @@ -0,0 +1,16 @@ +Bugs: + Find a way to make Sass comments work anywhere without breaking url(http:// + +Testing: + Add Sass support for various utilities (benchmark, profile) + Test html2haml + +Documentation: + Haml::Engine public method documentation could use work + +Features: + Sass should throw generic errors for undefined constants, not syntax errors + Sass::Plugin should log errors + Line numbers on Sass errors should be correct after empty lines + There should be a way to represent options in-document + Haml and Sass executables should return meaningful exit codes diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..dc1e644a --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.6.0 diff --git a/bin/haml b/bin/haml new file mode 100755 index 00000000..3480fb9b --- /dev/null +++ b/bin/haml @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +# The command line Haml parser. + +require File.dirname(__FILE__) + '/../lib/haml' +require 'haml/exec' + +opts = Haml::Exec::Haml.new(ARGV) +opts.parse! diff --git a/bin/html2haml b/bin/html2haml new file mode 100755 index 00000000..17754863 --- /dev/null +++ b/bin/html2haml @@ -0,0 +1,89 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../lib/haml' +require 'haml/engine' +require 'rubygems' +require 'hpricot' + +def tabulate(tabs) + ' ' * tabs +end + +TEXT_REGEXP = /^(\s*).*$/ + +def parse_text(text, tabs) + text.strip! + if text.empty? + String.new + else + lines = text.split("\n") + + lines.map do |line| + line.strip! + "#{tabulate(tabs)}#{'\\' if Haml::Engine::SPECIAL_CHARACTERS.include?(line[0])}#{line}\n" + end.join + end +end + +module Hpricot::Node + def to_haml(tabs) + parse_text(self.to_s, tabs) + end +end + +class Hpricot::Doc + def to_haml + output = '' + children.each { |child| output += child.to_haml(0) } + output + end +end + +class Hpricot::XMLDecl + def to_haml(tabs) + "#{tabulate(tabs)}!!! XML\n" + end +end + +class Hpricot::DocType + def to_haml(tabs) + "#{tabulate(tabs)}!!!\n" + end +end + +class Hpricot::Comment + def to_haml(tabs) + "#{tabulate(tabs)}/\n#{parse_text(self.content, tabs + 1)}" + end +end + +class Hpricot::Elem + def to_haml(tabs) + output = "#{tabulate(tabs)}" + output += "%#{name}" unless name == 'div' + + if attributes + output += "##{attributes['id']}" if attributes['id'] + attributes['class'].split(' ').each { |c| output += ".#{c}" } if attributes['class'] + attributes.delete("id") + attributes.delete("class") + output += attributes.inspect if attributes.length > 0 + end + + output += "/" if children.length == 0 + output += "\n" + + self.children.each do |child| + output += child.to_haml(tabs + 1) + end + + output + end +end + +# Must be required after Hpricot mods, +# so they're in scope +require 'haml/exec' + +opts = Haml::Exec::HTML2Haml.new(ARGV) +opts.parse! diff --git a/bin/sass b/bin/sass new file mode 100755 index 00000000..1cc522af --- /dev/null +++ b/bin/sass @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +# The command line Sass parser. + +require File.dirname(__FILE__) + '/../lib/haml' +require 'haml/exec' + +opts = Haml::Exec::Sass.new(ARGV) +opts.parse! diff --git a/init.rb b/init.rb new file mode 100644 index 00000000..3d4283a9 --- /dev/null +++ b/init.rb @@ -0,0 +1,7 @@ +require 'haml' +require 'haml/template' +require 'sass' +require 'sass/plugin' + +ActionView::Base.register_template_handler('haml', Haml::Template) +Sass::Plugin.update_stylesheets diff --git a/lib/haml.rb b/lib/haml.rb new file mode 100644 index 00000000..6250e09d --- /dev/null +++ b/lib/haml.rb @@ -0,0 +1,643 @@ +dir = File.dirname(__FILE__) +$LOAD_PATH << dir unless $LOAD_PATH.include?(dir) + +# = Haml (XHTML Abstraction Markup Language) +# +# Haml is a markup language +# that's used to cleanly and simply describe the XHTML of any web document, +# without the use of inline code. +# Haml functions as a replacement +# for inline page templating systems such as PHP, ERB, and ASP. +# However, Haml avoids the need for explicitly coding XHTML into the template, +# because it is actually an abstract description of the XHTML, +# with some code to generate dynamic content. +# +# == Features +# +# * Whitespace active +# * Well-formatted markup +# * DRY +# * Follows CSS conventions +# * Integrates Ruby code +# * Implements Rails templates with the .haml extension +# +# == Using Haml +# +# Haml can be used in two ways: +# as a plugin for Ruby on Rails, +# and as a standalong Ruby module. +# +# === Rails +# +# Haml is most commonly used as a plugin. +# It can be installed as a plugin using the Rails plugin installer: +# +# ./script/plugin install http://svn.hamptoncatlin.com/haml/tags/stable +# +# Once it's installed, all view files with the ".haml" extension +# will be compiled using Haml. +# +# You can access instance variables in Haml templates +# the same way you do in ERb templates. +# Helper methods are also available in Haml templates. +# For example: +# +# # file: app/controllers/movies_controller.rb +# +# class MoviesController < ApplicationController +# def index +# @title = "Teen Wolf" +# end +# end +# +# # file: app/views/movies/index.haml +# +# #content +# .title +# %h1= @title +# = link_to 'Home', home_url +# +# may be compiled to: +# +#
    +#
    +#

    Teen Wolf

    +# Home +#
    +#
    +# +# === Ruby Module +# +# Haml can also be used completely separately from Rails and ActionView. +# To do this, install the gem with RubyGems: +# +# gem install haml +# +# You can then use it by including the "haml" gem in Ruby code, +# and using Haml::Engine like so: +# +# engine = Haml::Engine.new("%p Haml code!") +# engine.render #=> "

    Haml code!

    \n" +# +# == Characters with meaning to Haml +# +# Various characters, when placed at a certain point in a line, +# instruct Haml to render different types of things. +# +# === XHTML Tags +# +# These characters render XHTML tags. +# +# ==== % +# +# +# The percent character is placed at the beginning of a line. +# It's followed immediately by the name of an element, +# then optionally by modifiers (see below), a space, +# and text to be rendered inside the element. +# It creates an element in the form of . +# For example: +# +# %one +# %two +# %three Hey there +# +# is compiled to: +# +# +# +# Hey there +# +# +# +# Any string is a valid element name; +# Haml will automatically generate opening and closing tags for any element. +# +# ==== {} +# +# Brackets represent a Ruby hash +# that is used for specifying the attributes of an element. +# It is literally evaluated as a Ruby hash, +# so logic will work in it and local variables may be used. +# Quote characters within the attribute +# will be replaced by appropriate escape sequences. +# The hash is placed after the tag is defined. +# For example: +# +# %head{ :name => "doc_head" } +# %script{ 'type' => "text/" + "javascript", +# :src => "javascripts/script_#{2 + 7}" } +# +# is compiled to: +# +# +# +# +# +# ==== [] +# +# Square brackets follow a tag definition and contain a Ruby object +# that is used to set the class and id of that tag. +# The class is set to the object's class +# (transformed to use underlines rather than camel case) +# and the id is set to the object's class, followed by its id. +# Because the id of an object is normally an obscure implementation detail, +# this is most useful for elements that represent instances of Models. +# For example: +# +# # file: app/controllers/users_controller.rb +# +# def show +# @user = CrazyUser.find(15) +# end +# +# # file: app/views/users/show.haml +# +# %div[@user] +# %bar[290]/ +# Hello! +# +# is compiled to: +# +#
    +# +# Hello! +#
    +# +# This is based off of DHH's SimplyHelpful syntax, +# as presented at RailsConf Europe 2006. +# +# ==== / +# +# The forward slash character, when placed at the end of a tag definition, +# causes the tag to be self-closed. +# For example: +# +# %br/ +# %meta{'http-equiv' => 'Content-Type', :content => 'text/html'}/ +# +# is compiled to: +# +#
    +# +# +# ==== . and # +# +# The period and pound sign are borrowed from CSS. +# They are used as shortcuts to specify the class +# and id attributes of an element, respectively. +# Multiple class names can be specified in a similar way to CSS, +# by chaining the class names together with periods. +# They are placed immediately after the tag and before an attributes hash. +# For example: +# +# %div#things +# %span#rice Chicken Fried +# %p.beans{ :food => 'true' } The magical fruit +# %h1.class.otherclass#id La La La +# +# is compiled to: +# +#
    +# Chicken Fried +#

    The magical fruit

    +#

    La La La

    +#
    +# +# And, +# +# #content +# .articles +# .article.title +# Doogie Howser Comes Out +# .article.date +# 2006-11-05 +# .article.entry +# Neil Patrick Harris would like to dispel any rumors that he is straight +# +# is compiled to: +# +#
    +#
    +#
    Doogie Howser Comes Out
    +#
    2006-11-05
    +#
    +# Neil Patrick Harris would like to dispel any rumors that he is straight +#
    +#
    +#
    +# +# ==== Implicit Div Elements +# +# Because the div element is used so often, it is the default element. +# If you only define a class and/or id using the . or # syntax, +# a div element is automatically used. +# For example: +# +# #collection +# .item +# .description What a cool item! +# +# is the same as: +# +# %div{:id => collection} +# %div{:class => 'item'} +# %div{:class => 'description'} What a cool item! +# +# and is compiled to: +# +#
    +#
    Broken record album
    +#
    What a cool item!
    +#
    +# +# ==== = +# +# = is placed at the end of a tag definition, +# after class, id, and attribute declarations. +# It's just a shortcut for inserting Ruby code into an element. +# It works the same as = without a tag: +# it inserts the result of the Ruby code into the template. +# However, if the result is short enough, +# it is displayed entirely on one line. +# For example: +# +# %p= "hello" +# +# is not quite the same as: +# +# %p +# = "hello" +# +# It's compiled to: +# +#

    hello

    +# +# === XHTML Helpers +# +# ==== No Special Character +# +# If no special character appears at the beginning of a line, +# the line is rendered as plain text. +# For example: +# +# %gee +# %whiz +# Wow this is cool! +# +# is compiled to: +# +# +# +# Wow this is cool! +# +# +# +# ==== !!! +# +# When describing XHTML documents with Haml, +# you can have a document type or XML prolog generated automatically +# by including the characters !!!. +# For example: +# +# !!! XML +# !!! +# %html +# %head +# %title Myspace +# %body +# %h1 I am the international space station +# %p Sign my guestbook +# +# is compiled to: +# +# +# +# +# +# Myspace +# +# +#

    I am the international space station

    +#

    Sign my guestbook

    +# +# +# +# You can also specify the version and type of XHTML after the !!!. +# XHTML 1.0 Strict, Transitional, and Frameset and XHTML 1.1 are supported. +# The default version is 1.0 and the default type is Transitional. +# For example: +# +# !!! 1.1 +# +# is compiled to: +# +# +# +# and +# +# !!! Strict +# +# is compiled to: +# +# +# +# If you're not using the UTF-8 characterset for your document, +# you can specify which encoding should appear +# in the XML prolog in a similar way. +# For example: +# +# !!! XML iso-8859-1 +# +# is compiled to: +# +# +# +# ==== / +# +# The forward slash character, when placed at the beginning of a line, +# wraps all text after it in an HTML comment. +# For example: +# +# %billabong +# / This is the billabong element +# I like billabongs! +# +# is compiled to: +# +# +# +# I like billabongs! +# +# +# The forward slash can also wrap indented sections of code. For example: +# +# / +# %p This doesn't render... +# %div +# %h1 Because it's commented out! +# +# is compiled to: +# +# +# +# You can also use Internet Explorer conditional comments +# (about)[http://www.quirksmode.org/css/condcom.html] +# by enclosing the condition in square brackets after the /. +# For example: +# +# /[if IE] +# %a{ :href => 'http://www.mozilla.com/en-US/firefox/' } +# %h1 Get Firefox +# +# is compiled to: +# +# +# +# ==== \ +# +# The backslash character escapes the first character of a line, +# allowing use of otherwise interpreted characters as plain text. +# For example: +# +# %title +# = @title +# \- MySite +# +# is compiled to: +# +# +# MyPage +# - MySite +# +# +# ==== | +# +# The pipe character designates a multiline string. +# It's placed at the end of a line +# and means that all following lines that end with | +# will be evaluated as though they were on the same line. +# For example: +# +# %whoo +# %hoo I think this might get | +# pretty long so I should | +# probably make it | +# multiline so it doesn't | +# look awful. | +# %p This is short. +# +# is compiled to: +# +# +# +# I think this might get pretty long so I should probably make it multiline so it doesn't look awful. +# +# +# +# ==== : +# +# The colon character designates a filter. +# This allows you to pass an indented block of text as input +# to another filtering program and add the result to the output of Haml. +# The syntax is simply a colon followed by the name of the filter. +# For example, +# +# %p +# :markdown +# Textile +# ======= +# +# Hello, *World* +# +# is compiled to +# +#

    +#

    Textile

    +# +#

    Hello, World

    +#

    +# +# Haml has the following filters defined: +# +# [plain] Does not parse the filtered text. +# This is useful for large blocks of text without HTML tags, +# when you don't want lines starting with . or - +# to be parsed. +# +# [ruby] Parses the filtered text with the normal Ruby interpreter. +# All output sent to $stdout, like with +puts+, +# is output into the Haml document. +# Not available if the suppress_eval option is set to true. +# +# [preserve] Inserts the filtered text into the template with whitespace preserved. +# preserved blocks of text aren't indented, +# and newlines are replaced with the HTML escape code for newlines, +# to preserve nice-looking output. +# +# [erb] Parses the filtered text with ERB, like an RHTML template. +# Not available if the suppress_eval option is set to true. +# At the moment, this doesn't support access to variables +# defined by Ruby on Rails or Haml code. +# +# [sass] Parses the filtered text with Sass to produce CSS output. +# +# [redcloth] Parses the filtered text with RedCloth (http://whytheluckystiff.net/ruby/redcloth), +# which uses both Textile and Markdown syntax. +# Only works if RedCloth is installed. +# +# [textile] Parses the filtered text with Textile (http://www.textism.com/tools/textile). +# Only works if RedCloth is installed. +# +# [markdown] Parses the filtered text with Markdown (http://daringfireball.net/projects/markdown). +# Only works if RedCloth or BlueCloth (http://www.deveiate.org/projects/BlueCloth) +# is installed +# (BlueCloth takes precedence if both are installed). +# +# You can also define your own filters (see Setting Options, below). +# +# === Ruby evaluators +# +# ==== = +# +# The equals character is followed by Ruby code, +# which is evaluated and the output inserted into the document as plain text. +# For example: +# +# %p +# = ['hi', 'there', 'reader!'].join " " +# = "yo" +# +# is compiled to: +# +#

    +# hi there reader! +# yo +#

    +# +# ==== - +# +# The hyphen character makes the text following it into "silent script": +# Ruby script that is evaluated, but not output. +# +# It is not recommended that you use this widely; +# almost all processing code and logic should be restricted +# to the Controller, the Helper, or partials. +# +# For example: +# +# - foo = "hello" +# - foo << " there" +# - foo << " you!" +# %p= foo +# +# is compiled to: +# +#

    +# hello there you! +#

    +# +# ===== Blocks +# +# Ruby blocks, like XHTML tags, don't need to be explicitly closed in Haml. +# Rather, they're automatically closed, based on indentation. +# A block begins whenever the indentation is increased +# after a silent script command. +# It ends when the indentation decreases +# (as long as it's not an +else+ clause or something similar). +# For example: +# +# - (42...47).each do |i| +# %p= i +# %p See, I can count! +# +# is compiled to: +# +#

    +# 42 +#

    +#

    +# 43 +#

    +#

    +# 44 +#

    +#

    +# 45 +#

    +#

    +# 46 +#

    +# +# Another example: +# +# %p +# - case 2 +# - when 1 +# = "1!" +# - when 2 +# = "2?" +# - when 3 +# = "3." +# +# is compiled to: +# +#

    +# 2? +#

    +# +# == Haml Options +# +# Options can be set by setting the hash Haml::Template.options +# from environment.rb in Rails, +# or by passing an options hash to Haml::Engine. +# Available options are: +# +# [:suppress_eval] Whether or not attribute hashes and Ruby scripts +# designated by = or ~ should be +# evaluated. If this is true, said scripts are +# rendered as empty strings. Defaults to false. +# +# [:precompiled] A string containing a precompiled Haml template. +# If this is passed, template is ignored +# and no precompilation is done. +# +# [:attr_wrapper] The character that should wrap element attributes. +# This defaults to ' (an apostrophe). Characters +# of this type within the attributes will be escaped +# (e.g. by replacing them with ') if +# the character is an apostrophe or a quotation mark. +# +# [:filename] The name of the Haml file being parsed. +# This is only used as information when exceptions are raised. +# This is automatically assigned when working through ActionView, +# so it's really only useful for the user to assign +# when dealing with Haml programatically. +# +# [:filters] A hash of filters that can be applied to Haml code. +# The keys are the string names of the filters; +# the values are references to the classes of the filters. +# User-defined filters should always have lowercase keys, +# and should have: +# * An +initialize+ method that accepts one parameter, +# the text to be filtered. +# * A +render+ method that returns the result of the filtering. +# +# [:locals] The local variables that will be available within the +# template. For instance, if :locals is +# { :foo => "bar" }, then within the template, +# = foo will produce bar. +# +module Haml; end + +require 'haml/engine' diff --git a/lib/haml/buffer.rb b/lib/haml/buffer.rb new file mode 100644 index 00000000..787392df --- /dev/null +++ b/lib/haml/buffer.rb @@ -0,0 +1,227 @@ +module Haml + # This class is used only internally. It holds the buffer of XHTML that + # is eventually output by Haml::Engine's to_html method. It's called + # from within the precompiled code, and helps reduce the amount of + # processing done within instance_eval'd code. + class Buffer + 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 + + # The string that holds the compiled XHTML. This is aliased as + # _erbout for compatibility with ERB-specific code. + attr_accessor :buffer + + # The number of tabs that are added or subtracted from the + # tabulation proscribed by the precompiled template. + attr_accessor :tabulation + + # Creates a new buffer. + def initialize(options = {}) + @options = options + @quote_escape = options[:attr_wrapper] == '"' ? """ : "'" + @other_quote_char = options[:attr_wrapper] == '"' ? "'" : '"' + @buffer = "" + @one_liner_pending = false + @tabulation = 0 + end + + # Renders +text+ with the proper tabulation. This also deals with + # making a possible one-line tag one line or not. + def push_text(text, tabulation, flattened = false) + if flattened + # In this case, tabulation is the number of spaces, rather + # than the number of tabs. + @buffer << "#{' ' * tabulation}#{flatten(text + "\n")}" + @one_liner_pending = true + elsif @one_liner_pending && one_liner?(text) + @buffer << text + else + if @one_liner_pending + @buffer << "\n" + @one_liner_pending = false + end + @buffer << "#{tabs(tabulation)}#{text}\n" + end + end + + # Properly formats the output of a script that was run in the + # instance_eval. + def push_script(result, tabulation, flattened) + if flattened + result = Haml::Helpers.find_and_preserve(result) + end + unless result.nil? + result = result.to_s + while result[-1] == 10 # \n + # String#chomp is slow + result = result[0...-1] + end + + result = result.gsub("\n", "\n#{tabs(tabulation)}") + push_text result, tabulation + end + nil + end + + # Takes the various information about the opening tag for an + # element, formats it, and adds it to the buffer. + def open_tag(name, tabulation, atomic, try_one_line, class_id, attributes_hash, obj_ref, flattened) + attributes = {} + attributes.merge!(parse_class_and_id(class_id)) unless class_id.nil? || class_id.empty? + attributes.merge!(parse_object_ref(obj_ref, attributes[:id], attributes[:class])) if obj_ref + attributes.merge!(attributes_hash) if attributes_hash + + @one_liner_pending = false + if atomic + str = " />\n" + elsif try_one_line + @one_liner_pending = true + str = ">" + elsif flattened + str = "> " + else + str = ">\n" + end + @buffer << "#{tabs(tabulation)}<#{name}#{build_attributes(attributes)}#{str}" + end + + # Creates a closing tag with the given name. + def close_tag(name, tabulation) + if @one_liner_pending + @buffer << "\n" + @one_liner_pending = false + else + push_text("", tabulation) + end + end + + # Opens an XHTML comment. + def open_comment(try_one_line, conditional, tabulation) + conditional << ">" if conditional + @buffer << "#{tabs(tabulation)}" : "-->" + if @one_liner_pending + @buffer << " #{close_tag}\n" + @one_liner_pending = false + else + push_text(close_tag, tabulation) + end + end + + # Stops parsing a flat section. + def stop_flat + buffer.concat("\n") + @one_liner_pending = false + end + + private + + # Gets count tabs. Mostly for internal use. + def tabs(count) + ' ' * (count + @tabulation) + end + + # Iterates through the classes and ids supplied through . + # and # syntax, and returns a hash with them as attributes, + # that can then be merged with another attributes hash. + def parse_class_and_id(list) + attributes = {} + list.scan(/([#.])([-_a-zA-Z0-9]+)/) do |type, property| + case type + when '.' + if attributes[:class] + attributes[:class] += " " + else + attributes[:class] = "" + end + attributes[:class] += property + when '#' + attributes[:id] = property + end + end + attributes + end + + # Takes an array of objects and uses the class and id of the first + # one to create an attributes hash. + def parse_object_ref(ref, old_id, old_class) + ref = ref[0] + # Let's make sure the value isn't nil. If it is, return the default Hash. + return {} if ref.nil? + class_name = ref.class.to_s.underscore + id = "#{class_name}_#{ref.id}" + + if old_class + class_name += " #{old_class}" + end + + if old_id + id = "#{old_id}_#{id}" + end + + {:id => id, :class => class_name} + end + + # Takes a hash and builds a list of XHTML attributes from it, returning + # the result. + def build_attributes(attributes = {}) + result = attributes.collect do |a,v| + v = v.to_s + unless v.nil? || v.empty? + attr_wrapper = @options[:attr_wrapper] + if v.include? attr_wrapper + if v.include? @other_quote_char + v = v.gsub(attr_wrapper, @quote_escape) + else + attr_wrapper = @other_quote_char + end + end + " #{a}=#{attr_wrapper}#{v}#{attr_wrapper}" + end + end + result.sort.join + end + + # Returns whether or not the given value is short enough to be rendered + # on one line. + def one_liner?(value) + value.length <= ONE_LINER_LENGTH && value.scan(/\n/).empty? + end + end +end + +unless String.methods.include? 'old_comp' + class String # :nodoc + alias_method :old_comp, :<=> + + def <=>(other) + if other.is_a? NilClass + -1 + else + old_comp(other) + end + end + end + + class NilClass # :nodoc: + include Comparable + + def <=>(other) + other.nil? ? 0 : 1 + end + end +end + diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb new file mode 100644 index 00000000..d7f45fd9 --- /dev/null +++ b/lib/haml/engine.rb @@ -0,0 +1,740 @@ +require 'haml/helpers' +require 'haml/buffer' +require 'haml/filters' +require 'haml/error' + +module Haml + # This is the class where all the parsing and processing of the Haml + # template is done. It can be directly used by the user by creating a + # new instance and calling to_html to render the template. For example: + # + # template = File.read('templates/really_cool_template.haml') + # haml_engine = Haml::Engine.new(template) + # output = haml_engine.to_html + # puts output + class Engine + # Allow access to the precompiled template + attr_reader :precompiled + + # Allow reading and writing of the options hash + attr :options, true + + # Designates an XHTML/XML element. + ELEMENT = ?% + + # Designates a
    element with the given class. + DIV_CLASS = ?. + + # Designates a
    element with the given id. + DIV_ID = ?# + + # Designates an XHTML/XML comment. + COMMENT = ?/ + + # Designates an XHTML doctype. + DOCTYPE = ?! + + # Designates script, the result of which is output. + SCRIPT = ?= + + # Designates script, the result of which is flattened and output. + FLAT_SCRIPT = ?~ + + # Designates script which is run but not output. + SILENT_SCRIPT = ?- + + # When following SILENT_SCRIPT, designates a comment that is not output. + SILENT_COMMENT = ?# + + # Designates a non-parsed line. + ESCAPE = ?\\ + + # Designates a block of filtered text. + FILTER = ?: + + # Designates a non-parsed line. Not actually a character. + PLAIN_TEXT = -1 + + # Keeps track of the ASCII values of the characters that begin a + # specially-interpreted line. + SPECIAL_CHARACTERS = [ + ELEMENT, + DIV_CLASS, + DIV_ID, + COMMENT, + DOCTYPE, + SCRIPT, + FLAT_SCRIPT, + SILENT_SCRIPT, + ESCAPE, + FILTER + ] + + # The value of the character that designates that a line is part + # of a multiline string. + MULTILINE_CHAR_VALUE = ?| + + # Characters that designate that a multiline string may be about + # to begin. + MULTILINE_STARTERS = SPECIAL_CHARACTERS - [?/] + + # Keywords that appear in the middle of a Ruby block with lowered + # indentation. If a block has been started using indentation, + # lowering the indentation with one of these won't end the block. + # For example: + # + # - if foo + # %p yes! + # - else + # %p no! + # + # The block is ended after %p no!, because else + # is a member of this array. + MID_BLOCK_KEYWORDS = ['else', 'elsif', 'rescue', 'ensure', 'when'] + + # The Regex that matches an HTML comment command. + COMMENT_REGEX = /\/(\[[a-zA-Z0-9 \.]*\])?(.*)/ + + # The Regex that matches a Doctype command. + DOCTYPE_REGEX = /([0-9]\.[0-9])?[\s]*([a-zA-Z]*)/ + + # The Regex that matches an HTML tag command. + TAG_REGEX = /[%]([-:_a-zA-Z0-9]+)([-_a-zA-Z0-9\.\#]*)(\{.*\})?(\[.*\])?([=\/\~]?)?(.*)?/ + + FLAT_WARNING = <to_html is called. + # See README for available options. + # + #-- + # When adding options, remember to add information about them + # to README! + #++ + # + def initialize(template, options = {}) + @options = { + :suppress_eval => false, + :attr_wrapper => "'", + :locals => {}, + :filters => { + 'sass' => Sass::Engine, + 'plain' => Haml::Filters::Plain, + 'preserve' => Haml::Filters::Preserve + } + } + + unless @options[:suppress_eval] + @options[:filters].merge!({ + 'erb' => ERB, + 'ruby' => Haml::Filters::Ruby + }) + end + + if !NOT_LOADED.include? 'redcloth' + @options[:filters].merge!({ + 'redcloth' => RedCloth, + 'textile' => Haml::Filters::Textile, + 'markdown' => Haml::Filters::Markdown + }) + end + if !NOT_LOADED.include? 'bluecloth' + @options[:filters]['markdown'] = Haml::Filters::Markdown + end + + @options.rec_merge! options + + @precompiled = @options[:precompiled] + + @template = template.strip #String + @to_close_stack = [] + @output_tabs = 0 + @template_tabs = 0 + @index = 0 + + # This is the base tabulation of the currently active + # flattened block. -1 signifies that there is no such block. + @flat_spaces = -1 + + begin + # Only do the first round of pre-compiling if we really need to. + # They might be passing in the precompiled string. + do_precompile if @precompiled.nil? && (@precompiled = String.new) + rescue Haml::Error => e + e.add_backtrace_entry(@index, @options[:filename]) + raise e + end + end + + # Processes the template and returns the result as a string. + def render(scope = Object.new, &block) + @scope_object = scope + @buffer = Haml::Buffer.new(@options) + + local_assigns = @options[:locals] + + # Get inside the view object's world + @scope_object.instance_eval do + # Set all the local assigns + local_assigns.each do |key,val| + self.class.send(:define_method, key) { val } + end + end + + # Compile the @precompiled buffer + compile &block + + # Return the result string + @buffer.buffer + end + + alias_method :to_html, :render + + private + + #Precompile each line + def do_precompile + push_silent <<-END + def _haml_render + _hamlout = @haml_stack[-1] + _erbout = _hamlout.buffer + END + + old_line = nil + old_index = nil + old_spaces = nil + old_tabs = nil + old_uline = nil + (@template + "\n-#\n-#").each_with_index do |line, index| + spaces, tabs = count_soft_tabs(line) + uline = line.lstrip[0...-1] + line = uline.rstrip + + if !line.empty? + if old_line + block_opened = tabs > old_tabs && !line.empty? + + suppress_render = handle_multiline(old_tabs, old_line, old_index) unless @flat_spaces != -1 + + if !suppress_render + line_empty = old_line.empty? + + process_indent(old_tabs, old_line) unless line_empty + flat = @flat_spaces != -1 + + + if !flat && old_spaces != old_tabs * 2 + raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.") + end + + if flat + push_flat(old_uline, old_spaces) + elsif !line_empty + process_line(old_line, old_index, block_opened) + end + + if @flat_spaces == -1 && tabs - old_tabs > 1 + raise SyntaxError.new("Illegal Indentation: Indenting more than once per line is illegal.") + end + end + end + + old_line = line + old_index = index + old_spaces = spaces + old_tabs = tabs + old_uline = uline + elsif @flat_spaces != -1 + process_indent(old_tabs, old_line) unless old_line.empty? + + if @flat_spaces != -1 + push_flat(old_line, old_spaces) + old_line = '' + old_uline = '' + old_spaces = 0 + end + end + end + + # Close all the open tags + @template_tabs.times { close } + + push_silent "end" + end + + # Processes and deals with lowering indentation. + def process_indent(count, line) + if count <= @template_tabs && @template_tabs > 0 + to_close = @template_tabs - count + + to_close.times do |i| + offset = to_close - 1 - i + unless offset == 0 && mid_block_keyword?(line) + close + end + end + end + end + + # Processes a single line of Haml. + # + # This method doesn't return anything; it simply processes the line and + # adds the appropriate code to @precompiled. + def process_line(line, index, block_opened) + @index = index + 1 + @block_opened = block_opened + + case line[0] + when DIV_CLASS, DIV_ID + render_div(line) + when ELEMENT + render_tag(line) + when COMMENT + render_comment(line) + when SCRIPT + sub_line = line[1..-1] + if sub_line[0] == SCRIPT + push_script(sub_line[1..-1].strip.dump.gsub('\\#', '#'), false) + else + push_script(sub_line, false) + end + when FLAT_SCRIPT + warn(FLAT_WARNING) unless defined?(Test::Unit) + push_flat_script(line[1..-1]) + when SILENT_SCRIPT + sub_line = line[1..-1] + unless sub_line[0] == SILENT_COMMENT + push_silent(sub_line, true) + if @block_opened && !mid_block_keyword?(line) + push_and_tabulate([:script]) + end + end + when FILTER + name = line[1..-1].downcase + start_filtered(options[:filters][name] || name) + when DOCTYPE + if line[0...3] == '!!!' + render_doctype(line) + else + push_plain line + end + when ESCAPE + push_plain line[1..-1] + else + push_plain line + end + end + + # Returns whether or not the line is a silent script line with one + # of Ruby's mid-block keywords. + def mid_block_keyword?(line) + line.length > 2 && line[0] == SILENT_SCRIPT && MID_BLOCK_KEYWORDS.include?(line[1..-1].split[0]) + end + + # Deals with all the logic of figuring out whether a given line is + # the beginning, continuation, or end of a multiline sequence. + # + # This returns whether or not the line should be + # rendered normally. + def handle_multiline(count, line, index) + suppress_render = false + # Multilines are denoting by ending with a `|` (124) + if is_multiline?(line) && @multiline_buffer + # A multiline string is active, and is being continued + @multiline_buffer += line[0...-1] + suppress_render = true + elsif is_multiline?(line) && (MULTILINE_STARTERS.include? line[0]) + # A multiline string has just been activated, start adding the lines + @multiline_buffer = line[0...-1] + @multiline_count = count + @multiline_index = index + process_indent(count, line) + suppress_render = true + elsif @multiline_buffer + # A multiline string has just ended, make line into the result + unless line.empty? + process_line(@multiline_buffer, @multiline_index, count > @multiline_count) + @multiline_buffer = nil + end + end + + return suppress_render + end + + # Checks whether or not +line+ is in a multiline sequence. + def is_multiline?(line) # ' '[0] == 32 + line && line.length > 1 && line[-1] == MULTILINE_CHAR_VALUE && line[-2] == 32 + end + + # Takes @precompiled, a string buffer of Ruby code, and + # evaluates it in the context of @scope_object, after preparing + # @scope_object. The code in @precompiled populates + # @buffer with the compiled XHTML code. + def compile(&block) + # Set the local variables pointing to the buffer + buffer = @buffer + @scope_object.extend Haml::Helpers + @scope_object.instance_eval do + @haml_stack ||= Array.new + @haml_stack.push(buffer) + + class << self + attr :haml_lineno # :nodoc: + end + end + + begin + # Evaluate the buffer in the context of the scope object + @scope_object.instance_eval @precompiled + @scope_object._haml_render &block + rescue Exception => e + class << e + include Haml::Error + end + + lineno = @scope_object.haml_lineno + + # Get information from the exception and format it so that + # Rails can understand it. + compile_error = e.message.scan(/\(eval\):([0-9]*):in `[-_a-zA-Z]*': compile error/)[0] + + if compile_error + eval_line = compile_error[0].to_i + line_marker = @precompiled.split("\n")[0...eval_line].grep(/@haml_lineno = [0-9]*/)[-1] + lineno = line_marker.scan(/[0-9]+/)[0].to_i if line_marker + end + + e.add_backtrace_entry(lineno, @options[:filename]) + raise e + end + + # Get rid of the current buffer + @scope_object.instance_eval do + @haml_stack.pop + end + end + + # Evaluates text in the context of @scope_object, but + # does not output the result. + def push_silent(text, add_index = false) + if add_index + @precompiled << "@haml_lineno = #{@index}\n#{text}\n" + else + # Not really DRY, but probably faster + @precompiled << "#{text}\n" + end + end + + # Adds text to @buffer with appropriate tabulation + # without parsing it. + def push_text(text) + @precompiled << "_hamlout.push_text(#{text.dump}, #{@output_tabs})\n" + end + + # Renders a block of text as plain text. + # Also checks for an illegally opened block. + def push_plain(text) + if @block_opened + raise SyntaxError.new("Illegal Nesting: Nesting within plain text is illegal.") + end + push_text text + end + + # Adds +text+ to @buffer while flattening text. + def push_flat(text, spaces) + tabulation = spaces - @flat_spaces + tabulation = tabulation > -1 ? tabulation : 0 + if @filter_buffer + @filter_buffer << "#{' ' * tabulation}#{text}\n" + else + @precompiled << "_hamlout.push_text(#{text.dump}, #{tabulation}, true)\n" + end + end + + # Causes text to be evaluated in the context of + # @scope_object and the result to be added to @buffer. + # + # If flattened is true, Haml::Helpers#find_and_flatten is run on + # the result before it is added to @buffer + def push_script(text, flattened) + unless options[:suppress_eval] + push_silent("haml_temp = #{text}", true) + out = "haml_temp = _hamlout.push_script(haml_temp, #{@output_tabs}, #{flattened})\n" + if @block_opened + push_and_tabulate([:loud, out]) + else + @precompiled << out + end + end + end + + # Causes text to be evaluated, and Haml::Helpers#find_and_flatten + # to be run on it afterwards. + def push_flat_script(text) + unless text.empty? + push_script(text, true) + else + unless @block_opened + raise SyntaxError.new('Filters must have nested text.') + end + start_flat(false) + end + end + + # Closes the most recent item in @to_close_stack. + def close + tag, value = @to_close_stack.pop + case tag + when :script + close_block + when :comment + close_comment value + when :element + close_tag value + when :flat + close_flat value + when :loud + close_loud value + when :filtered + close_filtered value + end + end + + # Puts a line in @precompiled that will add the closing tag of + # the most recently opened tag. + def close_tag(tag) + @output_tabs -= 1 + @template_tabs -= 1 + @precompiled << "_hamlout.close_tag(#{tag.dump}, #{@output_tabs})\n" + end + + # Closes a Ruby block. + def close_block + push_silent "end" + @template_tabs -= 1 + end + + # Closes a comment. + def close_comment(has_conditional) + @output_tabs -= 1 + @template_tabs -= 1 + push_silent "_hamlout.close_comment(#{has_conditional}, #{@output_tabs})" + end + + # Closes a flattened section. + def close_flat(in_tag) + @flat_spaces = -1 + if in_tag + close + else + push_silent('_hamlout.stop_flat') + @template_tabs -= 1 + end + end + + # Closes a loud Ruby block. + def close_loud(command) + push_silent 'end' + @precompiled << command + @template_tabs -= 1 + end + + # Closes a filtered block. + def close_filtered(filter) + @flat_spaces = -1 + if filter.is_a? String + if filter == 'redcloth' || filter == 'markdown' || filter == 'textile' + raise HamlError.new("You must have the RedCloth gem installed to use #{filter}") + else + raise HamlError.new("Filter \"#{filter}\" is not defined!") + end + else + filtered = filter.new(@filter_buffer).render + + unless filter == Haml::Filters::Preserve + push_text(filtered.rstrip.gsub("\n", "\n#{' ' * @output_tabs}")) + else + push_silent("_hamlout.buffer << #{filtered.dump} << \"\\n\"\n") + end + end + + @filter_buffer = nil + @template_tabs -= 1 + end + + # Parses a line that will render as an XHTML tag, and adds the code that will + # render that tag to @precompiled. + def render_tag(line) + matched = false + line.scan(TAG_REGEX) do |tag_name, attributes, attributes_hash, object_ref, action, value| + matched = true + value = value.to_s + + case action + when '/' + atomic = true + when '=', '~' + parse = true + else + value = value.strip + end + + flattened = (action == '~') + + warn(FLAT_WARNING) if flattened && !defined?(Test::Unit) + + value_exists = !value.empty? + attributes_hash = "nil" unless attributes_hash + object_ref = "nil" unless object_ref + + if @block_opened + if atomic + raise SyntaxError.new("Illegal Nesting: Nesting within an atomic tag is illegal.") + elsif action == '=' || value_exists + raise SyntaxError.new("Illegal Nesting: Nesting within a tag that already has content is illegal.") + end + elsif parse && !value_exists + raise SyntaxError.new("No tag content to parse.") + end + + push_silent "_hamlout.open_tag(#{tag_name.inspect}, #{@output_tabs}, #{atomic.inspect}, #{value_exists.inspect}, #{attributes.inspect}, #{attributes_hash}, #{object_ref}, #{flattened.inspect})", true + + unless atomic + push_and_tabulate([:element, tag_name]) + @output_tabs += 1 + + if value_exists + if parse + push_script(value, flattened) + else + push_text(value) + end + close + elsif flattened + start_flat(true) + end + end + end + + unless matched + raise SyntaxError.new("Invalid tag: \"#{line}\"") + end + end + + # Renders a line that creates an XHTML tag and has an implicit div because of + # . or #. + def render_div(line) + render_tag('%div' + line) + end + + # Renders an XHTML comment. + def render_comment(line) + conditional, content = line.scan(COMMENT_REGEX)[0] + content.strip! + + if @block_opened && !content.empty? + raise SyntaxError.new('Illegal Nesting: Nesting within a tag that already has content is illegal.') + end + + try_one_line = !content.empty? + push_silent "_hamlout.open_comment(#{try_one_line}, #{conditional.inspect}, #{@output_tabs})" + @output_tabs += 1 + push_and_tabulate([:comment, !conditional.nil?]) + if try_one_line + push_text content + close + end + end + + # Renders an XHTML doctype or XML shebang. + def render_doctype(line) + if @block_opened + raise SyntaxError.new("Illegal Nesting: Nesting within a header command is illegal.") + end + line = line[3..-1].lstrip.downcase + if line[0...3] == "xml" + encoding = line.split[1] || "utf-8" + wrapper = @options[:attr_wrapper] + doctype = "" + else + version, type = line.scan(DOCTYPE_REGEX)[0] + if version == "1.1" + doctype = '' + else + case type + when "strict" + doctype = '' + when "frameset" + doctype = '' + else + doctype = '' + end + end + end + push_text doctype + end + + # Starts a flattened block. + def start_flat(in_tag) + # @flat_spaces is the number of indentations in the template + # that forms the base of the flattened area + if in_tag + @to_close_stack.push([:flat, true]) + else + push_and_tabulate([:flat]) + end + @flat_spaces = @template_tabs * 2 + end + + # Starts a filtered block. + def start_filtered(filter) + unless @block_opened + raise SyntaxError.new('Filters must have nested text.') + end + push_and_tabulate([:filtered, filter]) + @flat_spaces = @template_tabs * 2 + @filter_buffer = String.new + end + + # Counts the tabulation of a line. + def count_soft_tabs(line) + spaces = line.index(/[^ ]/) + if line[spaces] == ?\t + raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.") + end + [spaces, spaces/2] + end + + # Pushes value onto @to_close_stack and increases + # @template_tabs. + def push_and_tabulate(value) + @to_close_stack.push(value) + @template_tabs += 1 + end + end +end + +class Hash # :nodoc: + # Same as Hash#merge!, but recursively merges sub-hashes. + def rec_merge!(other) + other.each do |key, value| + myval = self[key] + if value.is_a?(Hash) && myval.is_a?(Hash) + myval.rec_merge!(value) + else + self[key] = value + end + end + self + end + + def rec_merge(other) + toret = self.clone + toret.rec_merge! other + end +end diff --git a/lib/haml/error.rb b/lib/haml/error.rb new file mode 100644 index 00000000..078eb240 --- /dev/null +++ b/lib/haml/error.rb @@ -0,0 +1,43 @@ +module Haml + # The abstract type of exception raised by Haml code. + # Haml::SyntaxError includes this module, + # as do all exceptions raised by Ruby code within Haml. + # + # Haml::Error encapsulates information about the exception, + # such as the line of the Haml template it was raised on + # and the Haml file that was being parsed (if applicable). + # It also provides a handy way to rescue only exceptions raised + # because of a faulty template. + module Error + # The line of the Haml template on which the exception was thrown. + attr_reader :haml_line + + # The name of the file that was being parsed when the exception was raised. + # This will be nil unless Haml is being used as an ActionView plugin. + attr_reader :haml_filename + + # Adds a properly formatted entry to the exception's backtrace. + # +lineno+ should be the line on which the error occurred. + # +filename+ should be the file in which the error occurred, + # if applicable (defaults to "(haml)"). + def add_backtrace_entry(lineno, filename = nil) # :nodoc: + @haml_line = lineno + @haml_filename = filename + self.backtrace ||= [] + self.backtrace.unshift "#{filename || '(haml)'}:#{lineno}" + end + end + + # SyntaxError is the type of exception raised when Haml encounters an + # ill-formatted document. + # It's not particularly interesting, except in that it includes Haml::Error. + class SyntaxError < StandardError + include Haml::Error + end + + # HamlError is the type of exception raised when Haml encounters an error + # not of a syntactical nature, such as an undefined Filter. + class HamlError < StandardError + include Haml::Error + end +end diff --git a/lib/haml/exec.rb b/lib/haml/exec.rb new file mode 100644 index 00000000..d2c6df72 --- /dev/null +++ b/lib/haml/exec.rb @@ -0,0 +1,181 @@ +require File.dirname(__FILE__) + '/../haml' +require 'optparse' + +module Haml + # This module contains code for working with the + # haml, sass, and haml2html executables, + # such as command-line parsing stuff. + # It shouldn't need to be invoked by client code. + module Exec # :nodoc: + # A class that encapsulates the executable code + # for all three executables. + class Generic # :nodoc: + def initialize(args) + @args = args + @options = {} + end + + def parse! + @opts = OptionParser.new(&(method(:set_opts).to_proc)) + @opts.parse!(@args) + + process_result + + @options + end + + def to_s + @opts.to_s + end + + private + + def set_opts(opts) + opts.on('--stdin', :NONE, 'Read input from standard input instead of an input file') do + @options[:input] = $stdin + end + + opts.on('--stdout', :NONE, 'Print output to standard output instead of an output file') do + @options[:output] = $stdout + end + + opts.on_tail("-?", "-h", "--help", "Show this message") do + puts opts + exit + end + + opts.on_tail("-v", "--version", "Print version") do + puts("Haml " + File.read(File.dirname(__FILE__) + '/../../VERSION')) + exit + end + end + + def process_result + input = @options[:input] + output = @options[:output] + + if input + output ||= ARGV[0] + else + input ||= ARGV[0] + output ||= ARGV[1] + end + + unless input && output + puts @opts + exit 1 + end + + if input.is_a?(String) && !File.exists?(input) + puts "File #{input} doesn't exist!" + exit 1 + end + + unless input.is_a? IO + input = File.open(input) + input_file = true + end + + unless output.is_a? IO + output = File.open(output, "w") + output_file = true + end + + @options[:input] = input + @options[:output] = output + end + end + + # A class encapsulating the executable functionality + # specific to Haml and Sass. + class HamlSass < Generic # :nodoc: + private + + def set_opts(opts) + opts.banner = <]*>(.*?)<\/\1>/im) do |tag, contents| + input = input.gsub(contents, preserve(contents)) + end + input + end + + # Takes any string, finds all the endlines and converts them to + # HTML entities for endlines so they'll render correctly in + # whitespace-sensitive tags without screwing up the indentation. + def preserve(input) + input.gsub(/\n/, ' ').gsub(/\r/, '') + end + + alias_method :flatten, :preserve + + # Takes an Enumerable object and a block + # and iterates over the object, + # yielding each element to a Haml block + # and putting the result into
  • elements. + # This creates a list of the results of the block. + # For example: + # + # = list_of([['hello'], ['yall']]) do |i| + # = i[0] + # + # Produces: + # + #
  • hello
  • + #
  • yall
  • + # + # And + # + # = list_of({:title => 'All the stuff', :description => 'A book about all the stuff.'}) do |key, val| + # %h3= key.humanize + # %p= val + # + # Produces: + # + #
  • + #

    Title

    + #

    All the stuff

    + #
  • + #
  • + #

    Description

    + #

    A book about all the stuff.

    + #
  • + # + def list_of(array, &block) # :yields: item + to_return = array.collect do |i| + result = capture_haml(i, &block) + + if result.count("\n") > 1 + result.gsub!("\n", "\n ") + result = "\n #{result.strip}\n" + else + result.strip! + end + + "
  • #{result}
  • " + end + to_return.join("\n") + end + + # Increments the number of tabs the buffer automatically adds + # to the lines of the template. + # For example: + # + # %h1 foo + # - tab_up + # %p bar + # - tab_down + # %strong baz + # + # Produces: + # + #

    foo

    + #

    bar

    + # baz + # + def tab_up(i = 1) + buffer.tabulation += i + end + + # Increments the number of tabs the buffer automatically adds + # to the lines of the template. + # + # See tab_up. + def tab_down(i = 1) + buffer.tabulation -= i + end + + # Surrounds the given block of Haml code with the given characters, + # with no whitespace in between. + # For example: + # + # = surround '(', ')' do + # %a{:href => "food"} chicken + # + # Produces: + # + # (chicken) + # + # and + # + # = surround '*' do + # %strong angry + # + # Produces: + # + # *angry* + # + def surround(front, back = nil, &block) + back ||= front + output = capture_haml(&block) + + "#{front}#{output.chomp}#{back}\n" + end + + # Prepends the given character to the beginning of the Haml block, + # with no whitespace between. + # For example: + # + # = precede '*' do + # %span.small Not really + # + # Produces: + # + # *Not really + # + def precede(char, &block) + "#{char}#{capture_haml(&block).chomp}\n" + end + + # Appends the given character to the end of the Haml block, + # with no whitespace between. + # For example: + # + # click + # = succeed '.' do + # %a{:href=>"thing"} here + # + # Produces: + # + # click + # here. + # + def succeed(char, &block) + "#{capture_haml(&block).chomp}#{char}\n" + end + + # Captures the result of the given block of Haml code, + # gets rid of the excess indentation, + # and returns it as a string. + # For example, after the following, + # + # .foo + # - foo = capture_haml(13) do |a| + # %p= a + # + # the local variable foo would be assigned to "

    13

    \n". + # + def capture_haml(*args, &block) + capture_haml_with_buffer(buffer.buffer, *args, &block) + end + + private + + # Sets whether or not ActionView is installed on the system. + def self.action_view(value) # :nodoc: + @@action_view = value + end + + # Gets a reference to the current Haml::Buffer object. + def buffer + @haml_stack[-1] + end + + # Gives a proc the same local "_hamlout" and "_erbout" variables + # that the current template has. + def bind_proc(&proc) + _hamlout = buffer + _erbout = _hamlout.buffer + proc { |*args| proc.call(*args) } + end + + # Performs the function of capture_haml, assuming local_buffer + # is where the output of block goes. + def capture_haml_with_buffer(local_buffer, *args, &block) + position = local_buffer.length + + block.call(*args) + + captured = local_buffer.slice!(position..-1) + + min_tabs = nil + captured.each do |line| + tabs = line.index(/[^ ]/) + min_tabs ||= tabs + min_tabs = min_tabs > tabs ? tabs : min_tabs + end + + result = captured.map do |line| + line[min_tabs..-1] + end + result.to_s + end + + # Returns whether or not the current template is a Haml template. + # + # This function, unlike other Haml::Helpers functions, + # also works in other ActionView templates, + # where it will always return false. + def is_haml? + @haml_stack ? @haml_stack.size > 0 : false + end + + include ActionViewMods if self.const_defined? "ActionViewMods" + end +end + +module ActionView + class Base # :nodoc: + def is_haml? + false + end + end +end diff --git a/lib/haml/helpers/action_view_mods.rb b/lib/haml/helpers/action_view_mods.rb new file mode 100644 index 00000000..c88283f9 --- /dev/null +++ b/lib/haml/helpers/action_view_mods.rb @@ -0,0 +1,82 @@ +begin + require 'rubygems' + require 'active_support' + require 'action_controller' + require 'action_view' + action_view_included = true +rescue LoadError + action_view_included = false +end + +if action_view_included + class ActionView::Base + alias_method :old_concat, :concat unless instance_methods.include? "old_concat" + alias_method :old_form_tag, :form_tag unless instance_methods.include? "old_form_tag" + + alias_method :old_form_for, :form_for unless instance_methods.include? "old_form_for" + end + + module Haml + module Helpers + # This module overrides various helpers in ActionView + # to make them work more effectively with Haml. + # It also defines several helper methods, + # available from a Haml template, + # which are only useful within the context of ActionView. + # It's not available unless ActionView is installed. + # + #-- + # Methods in this module should be nodoc'd. + #++ + module ActionViewMods + def self.included(othermod) # :nodoc: + othermod.class_eval do + action_view(true) + alias_method :capture_erb_with_buffer, :capture_haml_with_buffer + end + end + + def concat(string, binding = nil) # :nodoc: + if is_haml? + buffer.buffer.concat(string) + else + old_concat(string, binding) + end + end + + def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &proc) # :nodoc: + if block_given? && is_haml? + oldproc = proc + proc = bind_proc do |*args| + concat "\n" + tab_up + oldproc.call(*args) + tab_down + end + end + res = old_form_tag(url_for_options, options, *parameters_for_url, &proc) + "\n" + concat "\n" if block_given? && is_haml? + res + end + + def form_for(object_name, *args, &proc) # :nodoc: + if block_given? && is_haml? + oldproc = proc + proc = bind_proc do |*args| + tab_up + oldproc.call(*args) + tab_down + end + end + old_form_for(object_name, *args, &proc) + concat "\n" if block_given? && is_haml? + end + + def generate_content_class_names + controller.controller_name + " " + controller.action_name + end + end + end + end +end + diff --git a/lib/haml/template.rb b/lib/haml/template.rb new file mode 100644 index 00000000..b1cf482b --- /dev/null +++ b/lib/haml/template.rb @@ -0,0 +1,124 @@ +require 'haml/engine' +require 'rubygems' +require 'active_support' +require 'action_view' + +module Haml + # This class interfaces with ActionView + # to make Haml usable as a Ruby on Rails plugin. + # It usually shouldn't need to be used by end users. + # Just in case, though, here's what you might do to render + # templates/index.haml: + # + # ActionView::Base.register_template_handler("haml", Haml::Template) + # base = ActionView::Base.new("templates") + # base.render("index") + # + # Or, if you want to really get into the nitty-gritty: + # + # base = ActionView::Base.new + # template = Haml::Template.new(base) + # template.render("templates/index.haml") + # + class Template + + class << self + @@options = {} + + # Gets various options for Haml. See README for details. + def options + @@options + end + + # Sets various options for Haml. See README for details. + def options=(value) + @@options = value + end + end + + # Creates a new Haml::Template object that uses view + # to render its templates. + def initialize(view) + @view = view + @@precompiled_templates ||= {} + end + + # Renders the file at the location template, + # with local_assigns available as local variables within the template. + # Returns the result as a string. + def render(template, local_assigns={}) + @view.instance_eval do + evaluate_assigns + end + + options = @@options.dup + locals = options[:locals] || {} + locals.merge! local_assigns + options[:locals] = locals + + if @view.haml_inline + engine = Haml::Engine.new(template, options) + else + options[:filename] ||= template + if @precompiled = get_precompiled(template) + options[:precompiled] ||= @precompiled + engine = Haml::Engine.new("", options) + else + engine = Haml::Engine.new(File.read(template), options) + set_precompiled(template, engine.precompiled) + end + end + + yield_proc = @view.instance_eval do + proc { |*name| instance_variable_get("@content_for_#{name.first || 'layout'}") } + end + + engine.to_html(@view) { |*args| yield_proc.call(*args) } + + end + + private + + # Gets the cached, precompiled version of the template at location filename + # as a string. + def get_precompiled(filename) + # Do we have it on file? Is it new enough? + if (precompiled, precompiled_on = @@precompiled_templates[filename]) && + (precompiled_on == File.mtime(filename).to_i) + precompiled + end + end + + # Sets the cached, precompiled version of the template at location filename + # to precompiled. + def set_precompiled(filename, precompiled) + @@precompiled_templates[filename] = [precompiled, File.mtime(filename).to_i] + end + end +end + +# This module refers to the ActionView module that's part of Ruby on Rails. +# Haml can be used as an alternate templating engine for it, +# and includes several modifications to make it more Haml-friendly. +# The documentation can be found +# here[http://rubyonrails.org/api/classes/ActionView/Base.html]. +module ActionView + class Base # :nodoc: + attr :haml_inline + + alias_method :read_template_file_old, :read_template_file + def read_template_file(template_path, extension) + if extension =~ /haml/i + template_path + else + read_template_file_old(template_path, extension) + end + end + + alias_method :render_template_old, :render_template + def render_template(template_extension, template, file_path = nil, local_assigns = {}) + @haml_inline = !template.nil? + render_template_old(template_extension, template, file_path, local_assigns) + end + end +end diff --git a/lib/sass.rb b/lib/sass.rb new file mode 100644 index 00000000..f14815a6 --- /dev/null +++ b/lib/sass.rb @@ -0,0 +1,418 @@ +dir = File.dirname(__FILE__) +$LOAD_PATH << dir unless $LOAD_PATH.include?(dir) + +# = Sass (Syntactically Awesome StyleSheets) +# +# Sass is a meta-language on top of CSS +# that's used to describe the style of a document +# cleanly and structurally, +# with more power than flat CSS allows. +# Sass both provides a simpler, more elegant syntax for CSS +# and implements various features that are useful +# for creating manageable stylesheets. +# +# == Features +# +# * Whitespace active +# * Well-formatted output +# * Elegant input +# * Feature-rich +# +# == Using Sass +# +# Sass can be used in two ways: +# As a plugin for Ruby on Rails +# and as a standalone parser. +# Sass is bundled with Haml, +# so if the Haml plugin or RubyGem is installed, +# Sass will already be installed as a plugin or gem, respectively. +# +# To install Haml and Sass as a Ruby on Rails plugin, +# use the normal Rails plugin installer: +# +# ./script/plugin install http://svn.hamptoncatlin.com/haml/tags/stable +# +# Sass templates in Rails don't quite function in the same way as views, +# because they don't contain dynamic content, +# and so only need to be compiled when the template file has been updated. +# By default (see options, below), +# ".sass" files are placed in public/stylesheets/sass. +# Then, whenever necessary, they're compiled into corresponding CSS files in public/stylesheets. +# For instance, public/stylesheets/sass/main.sass would be compiled to public/stylesheets/main.css. +# +# Using Sass in Ruby code is very simple. +# First install the Haml/Sass RubyGem: +# +# gem install haml +# +# Then you can use it by including the "sass" gem +# and using Sass::Engine like so: +# +# engine = Sass::Engine.new("#main\n :background-color #0000ff") +# engine.render #=> "#main { background-color: #0000ff; }\n" +# +# == CSS Rules +# +# Rules in flat CSS have two elements: +# the selector +# (e.g. "#main", "div p", "li a:hover") +# and the attributes +# (e.g. "color: #00ff00;", "width: 5em;"). +# +# Sass has both of these, +# as well as one additional element: nested rules. +# +# === Rules and Selectors +# +# However, some of the syntax is a little different. +# The syntax for selectors is the same, +# but instead of using brackets to delineate the attributes that belong to a particular rule, +# Sass uses two spaces of indentation. +# For example: +# +# #main p +# +# +# ... +# +# === Attributes +# +# The syntax for attributes is also slightly different. +# The colon is at the beginning of the attribute, +# rather than between the name and the value, +# so it's easier to tell what elements are attributes just by glancing at them. +# Attributes also don't have semicolons at the end; +# each attribute is on its own line, so they aren't necessary. +# For example: +# +# #main p +# :color #00ff00 +# :width 97% +# +# is compiled to: +# +# #main p { +# color: #00ff00; +# width: 97% } +# +# === Nested Rules +# +# Rules can also be nested within each other. +# This signifies that the inner rule's selector is a child of the outer selector. +# For example: +# +# #main p +# :color #00ff00 +# :width 97% +# +# .redbox +# :background-color #ff0000 +# :color #000000 +# +# is compiled to: +# +# #main p { +# color: #00ff00; +# width: 97%; } +# #main p .redbox { +# background-color: #ff0000; +# color: #000000; } +# +# This makes insanely complicated CSS layouts with lots of nested selectors very simple: +# +# #main +# :width 97% +# +# p, div +# :font-size 2em +# a +# :font-weight bold +# +# pre +# :font-size 3em +# +# is compiled to: +# +# #main { +# width: 97%; } +# #main p, #main div { +# font-size: 2em; } +# #main p a, #main div a { +# font-weight: bold; } +# #main pre { +# font-size: 3em; } +# +# === Attribute Namespaces +# +# CSS has quite a few attributes that are in "namespaces;" +# for instance, "font-family," "font-size," and "font-weight" +# are all in the "font" namespace. +# In CSS, if you want to set a bunch of attributes in the same namespace, +# you have to type it out each time. +# Sass offers a shortcut for this: +# just write the namespace one, +# then indent each of the sub-attributes within it. +# For example: +# +# .funky +# :font +# :family fantasy +# :size 30em +# :weight bold +# +# is compiled to: +# +# .funky { +# font-family: fantasy; +# font-size: 30em; +# font-weight: bold; } +# +# == Constants +# +# Sass has support for setting document-wide constants. +# They're set using an exclamation mark followed by the name, +# an equals sign, and the value. +# An attribute can then be set to the value of a constant +# by following it with another equals sign. +# For example: +# +# !main_color = #00ff00 +# +# #main +# :color = !main_color +# :p +# :background-color = !main_color +# :color #000000 +# +# is compiled to: +# +# #main { +# color: #00ff00; } +# #main p { +# background-color: #00ff00; +# color: #000000; } +# +# === Arithmetic +# +# You can even do basic arithmetic with constants. +# Sass recognizes numbers, colors, +# lengths (numbers with units), +# and strings (everything that's not one of the above), +# and various operators that work on various values. +# All the normal arithmetic operators +# (+, -, *, /, %, and parentheses for grouping) +# are defined as usual +# for numbers, colors, and lengths. +# The "+" operator is also defined for Strings +# as the concatenation operator. +# For example: +# +# !main_width = 10 +# !unit1 = em +# !unit2 = px +# !bg_color = #a5f39e +# +# #main +# :background-color = !bg_color +# p +# :background-color = !bg_color + #202020 +# :width = !main_width + !unit1 +# img.thumb +# :width = (!main_width + 15) + !unit2 +# +# is compiled to: +# +# #main { +# background-color: #a5f39e; } +# #main p { +# background-color: #c5ffbe; +# width: 10em; } +# #main img.thumb { +# width: 25em; } +# +# === Colors +# +# Not only can arithmetic be done between colors and other colors, +# but it can be done between colors and normal numbers. +# In this case, the operation is done piecewise one each of the +# Red, Green, and Blue components of the color. +# For example: +# +# !main_color = #a5f39e +# +# #main +# :background-color = !main_color +# p +# :background-color = !bg_color + 32 +# +# is compiled to: +# +# #main { +# background-color: #a5f39e; } +# #main p { +# background-color: #c5ffbe; } +# +# === Strings +# +# Strings are the type that's used by default +# when an element in a bit of constant arithmetic isn't recognized +# as another type of constant. +# However, they can also be created explicitly be wrapping a section of code with quotation marks. +# Inside the quotation marks, +# a backslash can be used to +# escape quotation marks that you want to appear in the CSS. +# For example: +# +# !content = "Hello, \"Hubert\" Bean." +# +# #main +# :content = "string(" + !content + ")" +# +# is compiled to: +# +# #main { +# content: string(Hello, "Hubert" Bean.) } +# +# === Default Concatenation +# +# All those plusses and quotes for concatenating strings +# can get pretty messy, though. +# Most of the time, if you want to concatenate stuff, +# you just want individual values with spaces in between them. +# Thus, in Sass, when two values are next to each other without an operator, +# they're simply joined with a space. +# For example: +# +# !font_family = sans-serif +# !main_font_size = 1em +# +# #main +# :font +# :family = !font-family +# :size = !main_font_size +# h6 +# :font = italic small-caps bold (!main_font_size + 0.1em) !font-family +# +# is compiled to: +# +# #main { +# font-family: sans-serif; +# font-size: 1em; } +# #main h6 { +# font: italic small-caps bold 1.1em sans-serif; } +# +# == Output Style +# +# Although the default CSS style that Sass outputs is very nice, +# and reflects the structure of the document in a similar way that Sass does, +# sometimes it's good to have other formats available. +# +# Sass allows you to choose between three different output styles +# by setting the :style option. +# In Rails, this is done by setting Sass::Template.options[:style]; +# outside Rails, it's done by passing an options hash with :style set. +# +# === :nested +# +# Nested style is the default Sass style, +# because it reflects the structure of the document +# in much the same way Sass does. +# Each attribute has its own line, +# but the indentation isn't constant. +# Each rule is indented based on how deeply it's nested. +# For example: +# +# #main { +# color: #fff; +# background-color: #000; } +# #main p { +# width: 10em; } +# +# .huge { +# font-size: 10em; +# font-weight: bold; +# text-decoration: underline; } +# +# Nested style is very useful when looking at large CSS files +# for the same reason Sass is useful for making them: +# it allows you to very easily grasp the structure of the file +# without actually reading anything. +# +# === :expanded +# +# Expanded is the typical human-made CSS style, +# with each attribute and rule taking up one line. +# Attributes are indented within the rules, +# but the rules aren't indented in any special way. +# For example: +# +# #main { +# color: #fff; +# background-color: #000; +# } +# #main p { +# width: 10em; +# } +# +# .huge { +# font-size: 10em; +# font-weight: bold; +# text-decoration: underline; +# } +# +# === :compact +# +# Compact style, as the name would imply, +# takes up less space than Nested or Expanded. +# However, it's also harder to read. +# Each CSS rule takes up only one line, +# with every attribute defined on that line. +# Nested rules are placed next to each other with no newline, +# while groups of rules have newlines between them. +# For example: +# +# #main { color: #fff; background-color: #000; } +# #main p { width: 10em; } +# +# .huge { font-size: 10em; font-weight: bold; text-decoration: underline; } +# +# == Sass Options +# +# Options can be set by setting the hash Sass::Plugin.options +# from environment.rb in Rails, +# or by passing an options hash to Sass::Engine. +# Available options are: +# +# [:style] Sets the style of the CSS output. +# See the section on Output Style, above. +# +# [:always_update] Whether the CSS files should be updated every +# time a controller is accessed, +# as opposed to only when the template has been modified. +# Defaults to false. +# Only has meaning within Ruby on Rails. +# +# [:always_update] Whether a Sass template should be checked for updates every +# time a controller is accessed, +# as opposed to only when the Rails server starts. +# If a Sass template has been updated, +# it will be recompiled and will overwrite the corresponding CSS file. +# Defaults to false if Rails is running in production mode, +# true otherwise. +# Only has meaning within Ruby on Rails. +# +# [:template_location] The directory where Sass templates should be read from. +# Defaults to RAILS_ROOT + "/public/stylesheets/sass". +# Only has meaning within Ruby on Rails. +# +# [:css_location] The directory where CSS output should be written to. +# Defaults to RAILS_ROOT + "/public/stylesheets". +# Only has meaning within Ruby on Rails. +# +# [:filename] The filename of the file being rendered. +# This is used solely for reporting errors, +# and is automatically set when using Rails. +# +module Sass; end + +require 'sass/engine' diff --git a/lib/sass/constant.rb b/lib/sass/constant.rb new file mode 100644 index 00000000..3a956b63 --- /dev/null +++ b/lib/sass/constant.rb @@ -0,0 +1,190 @@ +require 'sass/constant/operation' +require 'sass/constant/literal' + +module Sass + module Constant # :nodoc: + # The character that begins a constant. + CONSTANT_CHAR = ?! + + # Whitespace characters + WHITESPACE = [?\ , ?\t, ?\n, ?\r] + + # The character used to escape values + ESCAPE_CHAR = ?\\ + + # The character used to open and close strings + STRING_CHAR = ?" + + # A mapping of syntactically-significant characters + # to parsed symbols + SYMBOLS = { + ?( => :open, + ?) => :close, + ?+ => :plus, + ?- => :minus, + ?* => :times, + ?/ => :div, + ?% => :mod, + STRING_CHAR => :str, + ESCAPE_CHAR => :esc + } + + # The regular expression used to parse constants + MATCH = /^#{Regexp.escape(CONSTANT_CHAR.chr)}([^\s#{(SYMBOLS.keys + [ ?= ]).map {|c| Regexp.escape("#{c.chr}") }}]+)\s*=\s*(.+)/ + + # First-order operations + FIRST_ORDER = [:times, :div, :mod] + + # Second-order operations + SECOND_ORDER = [:plus, :minus] + + class << self + def parse(value, constants, line) + begin + operationalize(parenthesize(tokenize(value)), constants).to_s + rescue Sass::SyntaxError => e + if e.message == "Constant arithmetic error" + e.instance_eval do + @message += ": #{value.dump}" + end + end + e.sass_line = line + raise e + end + end + + private + + def tokenize(value) + escaped = false + is_string = false + negative_okay = true + str = '' + to_return = [] + + reset_str = Proc.new do + to_return << str unless str.empty? + '' + end + + value.each_byte do |byte| + unless escaped + if byte == ESCAPE_CHAR + escaped = true + next + end + + last = to_return[-1] + + if byte == STRING_CHAR + is_string = !is_string + + if is_string && last && (!last.is_a?(Symbol) || last == :close) + to_return << :concat + end + + str = reset_str.call + next + end + + unless is_string + + if WHITESPACE.include?(byte) + str = reset_str.call + next + end + + symbol = SYMBOLS[byte] + + if (symbol.nil? || symbol == :open) && + last && (!last.is_a?(Symbol) || last == :close) + # Two values connected without an operator + to_return << :concat + end + + if symbol && !(negative_okay && symbol == :minus) + str = reset_str.call + negative_okay = true + to_return << symbol + next + end + end + end + + escaped = false + negative_okay = false + str << byte.chr + end + + if is_string + raise Sass::SyntaxError.new("Unterminated string: #{value.dump}") + end + str = reset_str.call + to_return + end + + def parenthesize(value) + parenthesize_helper(0, value, value.length)[0] + end + + def parenthesize_helper(i, value, value_len) + to_return = [] + beginning = i + token = value[i] + + while i < value_len && token != :close + if token == :open + to_return.push(*value[beginning...i]) + sub, i = parenthesize_helper(i + 1, value, value_len) + beginning = i + to_return << sub + else + i += 1 + end + + token = value[i] + end + to_return.push(*value[beginning...i]) + return to_return, i + 1 + end + + #-- + # TODO: Don't pass around original value; + # have Constant.parse automatically add it to exception. + #++ + def operationalize(value, constants) + value = [value] unless value.is_a?(Array) + length = value.length + if length == 1 + value = value[0] + if value.is_a? Operation + value + else + Literal.parse(insert_constant(value, constants)) + end + elsif length == 2 + raise SyntaxError.new("Constant arithmetic error") + elsif length == 3 + Operation.new(operationalize(value[0], constants), operationalize(value[2], constants), value[1]) + else + if SECOND_ORDER.include?(value[1]) && FIRST_ORDER.include?(value[3]) + operationalize([value[0], value[1], operationalize(value[2..4], constants), *value[5..-1]], constants) + else + operationalize([operationalize(value[0..2], constants), *value[3..-1]], constants) + end + end + end + + def insert_constant(value, constants) + to_return = value + if value[0] == CONSTANT_CHAR + to_return = constants[value[1..-1]] + unless to_return + raise SyntaxError.new("Undefined constant: \"#{value}\"") + end + end + to_return + end + end + end +end diff --git a/lib/sass/constant/color.rb b/lib/sass/constant/color.rb new file mode 100644 index 00000000..30456f14 --- /dev/null +++ b/lib/sass/constant/color.rb @@ -0,0 +1,77 @@ +require 'sass/constant/literal' + +module Sass::Constant # :nodoc: + class Color < Literal # :nodoc: + + REGEXP = /\##{"([0-9a-fA-F]{1,2})" * 3}/ + + def parse(value) + @value = value.scan(REGEXP)[0].map { |num| num.ljust(2, 'f').to_i(16) } + end + + def plus(other) + if other.is_a? Sass::Constant::String + Sass::Constant::String.from_value(self.to_s + other.to_s) + else + piecewise(other, :+) + end + end + + def minus(other) + if other.is_a? Sass::Constant::String + raise NoMethodError.new(nil, :minus) + else + piecewise(other, :-) + end + end + + def times(other) + if other.is_a? Sass::Constant::String + raise NoMethodError.new(nil, :times) + else + piecewise(other, :*) + end + end + + def div(other) + if other.is_a? Sass::Constant::String + raise NoMethodError.new(nil, :div) + else + piecewise(other, :/) + end + end + + def mod(other) + if other.is_a? Sass::Constant::String + raise NoMethodError.new(nil, :mod) + else + piecewise(other, :%) + end + end + + def to_s + red, green, blue = @value.map { |num| num.to_s(16).rjust(2, '0') } + "##{red}#{green}#{blue}" + end + + protected + + def self.filter_value(value) + value.map { |num| num.to_i } + end + + private + + def piecewise(other, operation) + other_num = other.is_a? Number + other_val = other.value + + rgb = [] + for i in (0...3) + res = @value[i].send(operation, other_num ? other_val : other_val[i]) + rgb[i] = [ [res, 255].min, 0 ].max + end + Color.from_value(rgb) + end + end +end diff --git a/lib/sass/constant/literal.rb b/lib/sass/constant/literal.rb new file mode 100644 index 00000000..77ec7d10 --- /dev/null +++ b/lib/sass/constant/literal.rb @@ -0,0 +1,51 @@ +# Let the subclasses see the superclass +module Sass::Constant; class Literal; end; end; # :nodoc: + +require 'sass/constant/string' +require 'sass/constant/number' +require 'sass/constant/color' + +class Sass::Constant::Literal # :nodoc: + # The regular expression matching numbers. + NUMBER = /^(-?[0-9]*?\.?)([0-9]+)([^0-9\s]*)$/ + + # The regular expression matching colors. + COLOR = /^\#(#{"[0-9a-fA-F]" * 3}|#{"[0-9a-fA-F]" * 6})/ + + def self.parse(value) + case value + when NUMBER + Sass::Constant::Number.new(value) + when COLOR + Sass::Constant::Color.new(value) + else + Sass::Constant::String.new(value) + end + end + + def initialize(value = nil) + self.parse(value) if value + end + + def perform + self + end + + def concat(other) + Sass::Constant::String.from_value("#{self.to_s} #{other.to_s}") + end + + attr_reader :value + + protected + + def self.filter_value(value) + value + end + + def self.from_value(value) + instance = self.new + instance.instance_variable_set('@value', self.filter_value(value)) + instance + end +end diff --git a/lib/sass/constant/number.rb b/lib/sass/constant/number.rb new file mode 100644 index 00000000..d822f13b --- /dev/null +++ b/lib/sass/constant/number.rb @@ -0,0 +1,87 @@ +require 'sass/constant/literal' + +module Sass::Constant # :nodoc: + class Number < Literal # :nodoc: + + attr_reader :unit + + def parse(value) + first, second, unit = value.scan(Literal::NUMBER)[0] + @value = first.empty? ? second.to_i : "#{first}#{second}".to_f + @unit = unit unless unit.empty? + end + + def plus(other) + if other.is_a? Number + operate(other, :+) + elsif other.is_a? Color + other.plus(self) + else + Sass::Constant::String.from_value(self.to_s + other.to_s) + end + end + + def minus(other) + if other.is_a? Number + operate(other, :-) + else + raise NoMethodError.new(nil, :minus) + end + end + + def times(other) + if other.is_a? Number + operate(other, :*) + elsif other.is_a? Color + other.times(self) + else + raise NoMethodError.new(nil, :times) + end + end + + def div(other) + if other.is_a? Number + operate(other, :/) + else + raise NoMethodError.new(nil, :div) + end + end + + def mod(other) + if other.is_a? Number + operate(other, :%) + else + raise NoMethodError.new(nil, :mod) + end + end + + def to_s + value = @value + value = value.to_i if value % 1 == 0.0 + "#{value}#{@unit}" + end + + protected + + def self.from_value(value, unit=nil) + instance = super(value) + instance.instance_variable_set('@unit', unit) + instance + end + + def operate(other, operation) + unit = nil + if other.unit.nil? + unit = self.unit + elsif self.unit.nil? + unit = other.unit + elsif other.unit == self.unit + unit = self.unit + else + raise Sass::SyntaxError.new("Incompatible units: #{self.unit} and #{other.unit}") + end + + Number.from_value(self.value.send(operation, other.value), unit) + end + end +end diff --git a/lib/sass/constant/operation.rb b/lib/sass/constant/operation.rb new file mode 100644 index 00000000..3033af50 --- /dev/null +++ b/lib/sass/constant/operation.rb @@ -0,0 +1,30 @@ +require 'sass/constant/string' +require 'sass/constant/number' +require 'sass/constant/color' + +module Sass::Constant # :nodoc: + class Operation # :nodoc: + def initialize(operand1, operand2, operator) + @operand1 = operand1 + @operand2 = operand2 + @operator = operator + end + + def to_s + self.perform.to_s + end + + protected + + def perform + literal1 = @operand1.perform + literal2 = @operand2.perform + begin + literal1.send(@operator, literal2) + rescue NoMethodError => e + raise e unless e.name.to_s == @operator.to_s + raise Sass::SyntaxError.new("Undefined operation: \"#{literal1} #{@operator} #{literal2}\"") + end + end + end +end diff --git a/lib/sass/constant/string.rb b/lib/sass/constant/string.rb new file mode 100644 index 00000000..21b7f355 --- /dev/null +++ b/lib/sass/constant/string.rb @@ -0,0 +1,18 @@ +require 'sass/constant/literal' + +module Sass::Constant # :nodoc: + class String < Literal # :nodoc: + + def parse(value) + @value = value + end + + def plus(other) + Sass::Constant::String.from_value(self.to_s + other.to_s) + end + + def to_s + @value + end + end +end diff --git a/lib/sass/engine.rb b/lib/sass/engine.rb new file mode 100644 index 00000000..7d178a48 --- /dev/null +++ b/lib/sass/engine.rb @@ -0,0 +1,179 @@ +require 'sass/tree/node' +require 'sass/tree/value_node' +require 'sass/tree/rule_node' +require 'sass/constant' +require 'sass/error' + +module Sass + # This is the class where all the parsing and processing of the Sass + # template is done. It can be directly used by the user by creating a + # new instance and calling render to render the template. For example: + # + # template = File.load('stylesheets/sassy.sass') + # sass_engine = Sass::Engine.new(template) + # output = sass_engine.render + # puts output + class Engine + # The character that begins a CSS attribute. + ATTRIBUTE_CHAR = ?: + + # The character that designates that + # an attribute should be assigned to the result of constant arithmetic. + SCRIPT_CHAR = ?= + + # The string that begins one-line comments. + COMMENT_STRING = '//' + + # The regex that matches attributes. + ATTRIBUTE = /:([^\s=]+)\s*(=?)\s*(.*)/ + + # Creates a new instace of Sass::Engine that will compile the given + # template string when render is called. + # See README for available options. + # + #-- + # + # TODO: Add current options to REFRENCE. Remember :filename! + # + # When adding options, remember to add information about them + # to README! + #++ + # + def initialize(template, options={}) + @options = { + :style => :nested + }.merge! options + @template = template.split("\n") + @lines = [] + @constants = {} + end + + # Processes the template and returns the result as a string. + def render + begin + split_lines + + root = Tree::Node.new(@options[:style]) + index = 0 + while @lines[index] + child, index = build_tree(index) + child.line = index if child + root << child if child + end + @line = nil + + root.to_s + rescue SyntaxError => err + err.add_backtrace_entry(@options[:filename]) + raise err + end + end + + alias_method :to_css, :render + + private + + # Readies each line in the template for parsing, + # and computes the tabulation of the line. + def split_lines + old_tabs = 0 + @template.each_with_index do |line, index| + @line = index + 1 + + # TODO: Allow comments appended to the end of lines, + # find some way to make url(http://www.google.com/) work + unless line[0..1] == COMMENT_STRING # unless line is a comment + tabs = count_tabs(line) + + if tabs # if line isn't blank + if tabs - old_tabs > 1 + raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.", @line) + end + @lines << [line.strip, tabs] + + old_tabs = tabs + end + end + end + @line = nil + end + + # Counts the tabulation of a line. + def count_tabs(line) + spaces = line.index(/[^ ]/) + if spaces + if spaces % 2 == 1 || line[spaces] == ?\t + raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.", @line) + end + spaces / 2 + else + nil + end + end + + def build_tree(index) + line, tabs = @lines[index] + index += 1 + @line = index + node = parse_line(line) + + # Node is nil if it's non-outputting, like a constant assignment + return nil, index unless node + + has_children = has_children?(index, tabs) + + while has_children + child, index = build_tree(index) + + if child.nil? + raise SyntaxError.new("Constants may only be declared at the root of a document.", @line) + end + + child.line = @line + node << child if child + has_children = has_children?(index, tabs) + end + + return node, index + end + + def has_children?(index, tabs) + next_line = @lines[index] + next_line && next_line[1] > tabs + end + + def parse_line(line) + case line[0] + when ATTRIBUTE_CHAR + parse_attribute(line) + when Constant::CONSTANT_CHAR + parse_constant(line) + else + Tree::RuleNode.new(line, @options[:style]) + end + end + + def parse_attribute(line) + name, eq, value = line.scan(ATTRIBUTE)[0] + + if name.nil? || value.nil? + raise SyntaxError.new("Invalid attribute: \"#{line}\"", @line) + end + + if eq[0] == SCRIPT_CHAR + value = Sass::Constant.parse(value, @constants, @line).to_s + end + + Tree::AttrNode.new(name, value, @options[:style]) + end + + def parse_constant(line) + name, value = line.scan(Sass::Constant::MATCH)[0] + unless name && value + raise SyntaxError.new("Invalid constant: \"#{line}\"", @line) + end + @constants[name] = Sass::Constant.parse(value, @constants, @line) + nil + end + end +end diff --git a/lib/sass/error.rb b/lib/sass/error.rb new file mode 100644 index 00000000..66b0c2e8 --- /dev/null +++ b/lib/sass/error.rb @@ -0,0 +1,35 @@ +module Sass + # Sass::SyntaxError encapsulates information about the exception, + # such as the line of the Sass template it was raised on + # and the Sass file that was being parsed (if applicable). + # It also provides a handy way to rescue only exceptions raised + # because of a faulty template. + class SyntaxError < StandardError + # The line of the Sass template on which the exception was thrown. + attr_accessor :sass_line + + # The name of the file that was being parsed when the exception was raised. + # This will be nil unless Sass is being used as an ActionView plugin. + attr_reader :sass_filename + + # Creates a new SyntaxError. + # +lineno+ should be the line of the Sass template on which the error occurred. + def initialize(msg, lineno = nil) + @message = msg + @sass_line = lineno + end + + # Adds a properly formatted entry to the exception's backtrace. + # +filename+ should be the file in which the error occurred, + # if applicable (defaults to "(sass)"). + def add_backtrace_entry(filename) # :nodoc: + @sass_filename = filename + self.backtrace ||= [] + self.backtrace.unshift "#{filename || '(sass)'}:#{@sass_line}" + end + + def to_s # :nodoc: + @message + end + end +end diff --git a/lib/sass/plugin.rb b/lib/sass/plugin.rb new file mode 100644 index 00000000..97e0628d --- /dev/null +++ b/lib/sass/plugin.rb @@ -0,0 +1,119 @@ +require 'sass/engine' +require 'rubygems' +require 'action_controller' + +RAILS_ROOT = '. 'unless self.class.const_defined?('RAILS_ROOT') +RAILS_ENV = 'production' unless self.class.const_defined?('RAILS_ENV') + +module Sass + # This module contains methods that ActionController calls + # to automatically update Sass templates that need updating. + # It wasn't designed to be used outside of the context of ActionController. + module Plugin + class << self + @@options = { + :template_location => RAILS_ROOT + '/public/stylesheets/sass', + :css_location => RAILS_ROOT + '/public/stylesheets', + :always_update => false, + :always_check => RAILS_ENV != "production" + } + + # Gets various options for Sass. See README for details. + #-- + # TODO: *DOCUMENT OPTIONS* + #++ + def options + @@options + end + + # Sets various options for Sass. + def options=(value) + @@options.merge!(value) + end + + # Checks each stylesheet in options[:css_location] + # to see if it needs updating, + # and updates it using the corresponding template + # from options[:templates] + # if it does. + def update_stylesheets + Dir[options[:template_location] + '/*.sass'].each do |file| + name = File.basename(file)[0...-5] + + if options[:always_update] || stylesheet_needs_update?(name) + css = css_filename(name) + File.delete(css) if File.exists?(css) + + filename = template_filename(name) + l_options = @@options.dup + l_options[:filename] = filename + engine = Engine.new(File.read(filename), l_options) + begin + result = engine.render + rescue Exception => e + if RAILS_ENV != "production" + e_string = "#{e.class}: #{e.message}" + + if e.is_a? Sass::SyntaxError + e_string << "\non line #{e.sass_line}" + + if e.sass_filename + e_string << " of #{e.sass_filename}" + + if File.exists?(e.sass_filename) + e_string << "\n\n" + + min = [e.sass_line - 5, 0].max + File.read(e.sass_filename).rstrip.split("\n")[ + min .. e.sass_line + 5 + ].each_with_index do |line, i| + e_string << "#{min + i + 1}: #{line}\n" + end + end + end + end + result = "/*\n#{e_string}\n\nBacktrace:\n#{e.backtrace.join("\n")}\n*/" + else + result = "/* Internal stylesheet error */" + end + end + + Dir.mkdir(l_options[:css_location]) unless File.exist?(l_options[:css_location]) + File.open(css, 'w') do |file| + file.print(result) + end + end + end + end + + private + + def template_filename(name) + "#{@@options[:template_location]}/#{name}.sass" + end + + def css_filename(name) + "#{@@options[:css_location]}/#{name}.css" + end + + def stylesheet_needs_update?(name) + !File.exists?(css_filename(name)) || (File.mtime(template_filename(name)) - 2) > File.mtime(css_filename(name)) + end + end + end +end + +# This module refers to the ActionController module that's part of Ruby on Rails. +# Sass can be used as an alternate templating engine for Rails, +# and includes some modifications to make this more doable. +# The documentation can be found +# here[http://rubyonrails.org/api/classes/ActionController/Base.html]. +module ActionController + class Base # :nodoc: + alias_method :sass_old_process, :process + def process(*args) + Sass::Plugin.update_stylesheets if Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check] + sass_old_process(*args) + end + end +end diff --git a/lib/sass/tree/attr_node.rb b/lib/sass/tree/attr_node.rb new file mode 100644 index 00000000..90127047 --- /dev/null +++ b/lib/sass/tree/attr_node.rb @@ -0,0 +1,44 @@ +require 'sass/tree/node' + +module Sass::Tree + class AttrNode < ValueNode + attr_accessor :name + + def initialize(name, value, style) + @name = name + super(value, style) + end + + def to_s(parent_name = nil) + if name[-1] == ?: || value[-1] == ?; + raise Sass::SyntaxError.new("Invalid attribute: #{declaration.dump} (This isn't CSS!)", @line) + end + real_name = name + real_name = "#{parent_name}-#{real_name}" if parent_name + if children.size > 0 + to_return = String.new + children.each do |kid| + if @style == :compact + to_return << "#{kid.to_s(real_name)} " + else + to_return << "#{kid.to_s(real_name)}\n" + end + end + to_return << "\n" unless @style == :compact + to_return[0...-1] + else + if value.length < 1 + raise Sass::SyntaxError.new("Invalid attribute: #{declaration.dump}", @line) + end + + "#{real_name}: #{value};" + end + end + + private + + def declaration + ":#{name} #{value}" + end + end +end diff --git a/lib/sass/tree/node.rb b/lib/sass/tree/node.rb new file mode 100644 index 00000000..4655a50f --- /dev/null +++ b/lib/sass/tree/node.rb @@ -0,0 +1,29 @@ +module Sass + module Tree + class Node + attr_accessor :children + attr_accessor :line + + def initialize(style) + @style = style + @children = [] + end + + def <<(child) + @children << child + end + + def to_s + result = String.new + children.each do |child| + if child.is_a? AttrNode + raise SyntaxError.new('Attributes aren\'t allowed at the root of a document.', child.line) + end + + result += "#{child.to_s(1)}\n" + end + result[0...-1] + end + end + end +end diff --git a/lib/sass/tree/rule_node.rb b/lib/sass/tree/rule_node.rb new file mode 100644 index 00000000..ca26d314 --- /dev/null +++ b/lib/sass/tree/rule_node.rb @@ -0,0 +1,47 @@ +require 'sass/tree/node' +require 'sass/tree/attr_node' + +module Sass::Tree + class RuleNode < ValueNode + alias_method :rule, :value + alias_method :rule=, :value= + + def to_s(tabs, super_rules = nil) + attributes = [] + sub_rules = [] + total_rule = if super_rules + super_rules.split(/,\s*/).collect! do |s| + self.rule.split(/,\s*/).collect! {|r| "#{s} #{r}"}.join(", ") + end.join(", ") + else + self.rule + end + + children.each do |child| + if child.is_a? AttrNode + attributes << child + else + sub_rules << child + end + end + + to_return = '' + unless attributes.empty? + if @style == :compact + to_return << "#{total_rule} { #{attributes.join(' ')} }\n" + else + spaces = (@style == :expanded ? 2 : tabs * 2) + old_spaces = ' ' * (spaces - 2) + spaces = ' ' * spaces + + attributes = attributes.join("\n").gsub("\n", "\n#{spaces}").rstrip + end_attrs = (@style == :expanded ? "\n" : ' ') + to_return << "#{old_spaces}#{total_rule} {\n#{spaces}#{attributes}#{end_attrs}}\n" + end + end + + sub_rules.each { |sub| to_return << sub.to_s(tabs + 1, total_rule) } + to_return + end + end +end diff --git a/lib/sass/tree/value_node.rb b/lib/sass/tree/value_node.rb new file mode 100644 index 00000000..30adf202 --- /dev/null +++ b/lib/sass/tree/value_node.rb @@ -0,0 +1,12 @@ +require 'sass/tree/node' + +module Sass::Tree + class ValueNode < Node + attr_accessor :value + + def initialize(value, style) + @value = value + super(style) + end + end +end diff --git a/test/benchmark.rb b/test/benchmark.rb new file mode 100644 index 00000000..11a975b8 --- /dev/null +++ b/test/benchmark.rb @@ -0,0 +1,59 @@ +require File.dirname(__FILE__) + '/../lib/haml' +require 'haml/template' +require 'sass/engine' +require 'rubygems' +require 'active_support' +require 'action_view' +require 'benchmark' +require 'stringio' + +module Haml + class Benchmarker + + # Creates a new benchmarker that looks for templates in the base + # directory. + def initialize(base = File.dirname(__FILE__)) + ActionView::Base.register_template_handler("haml", Haml::Template) + unless base.class == ActionView::Base + @base = ActionView::Base.new(base) + else + @base = base + end + end + + # Benchmarks haml against ERb, and Sass on its own. + # + # Returns the results of the benchmarking as a string. + # + def benchmark(runs = 100) + template_name = 'standard' + haml_template = "haml/templates/#{template_name}" + rhtml_template = "haml/rhtml/#{template_name}" + sass_template = File.dirname(__FILE__) + "/sass/templates/complex.sass" + + old_stdout = $stdout + $stdout = StringIO.new + + times = Benchmark.bmbm do |b| + b.report("haml:") { runs.times { @base.render haml_template } } + b.report("erb:") { runs.times { @base.render rhtml_template } } + end + + #puts times[0].inspect, times[1].inspect + ratio = sprintf("%g", times[0].to_a[5] / times[1].to_a[5]) + puts "Haml/ERB: " + ratio + + puts '', '-' * 50, 'Sass on its own', '-' * 50 + + Benchmark.bmbm do |b| + b.report("sass:") { runs.times { Sass::Engine.new(File.read(sass_template)).render } } + end + + $stdout.pos = 0 + to_return = $stdout.read + $stdout = old_stdout + + to_return + end + end +end diff --git a/test/haml/engine_test.rb b/test/haml/engine_test.rb new file mode 100644 index 00000000..3a750914 --- /dev/null +++ b/test/haml/engine_test.rb @@ -0,0 +1,220 @@ +#!/usr/bin/env ruby + +require 'test/unit' +require File.dirname(__FILE__) + '/../../lib/haml' +require 'haml/engine' + +class EngineTest < Test::Unit::TestCase + + def render(text, options = {}) + Haml::Engine.new(text, options).to_html + end + + def test_empty_render_should_remain_empty + assert_equal('', render('')) + end + + # 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("
    \n
    ", render(".atlantis{:style => 'ugly'}").chomp) + rescue + assert_equal("
    \n
    ", render(".atlantis{:style => 'ugly'}").chomp) + end + + def test_ruby_code_should_work_inside_attributes + author = 'hcatlin' + assert_equal("

    foo

    ", render("%p{:class => 1+2} foo").chomp) + end + + def test_nil_should_render_empty_tag + assert_equal("
    \n
    ", + render(".no_attributes{:nil => nil}").chomp) + end + + def test_strings_should_get_stripped_inside_tags + assert_equal("
    This should have no spaces in front of it
    ", + render(".stripped This should have no spaces in front of it").chomp) + end + + def test_one_liner_should_be_one_line + assert_equal("

    Hello

    ", render('%p Hello').chomp) + end + + def test_long_liner_should_not_print_on_one_line + assert_equal("
    \n #{'x' * 51}\n
    ", render("%div #{'x' * 51}").chomp) + end + + def test_multi_render + engine = Haml::Engine.new("%strong Hi there!") + assert_equal("Hi there!\n", engine.to_html) + assert_equal("Hi there!\n", engine.to_html) + assert_equal("Hi there!\n", engine.to_html) + end + + # Options tests + + def test_stop_eval + assert_equal("", render("= 'Hello'", :suppress_eval => true)) + end + + def test_attr_wrapper + assert_equal("

    \n

    \n", render("%p{ :strange => 'attrs'}", :attr_wrapper => '*')) + assert_equal("

    \n

    \n", render("%p{ :escaped => 'quo\"te'}", :attr_wrapper => '"')) + assert_equal("

    \n

    \n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"')) + assert_equal("\n", render("!!! XML", :attr_wrapper => '"')) + end + + def test_locals + assert_equal("

    Paragraph!

    \n", render("%p= text", :locals => { :text => "Paragraph!" })) + end + + def test_precompiled + precompiled = <<-END + def _haml_render + _hamlout = @haml_stack[-1] + _erbout = _hamlout.buffer + + _hamlout.open_tag("p", 0, nil, true, "", nil, nil, false) + @haml_lineno = 1 + haml_temp = "Haml Rocks Socks" + haml_temp = _hamlout.push_script(haml_temp, 1, false) + _hamlout.close_tag("p", 0) + end + END + + assert_equal("

    Haml Rocks Socks

    \n", render("%h1 I shall not be rendered", :precompiled => precompiled)) + end + + def test_comps + assert_equal(-1, "foo" <=> nil) + assert_equal(1, nil <=> "foo") + end + + def test_rec_merge + hash1 = {1=>2, 3=>{5=>7, 8=>9}} + hash1_2 = hash1.clone + hash2 = {4=>5, 3=>{5=>2, 16=>12}} + hash3 = {1=>2, 4=>5, 3=>{5=>2, 8=>9, 16=>12}} + + assert_equal(hash3, hash1.rec_merge(hash2)) + assert_equal(hash1_2, hash1) + hash1.rec_merge!(hash2) + assert_equal(hash3, hash1) + end + + def test_exception_type + begin + render("%p hi\n= undefined") + rescue Exception => e + assert(e.is_a?(Haml::Error)) + assert_equal(2, e.haml_line) + assert_equal(nil, e.haml_filename) + assert_equal('(haml):2', e.backtrace[0]) + else + # Test failed... should have raised an exception + assert(false) + end + end + + def test_syntax_errors + errs = [ "!!!\n a", "a\n b", "a\n:foo\nb", "/ a\n b", + "% a", "%p a\n b", "a\n%p=\nb", "%p=\n a", + "a\n%p~\nb", "a\n~\nb", "%p/\n a", "%p\n \t%a b", + "%a\n b\nc", "%a\n b\nc", + ":notafilter\n This isn't\n a filter!", + ] + errs.each do |err| + begin + render(err) + rescue Exception => e + assert(e.is_a?(Haml::Error), + "#{err.dump} doesn't produce Haml::SyntaxError!") + else + assert(false, + "#{err.dump} doesn't produce an exception!") + end + end + end + + def test_compile_error + begin + render("a\nb\n- fee do\nc") + rescue Exception => e + assert_equal(3, e.haml_line) + else + assert(false, + '"a\nb\n- fee do\nc" doesn\'t produce an exception!') + end + end + + def test_no_bluecloth + old_markdown = false + if defined?(Haml::Filters::Markdown) + old_markdown = Haml::Filters::Markdown + end + + Kernel.module_eval do + alias_method :haml_old_require, :gem_original_require + + def gem_original_require(file) + raise LoadError if file == 'bluecloth' + haml_old_require(file) + end + end + + if old_markdown + Haml::Filters.instance_eval do + remove_const 'Markdown' + end + end + + # This is purposefully redundant, so it doesn't stop + # haml/filters from being required later on. + require 'haml/../haml/filters' + + assert_equal("

    Foo

    \t

    - a\n- b

    \n", + Haml::Engine.new(":markdown\n Foo\n ===\n - a\n - b").to_html) + + Haml::Filters.instance_eval do + remove_const 'Markdown' + end + + Haml::Filters.const_set('Markdown', old_markdown) if old_markdown + + Kernel.module_eval do + alias_method :gem_original_require, :haml_old_require + end + + NOT_LOADED.delete 'bluecloth' + end + + def test_no_redcloth + Kernel.module_eval do + alias_method :haml_old_require2, :gem_original_require + + def gem_original_require(file) + raise LoadError if file == 'redcloth' + haml_old_require2(file) + end + end + + # This is purposefully redundant, so it doesn't stop + # haml/filters from being required later on. + require 'haml/../haml/../haml/filters' + + begin + Haml::Engine.new(":redcloth\n _foo_").to_html + rescue Haml::HamlError + else + assert(false, "No exception raised!") + end + + Kernel.module_eval do + alias_method :gem_original_require2, :haml_old_require + end + + NOT_LOADED.delete 'redcloth' + end +end diff --git a/test/haml/helper_test.rb b/test/haml/helper_test.rb new file mode 100644 index 00000000..c3c1fdcf --- /dev/null +++ b/test/haml/helper_test.rb @@ -0,0 +1,106 @@ +#!/usr/bin/env ruby + +require 'test/unit' +require File.dirname(__FILE__) + '/../../lib/haml' +require 'haml/template' + +class HelperTest < Test::Unit::TestCase + include Haml::Helpers + + def setup + ActionView::Base.register_template_handler("haml", Haml::Template) + @base = ActionView::Base.new + @base.controller = ActionController::Base.new + end + + def render(text, options = {}) + if options == :action_view + @base.render :inline => text, :type => :haml + else + Haml::Engine.new(text, options).to_html + end + end + + def test_flatten + assert_equal(flatten("FooBar"), "FooBar") + + assert_equal(flatten("Foo\rBar"), "FooBar") + + assert_equal(flatten("Foo\nBar"), "Foo Bar") + + assert_equal(flatten("Hello\nWorld!\nYOU ARE \rFLAT?\n\rOMGZ!"), + "Hello World! YOU ARE FLAT? OMGZ!") + end + + def test_list_of_should_render_correctly + assert_equal("
  • 1
  • \n
  • 2
  • \n", render("= list_of([1, 2]) do |i|\n = i")) + assert_equal("
  • 1
  • \n", render("= list_of([[1]]) do |i|\n = i.first")) + assert_equal("
  • \n

    Fee

    \n

    A word!

    \n
  • \n
  • \n

    Fi

    \n

    A word!

    \n
  • \n
  • \n

    Fo

    \n

    A word!

    \n
  • \n
  • \n

    Fum

    \n

    A word!

    \n
  • \n", + render("= list_of(['Fee', 'Fi', 'Fo', 'Fum']) do |title|\n %h1= title\n %p A word!")) + end + + def test_buffer_access + assert(render("= buffer") =~ /#/) + assert_equal(render("= (buffer == _hamlout)"), "true\n") + end + + def test_tabs + assert_equal(render("foo\n- tab_up\nbar\n- tab_down\nbaz"), "foo\n bar\nbaz\n") + end + + def test_helpers_dont_leak + # Haml helpers shouldn't be accessible from ERB + render("foo") + proper_behavior = false + + begin + ActionView::Base.new.render(:inline => "<%= flatten('Foo\\nBar') %>") + rescue NoMethodError + proper_behavior = true + end + assert(proper_behavior) + + begin + ActionView::Base.new.render(:inline => "<%= concat('foo') %>") + rescue ArgumentError + proper_behavior = true + end + assert(proper_behavior) + end + + def test_action_view_included + assert(Haml::Helpers.action_view?) + end + + def test_action_view_not_included + #This is for 100% rcov, rather than any real testing purposes. + Kernel.module_eval do + alias_method :old_require, :require + def require(string) + raise LoadError if string == "action_view" + old_require string + end + end + + load File.dirname(__FILE__) + '/../../lib/haml/helpers/action_view_mods.rb' + + Kernel.module_eval do + alias_method :require, :old_require + end + end + + def test_form_tag + result = render("- form_tag 'foo' do\n %p bar\n %strong baz", :action_view) + should_be = "
    \n

    bar

    \n baz\n
    \n" + assert_equal(should_be, result) + end + + def test_capture_haml + assert_equal("\"

    13

    \\n\"\n", render("- foo = capture_haml(13) do |a|\n %p= a\n= foo.dump")) + end + + def test_is_haml + assert(!ActionView::Base.new.is_haml?) + end +end + diff --git a/test/haml/mocks/article.rb b/test/haml/mocks/article.rb new file mode 100644 index 00000000..805f8cad --- /dev/null +++ b/test/haml/mocks/article.rb @@ -0,0 +1,6 @@ +class Article + attr_accessor :id, :title, :body + def initialize + @id, @title, @body = 1, 'Hello', 'World' + end +end \ No newline at end of file diff --git a/test/haml/results/content_for_layout.xhtml b/test/haml/results/content_for_layout.xhtml new file mode 100644 index 00000000..8761e357 --- /dev/null +++ b/test/haml/results/content_for_layout.xhtml @@ -0,0 +1,16 @@ + + + + + +
    + Lorem ipsum dolor sit amet +
    +
    + Lorem ipsum dolor sit amet +
    +
    + Lorem ipsum dolor sit amet +
    + + diff --git a/test/haml/results/eval_suppressed.xhtml b/test/haml/results/eval_suppressed.xhtml new file mode 100644 index 00000000..c79ba72b --- /dev/null +++ b/test/haml/results/eval_suppressed.xhtml @@ -0,0 +1,2 @@ +

    +

    Me!

    diff --git a/test/haml/results/filters.xhtml b/test/haml/results/filters.xhtml new file mode 100644 index 00000000..a336a9c3 --- /dev/null +++ b/test/haml/results/filters.xhtml @@ -0,0 +1,57 @@ + +TESTING HAHAHAHA! +

    Foo

    + + +
    This is preformatted!
    +Look at that!
    +Wowie-zowie!
    + + +

    boldilicious!

    +

    Yeah

    + + +

    pretty much the same as above

    +This + Is + Plain + Text + %strong right? + + a + + b + + c + + d + + e + + f + + g + + h + + i + + j +
      +
    • Foo
    • +
    • Bar
    • +
    • BAZ!
    • +
    +Text! +Hello, World! +How are you doing today? + diff --git a/test/haml/results/helpers.xhtml b/test/haml/results/helpers.xhtml new file mode 100644 index 00000000..ccedf4ed --- /dev/null +++ b/test/haml/results/helpers.xhtml @@ -0,0 +1,60 @@ +&&&&&&&&&&& +
    +

    Title

    +

    + Woah this is really crazy + I mean wow, + man. +

    +
    +
    +

    Title

    +

    + Woah this is really crazy + I mean wow, + man. +

    +
    +
    +

    Title

    +

    + Woah this is really crazy + I mean wow, + man. +

    +
    +

    foo

    +

    + reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally loooooooooooooooooong +

    +
    +
    +
    +

    Big!

    +

    Small

    + +
    +
    +

    foo

    +

    bar

    +
    +
    + (parentheses!) +
    +*Not really +click +here. +

    baz

    +

    boom

    +foo +
    + +
    +
    +
    + Title: + + Body: + +
    +
  • google
  • diff --git a/test/haml/results/helpful.xhtml b/test/haml/results/helpful.xhtml new file mode 100644 index 00000000..8d5e78d9 --- /dev/null +++ b/test/haml/results/helpful.xhtml @@ -0,0 +1,8 @@ +
    +

    Hello

    +
    World
    +
    +
    id
    +
    class
    +
    id class
    +
    boo
    diff --git a/test/haml/results/just_stuff.xhtml b/test/haml/results/just_stuff.xhtml new file mode 100644 index 00000000..67377187 --- /dev/null +++ b/test/haml/results/just_stuff.xhtml @@ -0,0 +1,43 @@ + + + + + + + + +Boo! +Embedded? false! +Embedded? true! +Embedded? true! +
    wow!
    +stuff followed by whitespace +block with whitespace +

    + Escape + - character + %p foo + yee\ha +

    + + + +

    class attribute shouldn't appear!

    + + + +testtest diff --git a/test/haml/results/list.xhtml b/test/haml/results/list.xhtml new file mode 100644 index 00000000..b8ef0b0d --- /dev/null +++ b/test/haml/results/list.xhtml @@ -0,0 +1,12 @@ +! Not a Doctype ! +
      +
    • a
    • +
    • b
    • +
    • c
    • +
    • d
    • +
    • e
    • +
    • f
    • +
    • g
    • +
    • h
    • +
    • i
    • +
    diff --git a/test/haml/results/original_engine.xhtml b/test/haml/results/original_engine.xhtml new file mode 100644 index 00000000..600fc98e --- /dev/null +++ b/test/haml/results/original_engine.xhtml @@ -0,0 +1,24 @@ + + + + Stop. haml time +
    +

    This is a title!

    +

    + Lorem ipsum dolor sit amet, consectetur adipisicing elit +

    +

    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/haml/results/partials.xhtml b/test/haml/results/partials.xhtml new file mode 100644 index 00000000..3c8c437e --- /dev/null +++ b/test/haml/results/partials.xhtml @@ -0,0 +1,20 @@ +

    + @foo = + value one +

    +

    + @foo = + value two +

    +

    + @foo = + value two +

    +

    + @foo = + value three +

    +

    + @foo = + value three +

    diff --git a/test/haml/results/silent_script.xhtml b/test/haml/results/silent_script.xhtml new file mode 100644 index 00000000..76e90e0b --- /dev/null +++ b/test/haml/results/silent_script.xhtml @@ -0,0 +1,74 @@ +
    +

    I can count!

    + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 +

    I know my ABCs!

    +
      +
    • a
    • +
    • b
    • +
    • c
    • +
    • d
    • +
    • e
    • +
    • f
    • +
    • g
    • +
    • h
    • +
    • i
    • +
    • j
    • +
    • k
    • +
    • l
    • +
    • m
    • +
    • n
    • +
    • o
    • +
    • p
    • +
    • q
    • +
    • r
    • +
    • s
    • +
    • t
    • +
    • u
    • +
    • v
    • +
    • w
    • +
    • x
    • +
    • y
    • +
    • z
    • +
    +

    I can catch errors!

    + Oh no! "undefined method `silly' for String:Class" happened! +

    + "false" is: + false +

    + Even! + Odd! + Even! + Odd! + Even! +
    +
    + foobar +
    +0 +1 +2 +3 +4 +
    +

    boom

    +
    diff --git a/test/haml/results/standard.xhtml b/test/haml/results/standard.xhtml new file mode 100644 index 00000000..b19b8010 --- /dev/null +++ b/test/haml/results/standard.xhtml @@ -0,0 +1,43 @@ + + + + Hampton Catlin Is Totally Awesome + + + + +foo +
    + Yes, ladies and gentileman. He is just that egotistical. + Fantastic! This should be multi-line output + The question is if this would translate! Ahah! + 20 +
    +
    Quotes should be loved! Just like people!
    + Wow.| +

    + Holy cow multiline tags! A pipe (|) even! + PipesIgnored|PipesIgnored|PipesIgnored| + 1|2|3 +

    +
    + this shouldn't evaluate but now it should! +
    +
      +
    • a
    • +
    • b
    • +
    • c
    • +
    • d
    • +
    • e
    • +
    • f
    • +
    +
    with this text
    + hello + + + diff --git a/test/haml/results/tag_parsing.xhtml b/test/haml/results/tag_parsing.xhtml new file mode 100644 index 00000000..c1d694c4 --- /dev/null +++ b/test/haml/results/tag_parsing.xhtml @@ -0,0 +1,28 @@ +
    + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 +
    +
    +

    +

    +
    a
    +
    b
    +
    c
    +
    d
    +
    e
    +
    f
    +
    g
    +
    +
    + <{ :a => :b } +
    >{ :c => :d }
    +
    diff --git a/test/haml/results/very_basic.xhtml b/test/haml/results/very_basic.xhtml new file mode 100644 index 00000000..9713fb1c --- /dev/null +++ b/test/haml/results/very_basic.xhtml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/haml/results/whitespace_handling.xhtml b/test/haml/results/whitespace_handling.xhtml new file mode 100644 index 00000000..a435570e --- /dev/null +++ b/test/haml/results/whitespace_handling.xhtml @@ -0,0 +1,104 @@ +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    + Foo bar +
    foo bar
    +
    foo
    bar
    +

    foo
    bar

    +

    + foo + bar +

    +
                                                     ___
                                                  ,o88888
                                               ,o8888888'
                         ,:o:o:oooo.        ,8O88Pd8888"
                     ,.::.::o:ooooOoOoO. ,oO8O8Pd888'"
                   ,.:.::o:ooOoOoOO8O8OOo.8OOPd8O8O"
                  , ..:.::o:ooOoOOOO8OOOOo.FdO8O8"
                 , ..:.::o:ooOoOO8O888O8O,COCOO"
                , . ..:.::o:ooOoOOOO8OOOOCOCO"
                 . ..:.::o:ooOoOoOO8O8OCCCC"o
                    . ..:.::o:ooooOoCoCCC"o:o
                    . ..:.::o:o:,cooooCo"oo:o:
                 `   . . ..:.:cocoooo"'o:o:::'
                 .`   . ..::ccccoc"'o:o:o:::'
                :.:.    ,c:cccc"':.:.:.:.:.'
              ..:.:"'`::::c:"'..:.:.:.:.:.'  http://www.chris.com/ASCII/
            ...:.'.:.::::"'    . . . . .'
           .. . ....:."' `   .  . . ''
         . . . ...."'
         .. . ."'     -hrr-
        .
    
    
                                                  It's a planet!
    %strong This shouldn't be bold!
    
    + This should! + +
    +
    + 13 + +
    +
    +foo
    
    +  bar
    +
    +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    + Foo bar +
    foo bar
    +
    foo
    bar
    +

    foo
    bar

    +

    + foo + bar +

    +
    +                                                 ___
                                                  ,o88888
                                               ,o8888888'
                         ,:o:o:oooo.        ,8O88Pd8888"
                     ,.::.::o:ooooOoOoO. ,oO8O8Pd888'"
                   ,.:.::o:ooOoOoOO8O8OOo.8OOPd8O8O"
                  , ..:.::o:ooOoOOOO8OOOOo.FdO8O8"
                 , ..:.::o:ooOoOO8O888O8O,COCOO"
                , . ..:.::o:ooOoOOOO8OOOOCOCO"
                 . ..:.::o:ooOoOoOO8O8OCCCC"o
                    . ..:.::o:ooooOoCoCCC"o:o
                    . ..:.::o:o:,cooooCo"oo:o:
                 `   . . ..:.:cocoooo"'o:o:::'
                 .`   . ..::ccccoc"'o:o:o:::'
                :.:.    ,c:cccc"':.:.:.:.:.'
              ..:.:"'`::::c:"'..:.:.:.:.:.'  http://www.chris.com/ASCII/
            ...:.'.:.::::"'    . . . . .'
           .. . ....:."' `   .  . . ''
         . . . ...."'
         .. . ."'     -hrr-
        .
    
    
                                                  It's a planet!
    %strong This shouldn't be bold!
    
    +  
    + This should! + +
    +
    + 13 +
    +
    +       __     ______        __               ______
    .----.|  |--.|__    |.----.|  |--..--------.|  __  |
    |  __||     ||__    ||  __||    < |        ||  __  |
    |____||__|__||______||____||__|__||__|__|__||______|
    
    +
    +
    +foo
    
    +  bar
    +
    \ No newline at end of file diff --git a/test/haml/rhtml/standard.rhtml b/test/haml/rhtml/standard.rhtml new file mode 100644 index 00000000..7b66d61e --- /dev/null +++ b/test/haml/rhtml/standard.rhtml @@ -0,0 +1,55 @@ + + + + Hampton Catlin Is Totally Awesome + + + + + <% concat("Foo", Proc.new {}) %> +
    + Yes, ladies and gentileman. He is just that egotistical. + Fantastic! This should be multi-line output + The question is if this would translate! Ahah! + <%= 1 + 9 + 8 + 2 %> + <%# numbers should work and this should be ignored %> +
    + <% 120.times do |number| -%> + <%= number %> + <% end -%> +
    <%= " Quotes should be loved! Just like people!" %>
    + Wow. +

    + <%= "Holy cow " + + "multiline " + + "tags! " + + "A pipe (|) even!" %> + <%= [1, 2, 3].collect { |n| "PipesIgnored|" } %> + <%= [1, 2, 3].collect { |n| + n.to_s + }.join("|") %> +

    +
    + <% foo = String.new + foo << "this" + foo << " shouldn't" + foo << " evaluate" %> + <%= foo + "but now it should!" %> + <%# Woah crap a comment! %> +
    +
      + <% ('a'..'f').each do |a|%> +
    • <%= a %> + <% end %> +
      <%= @should_eval = "with this text" %>
      + <%= [ 104, 101, 108, 108, 111 ].map do |byte| + byte.chr + end %> + + + diff --git a/test/haml/runner.rb b/test/haml/runner.rb new file mode 100644 index 00000000..e6ba1cea --- /dev/null +++ b/test/haml/runner.rb @@ -0,0 +1,15 @@ +require 'rubygems' +require 'active_support' +require 'action_view' +require '../../lib/haml/template' +require 'fileutils' + +haml_template_engine = Haml::Template.new(ActionView::Base.new) +haml_template_engine.render(File.dirname(__FILE__) + '/templates/standard.haml') + +begin + eval(File.read("template_test.rb")) +rescue StandardError => e + puts e.backtrace + puts e.inspect +end diff --git a/test/haml/template_test.rb b/test/haml/template_test.rb new file mode 100644 index 00000000..2ed33eaa --- /dev/null +++ b/test/haml/template_test.rb @@ -0,0 +1,150 @@ +#!/usr/bin/env ruby + +require 'test/unit' +require 'rubygems' +require 'active_support' +require 'action_view' + +require File.dirname(__FILE__) + '/../../lib/haml' +require 'haml/template' +require File.dirname(__FILE__) + '/mocks/article' + +class TestFilter + def initialize(text) + @text = text + end + + def render + "TESTING HAHAHAHA!" + end +end + +class TemplateTest < Test::Unit::TestCase + @@templates = %w{ very_basic standard helpers + whitespace_handling original_engine list helpful + silent_script tag_parsing just_stuff partials + filters } + + def setup + ActionView::Base.register_template_handler("haml", Haml::Template) + Haml::Template.options = { :filters => { 'test'=>TestFilter } } + @base = ActionView::Base.new(File.dirname(__FILE__) + "/templates/", {'article' => Article.new, 'foo' => 'value one'}) + end + + def render(text) + Haml::Engine.new(text).to_html(@base) + 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) + test = Proc.new do |rendered| + load_result(name).split("\n").zip(rendered.split("\n")).each_with_index do |pair, line| + message = "template: #{name}\nline: #{line}" + assert_equal(pair.last, pair.first, message) + end + end + test.call(@base.render(name)) + test.call(@base.render(:file => "partialize", :locals => { :name => name })) + end + + def test_empty_render_should_remain_empty + assert_equal('', render('')) + end + + def test_templates_should_render_correctly + @@templates.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("

      something

      ", render("%p= @content_for_layout").chomp) + + @base.instance_eval("@author = 'Hampton Catlin'") + assert_equal("
      Hampton Catlin
      ", 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("

      foo

      ", render("%p{:class => @author} foo").chomp) + end + + def test_template_renders_should_eval + assert_equal("2\n", render("= 1+1")) + end + + def test_rhtml_still_renders + # Make sure it renders normally + res = @base.render("../rhtml/standard") + assert !(res.nil? || res.empty?) + + # Register Haml stuff in @base... + @base.render("standard") + + # Does it still render? + res = @base.render("../rhtml/standard") + assert !(res.nil? || res.empty?) + end + + def test_haml_options + Haml::Template.options = { :suppress_eval => true } + assert_equal({ :suppress_eval => true }, Haml::Template.options) + assert_renders_correctly("eval_suppressed") + Haml::Template.options = {} + end + + def test_exceptions_should_work_correctly + begin + Haml::Template.new(@base).render(File.dirname(__FILE__) + '/templates/breakage.haml') + rescue Exception => e + assert_equal("./test/haml/templates/breakage.haml:4", e.backtrace[0]) + else + assert false + end + + begin + render("- raise 'oops!'") + rescue Exception => e + assert_equal("(haml):1", e.backtrace[0]) + else + assert false + end + + template = < e + assert_equal("(haml):5", e.backtrace[0]) + else + assert false + end + end +end diff --git a/test/haml/templates/_partial.haml b/test/haml/templates/_partial.haml new file mode 100644 index 00000000..00e701db --- /dev/null +++ b/test/haml/templates/_partial.haml @@ -0,0 +1,7 @@ +%p + @foo = + = @foo +- @foo = 'value three' +%p + @foo = + = @foo diff --git a/test/haml/templates/_text_area.haml b/test/haml/templates/_text_area.haml new file mode 100644 index 00000000..896b9758 --- /dev/null +++ b/test/haml/templates/_text_area.haml @@ -0,0 +1,3 @@ +.text_area_test_area + ~ "" += "" diff --git a/test/haml/templates/breakage.haml b/test/haml/templates/breakage.haml new file mode 100644 index 00000000..57c17341 --- /dev/null +++ b/test/haml/templates/breakage.haml @@ -0,0 +1,8 @@ +%p + %h1 Hello! + = "lots of lines" + - raise "Oh no!" + %p + this is after the exception + %strong yes it is! +ho ho ho. diff --git a/test/haml/templates/content_for_layout.haml b/test/haml/templates/content_for_layout.haml new file mode 100644 index 00000000..fda84a57 --- /dev/null +++ b/test/haml/templates/content_for_layout.haml @@ -0,0 +1,10 @@ +!!! +%html + %head + %body + #content + = @content_for_layout + #yieldy + = yield :layout + #nosym + = yield diff --git a/test/haml/templates/eval_suppressed.haml b/test/haml/templates/eval_suppressed.haml new file mode 100644 index 00000000..8b39aa8a --- /dev/null +++ b/test/haml/templates/eval_suppressed.haml @@ -0,0 +1,5 @@ += "not me!" += "nor me!" +- foo = "not even me!" +%p= foo +%h1 Me! diff --git a/test/haml/templates/filters.haml b/test/haml/templates/filters.haml new file mode 100644 index 00000000..aedbb2c3 --- /dev/null +++ b/test/haml/templates/filters.haml @@ -0,0 +1,53 @@ +%style + :sass + p + :border + :style dotted + :width 10px + :color #ff00ff + h1 + :font-weight normal + +:test + This + Should + Not + Print + +:redcloth + Foo + === + + This is preformatted! + Look at that! + Wowie-zowie! + + *boldilicious!* + +:textile + h1. Yeah + + _pretty much the same as above_ + +:plain + This + Is + Plain + Text + %strong right? + +:erb + <% 10.times do |c| %> + <%= (c+97).chr %> + <% end %> + +:markdown + * Foo + * Bar + * BAZ! + += "Text!" + +:ruby + puts "Hello, World!" + puts "How are you doing today?" diff --git a/test/haml/templates/helpers.haml b/test/haml/templates/helpers.haml new file mode 100644 index 00000000..bf99c520 --- /dev/null +++ b/test/haml/templates/helpers.haml @@ -0,0 +1,48 @@ += h("&&&&&&&&&&&") # This is an ActionView Helper... should load +- foo = capture do # This ActionView Helper is designed for ERB, but should work with haml + %div + %p.title Title + %p.text + Woah this is really crazy + I mean wow, + man. +- 3.times do + = foo +%p foo +- tab_up +%p reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally loooooooooooooooooong +- tab_down +.woah + #funky + = capture_haml do + %div + %h1 Big! + %p Small + / Invisible + = capture do + .dilly + %p foo + %h1 bar + = surround '(', ')' do + %strong parentheses! += precede '*' do + %span.small Not really +click += succeed '.' do + %a{:href=>"thing"} here +%p baz +- buffer.tabulation = 10 +%p boom +- concat "foo\n" +- buffer.tabulation = 0 +- def url_for(*stuff); stuff.join(' '); end += form_tag 'hello/world' +- form_tag 'heeheeaform' do + %div= submit_tag 'save' +- form_for :article, @article, :url => 'article_url' do |f| + Title: + = f.text_field :title + Body: + = f.text_field :body += list_of({:google => 'http://www.google.com'}) do |name, link| + %a{ :href => link }= name diff --git a/test/haml/templates/helpful.haml b/test/haml/templates/helpful.haml new file mode 100644 index 00000000..825add30 --- /dev/null +++ b/test/haml/templates/helpful.haml @@ -0,0 +1,9 @@ +%div[@article] + %h1= @article.title + %div= @article.body +#id[@article] id +.class[@article] class +#id.class[@article] id class +%div{:class => "article full"}[@article]= "boo" +%span[@not_a_real_variable_and_will_be_nil] + Boo diff --git a/test/haml/templates/just_stuff.haml b/test/haml/templates/just_stuff.haml new file mode 100644 index 00000000..2986cf7c --- /dev/null +++ b/test/haml/templates/just_stuff.haml @@ -0,0 +1,41 @@ +!!! XML +!!! XML ISO-8859-1 +!!! XML UtF-8 Foo bar +!!! +!!! 1.1 +!!! 1.1 Strict +!!! Strict foo bar +!!! FRAMESET +%strong{:apos => "Foo's bar!"} Boo! +== Embedded? false! +== Embedded? #{true}! +- embedded = true +== Embedded? #{embedded}! +.render= render :inline => "%em= 'wow!'", :type => :haml += "stuff followed by whitespace" + +- if true + + %strong block with whitespace +%p + \Escape + \- character + \%p foo + \yee\ha +/ Short comment +/ This is a really long comment look how long it is it should be on a line of its own don't you think? +/ + This is a block comment + cool, huh? + %strong there can even be sub-tags! + = "Or script!" +%p{ :class => "" } class attribute shouldn't appear! +/[if lte IE6] conditional comment! +/[if gte IE7] + %p Block conditional comment + %div + %h1 Cool, eh? +/[if gte IE5.2] + Woah a period. += "test" | + "test" | \ No newline at end of file diff --git a/test/haml/templates/list.haml b/test/haml/templates/list.haml new file mode 100644 index 00000000..52605b1a --- /dev/null +++ b/test/haml/templates/list.haml @@ -0,0 +1,12 @@ +! Not a Doctype ! +%ul + %li a + %li b + %li c + %li d + %li e + %li f + %li g + %li h + %li i + diff --git a/test/haml/templates/original_engine.haml b/test/haml/templates/original_engine.haml new file mode 100644 index 00000000..df31a5aa --- /dev/null +++ b/test/haml/templates/original_engine.haml @@ -0,0 +1,17 @@ +!!! +%html + %head + %title Stop. haml time + #content + %h1 This is a title! + %p Lorem ipsum dolor sit amet, consectetur adipisicing elit + %p{:class => 'foo'} 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? diff --git a/test/haml/templates/partialize.haml b/test/haml/templates/partialize.haml new file mode 100644 index 00000000..327d90da --- /dev/null +++ b/test/haml/templates/partialize.haml @@ -0,0 +1 @@ += render :file => "#{name}.haml" diff --git a/test/haml/templates/partials.haml b/test/haml/templates/partials.haml new file mode 100644 index 00000000..3fab791d --- /dev/null +++ b/test/haml/templates/partials.haml @@ -0,0 +1,12 @@ +%p + @foo = + = @foo +- @foo = 'value two' +%p + @foo = + = @foo += render :file => "_partial.haml" +%p + @foo = + = @foo +- @foo = 'value one' diff --git a/test/haml/templates/silent_script.haml b/test/haml/templates/silent_script.haml new file mode 100644 index 00000000..45199f0b --- /dev/null +++ b/test/haml/templates/silent_script.haml @@ -0,0 +1,40 @@ +%div + %h1 I can count! + - (1..20).each do |i| + = i + %h1 I know my ABCs! + %ul + - ('a'..'z').each do |i| + %li= i + %h1 I can catch errors! + - begin + - String.silly + - rescue NameError => e + = "Oh no! \"#{e}\" happened!" + %p + "false" is: + - if false + = "true" + - else + = "false" + - if true + - 5.times do |i| + - if i % 2 == 1 + Odd! + - else + Even! + - else + = "This can't happen!" +- 13 | +.foo + %strong foobar +- 5.times | + do | + |a| | + %strong= a +.test + - "foo | + bar | + baz" | + + %p boom diff --git a/test/haml/templates/standard.haml b/test/haml/templates/standard.haml new file mode 100644 index 00000000..b6fa020a --- /dev/null +++ b/test/haml/templates/standard.haml @@ -0,0 +1,43 @@ +!!! +%html{"xml-lang" => "en-US"} + %head + %title Hampton Catlin Is Totally Awesome + %meta{"http-equiv" => "Content-Type", :content => "text/html; charset=utf-8"}/ + %body + / You're In my house now! + - concat("foo\n") + .header + Yes, ladies and gentileman. He is just that egotistical. + Fantastic! This should be multi-line output + The question is if this would translate! Ahah! + = 1 + 9 + 8 + 2 #numbers should work and this should be ignored + #body= " Quotes should be loved! Just like people!" + - 120.times do |number| + - number + Wow.| + %p + = "Holy cow " + | + "multiline " + | + "tags! " + | + "A pipe (|) even!" | + = [1, 2, 3].collect { |n| "PipesIgnored|" } + = [1, 2, 3].collect { |n| | + n.to_s | + }.join("|") | + %div.silent + - foo = String.new + - foo << "this" + - foo << " shouldn't" + - foo << " evaluate" + = foo + " but now it should!" + -# Woah crap a comment! + + -# That was a line that shouldn't close everything. + %ul.really.cool + - ('a'..'f').each do |a| + %li= a + #combo.of_divs_with_underscore= @should_eval = "with this text" + = [ 104, 101, 108, 108, 111 ].map do |byte| + - byte.chr + .footer + %strong.shout= "This is a really long ruby quote. It should be loved and wrapped because its more than 50 characters. This value may change in the future and this test may look stupid. \nSo, I'm just making it *really* long. God, I hope this works" diff --git a/test/haml/templates/tag_parsing.haml b/test/haml/templates/tag_parsing.haml new file mode 100644 index 00000000..728a7380 --- /dev/null +++ b/test/haml/templates/tag_parsing.haml @@ -0,0 +1,24 @@ +%div.tags + %foo 1 + %FOO 2 + %fooBAR 3 + %fooBar 4 + %foo_bar 5 + %foo-bar 6 + %foo:bar 7 + %foo.bar 8 + %fooBAr_baz:boom_bar 9 + %foo13 10 + %foo2u 11 +%div.classes + %p.foo.bar#baz#boom + .fooBar a + .foo-bar b + .foo_bar c + .FOOBAR d + .foo16 e + .123 f + .foo2u g +%div.broken + %foo<{ :a => :b } + .foo>{ :c => :d } diff --git a/test/haml/templates/very_basic.haml b/test/haml/templates/very_basic.haml new file mode 100644 index 00000000..93396b96 --- /dev/null +++ b/test/haml/templates/very_basic.haml @@ -0,0 +1,4 @@ +!!! +%html + %head + %body diff --git a/test/haml/templates/whitespace_handling.haml b/test/haml/templates/whitespace_handling.haml new file mode 100644 index 00000000..0d983213 --- /dev/null +++ b/test/haml/templates/whitespace_handling.haml @@ -0,0 +1,137 @@ +#whitespace_test + = render :file => "_text_area.haml", :locals => { :value => "Oneline" } + = render :file => "_text_area.haml", :locals => { :value => "Two\nlines" } + ~ render :file => "_text_area.haml", :locals => { :value => "Oneline" } + ~ render :file => "_text_area.haml", :locals => { :value => "Two\nlines" } + #flattened~ render :file => "_text_area.haml", :locals => { :value => "Two\nlines" } +.hithere + ~ "Foo bar" + ~ "
      foo bar
      " + ~ "
      foo\nbar
      " + %p~ "
      foo\nbar
      " + %p~ "foo\nbar" + %pre~ + ___ + ,o88888 + ,o8888888' + ,:o:o:oooo. ,8O88Pd8888" + ,.::.::o:ooooOoOoO. ,oO8O8Pd888'" + ,.:.::o:ooOoOoOO8O8OOo.8OOPd8O8O" + , ..:.::o:ooOoOOOO8OOOOo.FdO8O8" + , ..:.::o:ooOoOO8O888O8O,COCOO" + , . ..:.::o:ooOoOOOO8OOOOCOCO" + . ..:.::o:ooOoOoOO8O8OCCCC"o + . ..:.::o:ooooOoCoCCC"o:o + . ..:.::o:o:,cooooCo"oo:o: + ` . . ..:.:cocoooo"'o:o:::' + .` . ..::ccccoc"'o:o:o:::' + :.:. ,c:cccc"':.:.:.:.:.' + ..:.:"'`::::c:"'..:.:.:.:.:.' http://www.chris.com/ASCII/ + ...:.'.:.::::"' . . . . .' + .. . ....:."' ` . . . '' + . . . ...."' + .. . ."' -hrr- + . + + + It's a planet! + %strong This shouldn't be bold! + %strong This should! + %textarea + ~ + ___ ___ ___ ___ + /\__\ /\ \ /\__\ /\__\ + /:/ / /::\ \ /::| | /:/ / + /:/__/ /:/\:\ \ /:|:| | /:/ / + /::\ \ ___ /::\~\:\ \ /:/|:|__|__ /:/ / + /:/\:\ /\__\ /:/\:\ \:\__\ /:/ |::::\__\ /:/__/ + \/__\:\/:/ / \/__\:\/:/ / \/__/~~/:/ / \:\ \ + \::/ / \::/ / /:/ / \:\ \ + /:/ / /:/ / /:/ / \:\ \ + /:/ / /:/ / /:/ / \:\__\ + \/__/ \/__/ \/__/ \/__/ + + Many + thanks + to + http://www.network-science.de/ascii/ + %strong indeed! +.foo + ~ 13 + ~ ['a', 'b', 'c'].map do |a| + - "" +%pre + ~ + foo + bar +#whitespace_test + = render :file => "_text_area.haml", :locals => { :value => "Oneline" } + = render :file => "_text_area.haml", :locals => { :value => "Two\nlines" } + = find_and_preserve render(:file => "_text_area.haml", :locals => { :value => "Oneline" }) + = find_and_preserve render(:file => "_text_area.haml", :locals => { :value => "Two\nlines" }) + #flattened= find_and_preserve render(:file => "_text_area.haml", :locals => { :value => "Two\nlines" }) +.hithere + = find_and_preserve("Foo bar") + = find_and_preserve("
      foo bar
      ") + = find_and_preserve("
      foo\nbar
      ") + %p= find_and_preserve("
      foo\nbar
      ") + %p= find_and_preserve("foo\nbar") + %pre + :preserve + ___ + ,o88888 + ,o8888888' + ,:o:o:oooo. ,8O88Pd8888" + ,.::.::o:ooooOoOoO. ,oO8O8Pd888'" + ,.:.::o:ooOoOoOO8O8OOo.8OOPd8O8O" + , ..:.::o:ooOoOOOO8OOOOo.FdO8O8" + , ..:.::o:ooOoOO8O888O8O,COCOO" + , . ..:.::o:ooOoOOOO8OOOOCOCO" + . ..:.::o:ooOoOoOO8O8OCCCC"o + . ..:.::o:ooooOoCoCCC"o:o + . ..:.::o:o:,cooooCo"oo:o: + ` . . ..:.:cocoooo"'o:o:::' + .` . ..::ccccoc"'o:o:o:::' + :.:. ,c:cccc"':.:.:.:.:.' + ..:.:"'`::::c:"'..:.:.:.:.:.' http://www.chris.com/ASCII/ + ...:.'.:.::::"' . . . . .' + .. . ....:."' ` . . . '' + . . . ...."' + .. . ."' -hrr- + . + + + It's a planet! + %strong This shouldn't be bold! + %strong This should! + %textarea + :preserve + ___ ___ ___ ___ + /\__\ /\ \ /\__\ /\__\ + /:/ / /::\ \ /::| | /:/ / + /:/__/ /:/\:\ \ /:|:| | /:/ / + /::\ \ ___ /::\~\:\ \ /:/|:|__|__ /:/ / + /:/\:\ /\__\ /:/\:\ \:\__\ /:/ |::::\__\ /:/__/ + \/__\:\/:/ / \/__\:\/:/ / \/__/~~/:/ / \:\ \ + \::/ / \::/ / /:/ / \:\ \ + /:/ / /:/ / /:/ / \:\ \ + /:/ / /:/ / /:/ / \:\__\ + \/__/ \/__/ \/__/ \/__/ + + Many + thanks + to + http://www.network-science.de/ascii/ + %strong indeed! +.foo + = find_and_preserve(13) +%pre + :preserve + __ ______ __ ______ + .----.| |--.|__ |.----.| |--..--------.| __ | + | __|| ||__ || __|| < | || __ | + |____||__|__||______||____||__|__||__|__|__||______| +%pre + :preserve + foo + bar diff --git a/test/profile.rb b/test/profile.rb new file mode 100644 index 00000000..26275bc7 --- /dev/null +++ b/test/profile.rb @@ -0,0 +1,63 @@ +require File.dirname(__FILE__) + '/../lib/haml' +require 'haml/template' +require 'rubygems' +require 'active_support' +require 'action_view' +require 'profiler' +require 'stringio' + +module Haml + # Used by both Haml::Profiler and Sass::Profiler. + # Encapsulates profiling behavior. + module AbstractProfiler + def self.profile(times, &block) + # Runs the profiler, collects information + Profiler__::start_profile + times.times &block + Profiler__::stop_profile + + # Outputs information to a StringIO, returns result + io = StringIO.new + Profiler__::print_profile(io) + io.pos = 0 + result = io.read + io.close + result + end + end + + # A profiler for Haml, mostly for development use. This simply implements + # the Ruby profiler for profiling haml code. + class Profiler + + # Creates a new profiler that looks for templates in the base + # directory. + def initialize(base = File.join(File.dirname(__FILE__), 'haml', 'templates')) + ActionView::Base.register_template_handler("haml", Haml::Template) + unless base.class == ActionView::Base + @base = ActionView::Base.new(base) + else + @base = base + end + end + + # Profiles haml on the given template with the given number of runs. + # The template name shouldn't have a file extension; this will + # automatically look for a haml template. + # + # Returns the results of the profiling as a string. + def profile(runs = 100, template_name = 'standard') + AbstractProfiler.profile(runs) { @base.render template_name } + end + end +end + +module Sass + class Profiler + def profile(runs = 100, template_name = 'complex') + Haml::AbstractProfiler.profile(runs) do + Sass::Engine.new("#{File.dirname(__FILE__)}/sass/templates/#{template_name}.sass").render + end + end + end +end diff --git a/test/sass/engine_test.rb b/test/sass/engine_test.rb new file mode 100644 index 00000000..d9119b28 --- /dev/null +++ b/test/sass/engine_test.rb @@ -0,0 +1,87 @@ +#!/usr/bin/env ruby + +require 'test/unit' +require File.dirname(__FILE__) + '/../../lib/sass' +require 'sass/engine' + +class SassEngineTest < Test::Unit::TestCase + EXCEPTION_MAP = { + "!a = 1 + " => 'Constant arithmetic error: "1 +"', + "!a = 1 + 2 +" => 'Constant arithmetic error: "1 + 2 +"', + "!a = \"b" => 'Unterminated string: "\\"b"', + "!a = #aaa - a" => 'Undefined operation: "#afafaf minus a"', + "!a = #aaa / a" => 'Undefined operation: "#afafaf div a"', + "!a = #aaa * a" => 'Undefined operation: "#afafaf times a"', + "!a = #aaa % a" => 'Undefined operation: "#afafaf mod a"', + "!a = 1 - a" => 'Undefined operation: "1 minus a"', + "!a = 1 * a" => 'Undefined operation: "1 times a"', + "!a = 1 / a" => 'Undefined operation: "1 div a"', + "!a = 1 % a" => 'Undefined operation: "1 mod a"', + ":" => 'Invalid attribute: ":"', + ": a" => 'Invalid attribute: ": a"', + ":= a" => 'Invalid attribute: ":= a"', + "a\n :b" => 'Invalid attribute: ":b "', + "a\n :b: c" => 'Invalid attribute: ":b: c" (This isn\'t CSS!)', + "a\n :b c;" => 'Invalid attribute: ":b c;" (This isn\'t CSS!)', + ":a" => 'Attributes aren\'t allowed at the root of a document.', + "!" => 'Invalid constant: "!"', + "!a" => 'Invalid constant: "!a"', + "! a" => 'Invalid constant: "! a"', + "!a b" => 'Invalid constant: "!a b"', + "a\n\t:b c" => "Illegal Indentation: Only two space characters are allowed as tabulation.", + "a\n :b c" => "Illegal Indentation: Only two space characters are allowed as tabulation.", + "a\n :b c" => "Illegal Indentation: Only two space characters are allowed as tabulation.", + "a\n :b\n !c = 3" => "Constants may only be declared at the root of a document.", + "!a = 1b + 2c" => "Incompatible units: b and c" + } + + def test_basic_render + renders_correctly "basic", { :style => :compact } + end + + def test_alternate_styles + renders_correctly "expanded", { :style => :expanded } + renders_correctly "compact", { :style => :compact } + renders_correctly "nested", { :style => :nested } + end + + def test_exceptions + EXCEPTION_MAP.each do |key, value| + begin + Sass::Engine.new(key).render + rescue Sass::SyntaxError => err + assert_equal(value, err.message) + assert(err.sass_line, "Line: #{key}") + assert_match(/\(sass\):[0-9]+/, err.backtrace[0], "Line: #{key}") + else + assert(false, "Exception not raised for '#{key}'!") + end + end + end + + def test_exception_line + to_render = "rule\n :attr val\n :broken\n" + begin + Sass::Engine.new(to_render).render + rescue Sass::SyntaxError => err + assert_equal(3, err.sass_line) + else + assert(false, "Exception not raised for '#{to_render}'!") + end + end + + private + + def renders_correctly(name, options={}) + sass_file = load_file(name, "sass") + css_file = load_file(name, "css") + css_result = Sass::Engine.new(sass_file, options).render + assert_equal css_file, css_result + end + + def load_file(name, type = "sass") + @result = '' + File.new(File.dirname(__FILE__) + "/#{type == 'sass' ? 'templates' : 'results'}/#{name}.#{type}").each_line { |l| @result += l } + @result + end +end diff --git a/test/sass/plugin_test.rb b/test/sass/plugin_test.rb new file mode 100644 index 00000000..3e700cbc --- /dev/null +++ b/test/sass/plugin_test.rb @@ -0,0 +1,103 @@ +#!/usr/bin/env ruby + +require 'test/unit' +require File.dirname(__FILE__) + '/../../lib/sass' + +RAILS_ENV = 'testing' + +require 'sass/plugin' + +class SassPluginTest < Test::Unit::TestCase + @@templates = %w{ complex constants } + + def setup + Sass::Plugin.options = { + :template_location => File.dirname(__FILE__) + '/templates', + :css_location => File.dirname(__FILE__) + '/tmp', + :style => :compact + } + Sass::Plugin.options[:always_update] = true + + Sass::Plugin.update_stylesheets + end + + def teardown + File.delete(*Dir[tempfile_loc('*')]) + end + + def test_templates_should_render_correctly + @@templates.each { |name| assert_renders_correctly(name) } + end + + def test_no_update + File.delete(tempfile_loc('basic')) + assert Sass::Plugin.stylesheet_needs_update?('basic') + Sass::Plugin.update_stylesheets + assert !Sass::Plugin.stylesheet_needs_update?('basic') + end + + def test_exception_handling + File.delete(tempfile_loc('bork')) + Sass::Plugin.update_stylesheets + File.open(tempfile_loc('bork')) do |file| + assert_equal("/*\nSass::SyntaxError: Undefined constant: \"!bork\"\non line 2 of #{File.dirname(__FILE__) + '/templates/bork.sass'}\n\n1: bork\n2: :bork= !bork", file.read.split("\n")[0...6].join("\n")) + end + File.delete(tempfile_loc('bork')) + end + + def test_production_exception_handling + Sass.const_set('RAILS_ENV', 'production') + + File.delete(tempfile_loc('bork')) + Sass::Plugin.update_stylesheets + assert_equal("/* Internal stylesheet error */", File.read(tempfile_loc('bork'))) + File.delete(tempfile_loc('bork')) + + Sass::Plugin.const_set('RAILS_ENV', 'testing') + end + + def test_controller_process + File.delete(tempfile_loc('basic')) + assert Sass::Plugin.stylesheet_needs_update?('basic') + + ActionController::Base.new.process + + assert !Sass::Plugin.stylesheet_needs_update?('basic') + end + + private + + def assert_renders_correctly(name) + File.read(result_loc(name)).split("\n").zip(File.read(tempfile_loc(name)).split("\n")).each_with_index do |pair, line| + message = "template: #{name}\nline: #{line + 1}" + assert_equal(pair.first, pair.last, message) + end + end + + def tempfile_loc(name) + File.dirname(__FILE__) + "/tmp/#{name}.css" + end + + def result_loc(name) + File.dirname(__FILE__) + "/results/#{name}.css" + end +end + +module Sass::Plugin + class << self + public :stylesheet_needs_update? + end +end + +class Sass::Engine + alias_method :old_render, :render + + def render + raise "bork bork bork!" if @template[0] == "{bork now!}" + old_render + end +end + +class ActionController::Base + def sass_old_process(*args); end +end diff --git a/test/sass/results/basic.css b/test/sass/results/basic.css new file mode 100644 index 00000000..b0d1182f --- /dev/null +++ b/test/sass/results/basic.css @@ -0,0 +1,9 @@ +body { font: Arial; background: blue; } + +#page { width: 700px; height: 100; } +#page #header { height: 300px; } +#page #header h1 { font-size: 50px; color: blue; } + +#content.user.show #container.top #column.left { width: 100px; } +#content.user.show #container.top #column.right { width: 600px; } +#content.user.show #container.bottom { background: brown; } diff --git a/test/sass/results/compact.css b/test/sass/results/compact.css new file mode 100644 index 00000000..bd6bc919 --- /dev/null +++ b/test/sass/results/compact.css @@ -0,0 +1,5 @@ +#main { width: 15em; color: #0000ff; } +#main p { border-style: dotted; border-width: 2px; } +#main .cool { width: 100px; } + +#left { font-size: 2em; font-weight: bold; float: left; } diff --git a/test/sass/results/complex.css b/test/sass/results/complex.css new file mode 100644 index 00000000..5c9c231c --- /dev/null +++ b/test/sass/results/complex.css @@ -0,0 +1,86 @@ +body { margin: 0; font: 0.85em "Lucida Grande", "Trebuchet MS", Verdana, sans-serif; color: #fff; background: url(/images/global_bg.gif); } + +#page { width: 900px; margin: 0 auto; background: #440008; border-top-width: 5px; border-top-style: solid; border-top-color: #ff8500; } + +#header { height: 75px; padding: 0; } +#header h1 { float: left; width: 274px; height: 75px; margin: 0; background-image: url(/images/global_logo.gif); background-repeat: no-repeat; text-indent: -9999px; } +#header .status { float: right; padding-top: .5em; padding-left: .5em; padding-right: .5em; padding-bottom: 0; } +#header .status p { float: left; margin-top: 0; margin-right: 0.5em; margin-bottom: 0; margin-left: 0; } +#header .status ul { float: left; margin: 0; padding: 0; } +#header .status li { list-style-type: none; display: inline; margin: 0 5px; } +#header .status a:link, #header .status a:visited { color: #ff8500; text-decoration: none; } +#header .status a:hover { text-decoration: underline; } +#header .search { float: right; clear: right; margin: 12px 0 0 0; } +#header .search form { margin: 0; } +#header .search input { margin: 0 3px 0 0; padding: 2px; border: none; } + +#menu { clear: both; text-align: right; height: 20px; border-bottom: 5px solid #006b95; background: #00a4e4; } +#menu .contests ul { margin: 0 5px 0 0; padding: 0; } +#menu .contests ul li { list-style-type: none; margin: 0 5px; padding: 5px 5px 0 5px; display: inline; font-size: 1.1em; color: #fff; background: #00a4e4; } +#menu .contests a:link, #menu .contests a:visited { color: #fff; text-decoration: none; font-weight: bold; } +#menu .contests a:hover { text-decoration: underline; } + +#content { clear: both; } +#content .container { clear: both; } +#content .container .column { float: left; } +#content .container .column .right { float: right; } +#content a:link, #content a:visited { color: #93d700; text-decoration: none; } +#content a:hover { text-decoration: underline; } + +#content p, #content div { width: 40em; } +#content p li, #content p dt, #content p dd, #content div li, #content div dt, #content div dd { color: #ddffdd; background-color: #4792bb; } +#content .container.video .column.left { width: 200px; } +#content .container.video .column.left .box { margin-top: 10px; } +#content .container.video .column.left .box p { margin: 0 1em auto 1em; } +#content .container.video .column.left .box.participants img { float: left; margin: 0 1em auto 1em; border: 1px solid #6e000d; } +#content .container.video .column.left .box.participants h2 { margin: 0 0 10px 0; padding: 0.5em; background: #6e000d url(/images/hdr_participant.gif) 2px 2px no-repeat; text-indent: -9999px; border-top-width: 5px; border-top-style: solid; border-top-color: #a20013; border-right-width: 1px; border-right-style: dotted; } +#content .container.video .column.middle { width: 500px; } +#content .container.video .column.right { width: 200px; } +#content .container.video .column.right .box { margin-top: 0; } +#content .container.video .column.right .box p { margin: 0 1em auto 1em; } +#content .container.video .column p { margin-top: 0; } + +#content.contests .container.information .column.right .box { margin: 1em 0; } +#content.contests .container.information .column.right .box.videos .thumbnail img { width: 200px; height: 150px; margin-bottom: 5px; } +#content.contests .container.information .column.right .box.videos a:link, #content.contests .container.information .column.right .box.videos a:visited { color: #93d700; text-decoration: none; } +#content.contests .container.information .column.right .box.videos a:hover { text-decoration: underline; } +#content.contests .container.information .column.right .box.votes a { display: block; width: 200px; height: 60px; margin: 15px 0; background: url(/images/btn_votenow.gif) no-repeat; text-indent: -9999px; outline: none; border: none; } +#content.contests .container.information .column.right .box.votes h2 { margin: 52px 0 10px 0; padding: 0.5em; background: #6e000d url(/images/hdr_videostats.gif) 2px 2px no-repeat; text-indent: -9999px; border-top: 5px solid #a20013; } + +#content.contests .container.video .box.videos h2 { margin: 0; padding: 0.5em; background: #6e000d url(/images/hdr_newestclips.gif) 2px 2px no-repeat; text-indent: -9999px; border-top: 5px solid #a20013; } +#content.contests .container.video .box.videos table { width: 100; } +#content.contests .container.video .box.videos table td { padding: 1em; width: 25; vertical-align: top; } +#content.contests .container.video .box.videos table td p { margin: 0 0 5px 0; } +#content.contests .container.video .box.videos table td a:link, #content.contests .container.video .box.videos table td a:visited { color: #93d700; text-decoration: none; } +#content.contests .container.video .box.videos table td a:hover { text-decoration: underline; } +#content.contests .container.video .box.videos .thumbnail { float: left; } +#content.contests .container.video .box.videos .thumbnail img { width: 80px; height: 60px; margin: 0 10px 0 0; border: 1px solid #6e000d; } + +#content .container.comments .column { margin-top: 15px; } +#content .container.comments .column.left { width: 600px; } +#content .container.comments .column.left .box ol { margin: 0; padding: 0; } +#content .container.comments .column.left .box li { list-style-type: none; padding: 10px; margin: 0 0 1em 0; background: #6e000d; border-top: 5px solid #a20013; } +#content .container.comments .column.left .box li div { margin-bottom: 1em; } +#content .container.comments .column.left .box li ul { text-align: right; } +#content .container.comments .column.left .box li ul li { display: inline; border: none; padding: 0; }; } +#content .container.comments .column.right { width: 290px; padding-left: 10px; } +#content .container.comments .column.right h2 { margin: 0; padding: 0.5em; background: #6e000d url(/images/hdr_addcomment.gif) 2px 2px no-repeat; text-indent: -9999px; border-top: 5px solid #a20013; } +#content .container.comments .column.right .box textarea { width: 290px; height: 100px; border: none; } + +#footer { margin-top: 10px; padding: 1.2em 1.5em; background: #ff8500; } +#footer ul { margin: 0; padding: 0; list-style-type: none; } +#footer ul li { display: inline; margin: 0 0.5em; color: #440008; } +#footer ul.links { float: left; } +#footer ul.links a:link, #footer ul.links a:visited { color: #440008; text-decoration: none; } +#footer ul.links a:hover { text-decoration: underline; } +#footer ul.copyright { float: right; } + +.clear { clear: both; } + +.centered { text-align: center; } + +img { border: none; } + +button.short { width: 60px; height: 22px; padding: 0 0 2px 0; color: #fff; border: none; background: url(/images/btn_short.gif) no-repeat; } + +table { border-collapse: collapse; } diff --git a/test/sass/results/constants.css b/test/sass/results/constants.css new file mode 100644 index 00000000..28aa8ea3 --- /dev/null +++ b/test/sass/results/constants.css @@ -0,0 +1,12 @@ +#main { content: Hello!; qstr: Quo"ted"!; hstr: Hyph-en!; width: 30em; background-color: #000; color: #ffffaf; con: foo bar 9 hi there boom; con2: noquo quo; } +#main #sidebar { background-color: #00ff98; num-normal: 10; num-dec: 10.2; num-dec0: 99; num-neg: -10; esc: 10+12; many: 6; order: 7; complex: #4c9db1hi16; } + +#plus { num-num: 7; num-num-un: 25em; num-num-un2: 23em; num-num-neg: 9.87; num-str: 100px; num-col: #bcbcbc; str-str: hi there; str-str2: hi there; str-col: 14em solid #1f2f3f; str-num: times: 13; col-num: #ff8aaa; col-col: #5f80ff; } + +#minus { num-num: 900; col-num: #f9f9f4; col-col: #000035; } + +#times { num-num: 7; num-col: #7496b8; col-num: #092345; col-col: #243648; } + +#div { num-num: 3.33333333333333; num-num2: 3; col-num: #092345; col-col: #0b0d0f; } + +#mod { num-num: 2; col-col: #0f0e05; col-num: #020001; } diff --git a/test/sass/results/expanded.css b/test/sass/results/expanded.css new file mode 100644 index 00000000..005586ed --- /dev/null +++ b/test/sass/results/expanded.css @@ -0,0 +1,18 @@ +#main { + width: 15em; + color: #0000ff; +} +#main p { + border-style: dotted; + border-width: 2px; +} +#main .cool { + width: 100px; +} + +#left { + font-size: 2em; + font-weight: bold; + + float: left; +} diff --git a/test/sass/results/nested.css b/test/sass/results/nested.css new file mode 100644 index 00000000..05a73c1b --- /dev/null +++ b/test/sass/results/nested.css @@ -0,0 +1,14 @@ +#main { + width: 15em; + color: #0000ff; } + #main p { + border-style: dotted; + border-width: 2px; } + #main .cool { + width: 100px; } + +#left { + font-size: 2em; + font-weight: bold; + + float: left; } diff --git a/test/sass/templates/basic.sass b/test/sass/templates/basic.sass new file mode 100644 index 00000000..71117bf5 --- /dev/null +++ b/test/sass/templates/basic.sass @@ -0,0 +1,23 @@ + + +body + :font Arial + :background blue + +#page + :width 700px + :height 100 + #header + :height 300px + h1 + :font-size 50px + :color blue + +#content.user.show + #container.top + #column.left + :width 100px + #column.right + :width 600px + #container.bottom + :background brown \ No newline at end of file diff --git a/test/sass/templates/bork.sass b/test/sass/templates/bork.sass new file mode 100644 index 00000000..b0d9abea --- /dev/null +++ b/test/sass/templates/bork.sass @@ -0,0 +1,2 @@ +bork + :bork= !bork diff --git a/test/sass/templates/compact.sass b/test/sass/templates/compact.sass new file mode 100644 index 00000000..675fea4b --- /dev/null +++ b/test/sass/templates/compact.sass @@ -0,0 +1,15 @@ +#main + :width 15em + :color #0000ff + p + :border + :style dotted + :width 2px + .cool + :width 100px + +#left + :font + :size 2em + :weight bold + :float left diff --git a/test/sass/templates/complex.sass b/test/sass/templates/complex.sass new file mode 100644 index 00000000..0b806be9 --- /dev/null +++ b/test/sass/templates/complex.sass @@ -0,0 +1,291 @@ +body + :margin 0 + :font 0.85em "Lucida Grande", "Trebuchet MS", Verdana, sans-serif + :color #fff + :background url(/images/global_bg.gif) + +#page + :width 900px + :margin 0 auto + :background #440008 + :border-top + :width 5px + :style solid + :color #ff8500 + +#header + :height 75px + :padding 0 + h1 + :float left + :width 274px + :height 75px + :margin 0 + :background + :image url(/images/global_logo.gif) + :repeat no-repeat + :text-indent -9999px + .status + :float right + :padding + :top .5em + :left .5em + :right .5em + :bottom 0 + p + :float left + :margin + :top 0 + :right 0.5em + :bottom 0 + :left 0 + ul + :float left + :margin 0 + :padding 0 + li + :list-style-type none + :display inline + :margin 0 5px + a:link, a:visited + :color #ff8500 + :text-decoration none + a:hover + :text-decoration underline + .search + :float right + :clear right + :margin 12px 0 0 0 + form + :margin 0 + input + :margin 0 3px 0 0 + :padding 2px + :border none + +#menu + :clear both + :text-align right + :height 20px + :border-bottom 5px solid #006b95 + :background #00a4e4 + .contests + ul + :margin 0 5px 0 0 + :padding 0 + li + :list-style-type none + :margin 0 5px + :padding 5px 5px 0 5px + :display inline + :font-size 1.1em + :color #fff + :background #00a4e4 + a:link, a:visited + :color #fff + :text-decoration none + :font-weight bold + a:hover + :text-decoration underline + +//General content information +#content + :clear both + .container + :clear both + .column + :float left + .left + .middle + .right + :float right + a:link, a:visited + :color #93d700 + :text-decoration none + a:hover + :text-decoration underline + +#content + p, div + :width 40em + li, dt, dd + :color #ddffdd + :background-color #4792bb + .container.video + .column.left + :width 200px + .box + :margin-top 10px + p + :margin 0 1em auto 1em + .box.participants + img + :float left + :margin 0 1em auto 1em + :border 1px solid #6e000d + h2 + :margin 0 0 10px 0 + :padding 0.5em + :background #6e000d url(/images/hdr_participant.gif) 2px 2px no-repeat + :text-indent -9999px + :border + :top + :width 5px + :style solid + :color #a20013 + :right + :width 1px + :style dotted + .column.middle + :width 500px + .column.right + :width 200px + .box + :margin-top 0 + p + :margin 0 1em auto 1em + .column + p + :margin-top 0 + +#content.contests + .container.information + .column.right + .box + :margin 1em 0 + .box.videos + .thumbnail img + :width 200px + :height 150px + :margin-bottom 5px + a:link, a:visited + :color #93d700 + :text-decoration none + a:hover + :text-decoration underline + .box.votes + a + :display block + :width 200px + :height 60px + :margin 15px 0 + :background url(/images/btn_votenow.gif) no-repeat + :text-indent -9999px + :outline none + :border none + h2 + :margin 52px 0 10px 0 + :padding 0.5em + :background #6e000d url(/images/hdr_videostats.gif) 2px 2px no-repeat + :text-indent -9999px + :border-top 5px solid #a20013 + +#content.contests + .container.video + .box.videos + h2 + :margin 0 + :padding 0.5em + :background #6e000d url(/images/hdr_newestclips.gif) 2px 2px no-repeat + :text-indent -9999px + :border-top 5px solid #a20013 + table + :width 100 + td + :padding 1em + :width 25 + :vertical-align top + p + :margin 0 0 5px 0 + a:link, a:visited + :color #93d700 + :text-decoration none + a:hover + :text-decoration underline + .thumbnail + :float left + img + :width 80px + :height 60px + :margin 0 10px 0 0 + :border 1px solid #6e000d + +#content + .container.comments + .column + :margin-top 15px + .column.left + :width 600px + .box + ol + :margin 0 + :padding 0 + li + :list-style-type none + :padding 10px + :margin 0 0 1em 0 + :background #6e000d + :border-top 5px solid #a20013 + div + :margin-bottom 1em + ul + :text-align right + li + :display inline; border: none; padding: 0; } + .column.right + :width 290px + :padding-left 10px + h2 + :margin 0 + :padding 0.5em + :background #6e000d url(/images/hdr_addcomment.gif) 2px 2px no-repeat + :text-indent -9999px + :border-top 5px solid #a20013 + .box + textarea + :width 290px + :height 100px + :border none + +#footer + :margin-top 10px + :padding 1.2em 1.5em + :background #ff8500 + ul + :margin 0 + :padding 0 + :list-style-type none + li + :display inline + :margin 0 0.5em + :color #440008 + ul.links + :float left + a:link, a:visited + :color #440008 + :text-decoration none + a:hover + :text-decoration underline + ul.copyright + :float right + + +.clear + :clear both + +.centered + :text-align center + +img + :border none + +button.short + :width 60px + :height 22px + :padding 0 0 2px 0 + :color #fff + :border none + :background url(/images/btn_short.gif) no-repeat + +table + :border-collapse collapse diff --git a/test/sass/templates/constants.sass b/test/sass/templates/constants.sass new file mode 100644 index 00000000..b74502bd --- /dev/null +++ b/test/sass/templates/constants.sass @@ -0,0 +1,80 @@ +!width = 10em + 20 +!color = #00ff98 +!main_text = #ffa +!num = 10 +!dec = 10.2 +!dec_0 = 99.0 +!neg = -10 +!esc= 10\+12 +!str= "Hello!" +!qstr= "Quo\"ted\"!" +!hstr= "Hyph-en!" +!concat = (5 + 4) hi there + +#main + :content = !str + :qstr = !qstr + :hstr = !hstr + :width = !width + :background-color #000 + :color= !main_text + :con= foo bar (!concat boom) + :con2= noquo "quo" + #sidebar + :background-color= !color + :num + :normal= !num + :dec= !dec + :dec0= !dec_0 + :neg= !neg + :esc= !esc + :many= 1 + 2 + 3 + :order= 1 + 2 * 3 + :complex= ((1 + 2) + 15)+#3a8b9f + (hi+(1 +1+ 2)* 4) + +#plus + :num + :num= 5+2 + :num-un= 10em + 15em + :num-un2= 10 + 13em + :num-neg= 10 + -.13 + :str= 100 + px + :col= 13 + #aaa + :str + :str= hi + \ there + :str2= hi + " there" + :col= "14em solid " + #123 + :num= times:\ + 13 + :col + :num= #f02 + 123.5 + :col= #12A + #405162 + +#minus + :num + :num= 912 - 12 + :col + :num= #fffffa - 5.2 + :col= #abcdef - #fedcba + +#times + :num + :num= 2 * 3.5 + :col= 2 * #3a4b5c + :col + :num= #12468a * 0.5 + :col= #121212 * #020304 + +#div + :num + :num= 10 / 3.0 + :num2= 10 / 3 + :col + :num= #12468a / 2 + :col= #abcdef / #0f0f0f + +#mod + :num + :num= 17 % 3 + :col + :col= #5f6e7d % #10200a + :num= #aaabac % 3 diff --git a/test/sass/templates/expanded.sass b/test/sass/templates/expanded.sass new file mode 100644 index 00000000..675fea4b --- /dev/null +++ b/test/sass/templates/expanded.sass @@ -0,0 +1,15 @@ +#main + :width 15em + :color #0000ff + p + :border + :style dotted + :width 2px + .cool + :width 100px + +#left + :font + :size 2em + :weight bold + :float left diff --git a/test/sass/templates/nested.sass b/test/sass/templates/nested.sass new file mode 100644 index 00000000..675fea4b --- /dev/null +++ b/test/sass/templates/nested.sass @@ -0,0 +1,15 @@ +#main + :width 15em + :color #0000ff + p + :border + :style dotted + :width 2px + .cool + :width 100px + +#left + :font + :size 2em + :weight bold + :float left From d4fdd5ebdf470acd328cb2a8d3efaa6c1e925e8d Mon Sep 17 00:00:00 2001 From: nex3 Date: Sun, 11 Mar 2007 20:49:12 +0000 Subject: [PATCH 020/125] Proper version number. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@402 7063305b-7217-0410-af8c-cdc13e5119b9 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index dc1e644a..bc80560f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.0 +1.5.0 From 2c56c40543074e5bd73ef6e6c0a2177d84bf8246 Mon Sep 17 00:00:00 2001 From: nex3 Date: Fri, 16 Mar 2007 19:19:28 +0000 Subject: [PATCH 021/125] Minor documentation fix (thanks to Tom Stuart). Also fixed the VERSION file in rel_1-5-0. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@415 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/sass.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sass.rb b/lib/sass.rb index f14815a6..fd1ec430 100644 --- a/lib/sass.rb +++ b/lib/sass.rb @@ -392,7 +392,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # Defaults to false. # Only has meaning within Ruby on Rails. # -# [:always_update] Whether a Sass template should be checked for updates every +# [:always_check] Whether a Sass template should be checked for updates every # time a controller is accessed, # as opposed to only when the Rails server starts. # If a Sass template has been updated, From fb95cc2d21e75ae7c910eb68f96e4db6cbbf339c Mon Sep 17 00:00:00 2001 From: nex3 Date: Mon, 19 Mar 2007 16:56:11 +0000 Subject: [PATCH 022/125] Fixed a minor doc bug. Thanks are due to Paul Abbot. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@444 7063305b-7217-0410-af8c-cdc13e5119b9 --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index c4dfd1f7..46c01527 100644 --- a/README +++ b/README @@ -47,7 +47,7 @@ a tag without a name defaults to a div. So becomes -
      Hello! +
      Hello!
      Haml uses indentation to bring the individual elements to represent the HTML structure. From 5e5c34e65603b42868825ed30982937902733787 Mon Sep 17 00:00:00 2001 From: nex3 Date: Fri, 30 Mar 2007 02:22:39 +0000 Subject: [PATCH 023/125] :suppres_eval works properly in stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@456 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/engine.rb | 17 +++++++++-------- test/haml/engine_test.rb | 9 +++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index d7f45fd9..95694d0c 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -129,13 +129,6 @@ END } } - unless @options[:suppress_eval] - @options[:filters].merge!({ - 'erb' => ERB, - 'ruby' => Haml::Filters::Ruby - }) - end - if !NOT_LOADED.include? 'redcloth' @options[:filters].merge!({ 'redcloth' => RedCloth, @@ -149,6 +142,14 @@ END @options.rec_merge! options + unless @options[:suppress_eval] + @options[:filters].merge!({ + 'erb' => ERB, + 'ruby' => Haml::Filters::Ruby + }) + end + @options[:filters].rec_merge! options[:filters] if options[:filters] + @precompiled = @options[:precompiled] @template = template.strip #String @@ -589,7 +590,7 @@ END warn(FLAT_WARNING) if flattened && !defined?(Test::Unit) value_exists = !value.empty? - attributes_hash = "nil" unless attributes_hash + attributes_hash = "nil" if attributes_hash.nil? || @options[:suppress_eval] object_ref = "nil" unless object_ref if @block_opened diff --git a/test/haml/engine_test.rb b/test/haml/engine_test.rb index 3a750914..b8da45e2 100644 --- a/test/haml/engine_test.rb +++ b/test/haml/engine_test.rb @@ -57,6 +57,15 @@ class EngineTest < Test::Unit::TestCase def test_stop_eval assert_equal("", render("= 'Hello'", :suppress_eval => true)) + assert_equal("
      \n", render("#foo{:yes => 'no'}/", :suppress_eval => true)) + + begin + assert_equal("", render(":ruby\n puts 'hello'", :suppress_eval => true)) + rescue Haml::HamlError => err + caught = true + assert_equal('Filter "ruby" is not defined!', err.message) + end + assert(caught, "Rendering a ruby filter without evaluating didn't throw an error!") end def test_attr_wrapper From 9ffe055f63256825688869f5b8d6f1fcedc06c8c Mon Sep 17 00:00:00 2001 From: nex3 Date: Fri, 30 Mar 2007 02:25:19 +0000 Subject: [PATCH 024/125] Case statements work in stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@457 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/engine.rb | 5 +++-- test/haml/results/just_stuff.xhtml | 2 ++ test/haml/templates/just_stuff.haml | 12 +++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index 95694d0c..ef85868b 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -310,8 +310,9 @@ END when SILENT_SCRIPT sub_line = line[1..-1] unless sub_line[0] == SILENT_COMMENT - push_silent(sub_line, true) - if @block_opened && !mid_block_keyword?(line) + mbk = mid_block_keyword?(line) + push_silent(sub_line, !mbk) + if (@block_opened && !mbk) || line[1..-1].split(' ', 2)[0] == "case" push_and_tabulate([:script]) end end diff --git a/test/haml/results/just_stuff.xhtml b/test/haml/results/just_stuff.xhtml index 67377187..a6d99316 100644 --- a/test/haml/results/just_stuff.xhtml +++ b/test/haml/results/just_stuff.xhtml @@ -41,3 +41,5 @@ stuff followed by whitespace Woah a period. testtest + + diff --git a/test/haml/templates/just_stuff.haml b/test/haml/templates/just_stuff.haml index 2986cf7c..620ac552 100644 --- a/test/haml/templates/just_stuff.haml +++ b/test/haml/templates/just_stuff.haml @@ -38,4 +38,14 @@ /[if gte IE5.2] Woah a period. = "test" | - "test" | \ No newline at end of file + "test" | +- case :foo +- when :bar + %bar/ +- when :foo + %foo/ +- case :foo + - when :bar + %bar/ + - when :foo + %foo/ From 76c92aea84d5a9235b47570e1a16a24fef297b0e Mon Sep 17 00:00:00 2001 From: nex3 Date: Fri, 30 Mar 2007 19:05:17 +0000 Subject: [PATCH 025/125] Doc error fixed in stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@464 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/sass.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/sass.rb b/lib/sass.rb index fd1ec430..e57ce8b9 100644 --- a/lib/sass.rb +++ b/lib/sass.rb @@ -283,15 +283,15 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # they're simply joined with a space. # For example: # -# !font_family = sans-serif +# !font_family = "sans-serif" # !main_font_size = 1em # # #main # :font -# :family = !font-family +# :family = !font_family # :size = !main_font_size # h6 -# :font = italic small-caps bold (!main_font_size + 0.1em) !font-family +# :font = italic "small-caps" bold (!main_font_size + 0.1em) !font_family # # is compiled to: # From 942815a09c68d42d2d71938d566b61031fe04519 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sat, 31 Mar 2007 23:27:05 +0000 Subject: [PATCH 026/125] Merged Nate Vack's ActionViewMods changes to stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@466 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/helpers.rb | 12 ++--- lib/haml/helpers/action_view_mods.rb | 66 +++++++++++----------------- test/haml/helper_test.rb | 4 +- 3 files changed, 31 insertions(+), 51 deletions(-) diff --git a/lib/haml/helpers.rb b/lib/haml/helpers.rb index ba5decc9..d851a0d1 100644 --- a/lib/haml/helpers.rb +++ b/lib/haml/helpers.rb @@ -8,12 +8,12 @@ module Haml module Helpers self.extend self - @@action_view = false + @@action_view_defined = defined?(ActionView) @@force_no_action_view = false # Returns whether or not ActionView is installed on the system. def self.action_view? - @@action_view + @@action_view_defined end # Isolates the whitespace-sensitive tags in the string and uses preserve @@ -186,11 +186,6 @@ module Haml end private - - # Sets whether or not ActionView is installed on the system. - def self.action_view(value) # :nodoc: - @@action_view = value - end # Gets a reference to the current Haml::Buffer object. def buffer @@ -226,6 +221,7 @@ module Haml end result.to_s end + alias_method :capture_erb_with_buffer, :capture_haml_with_buffer # Returns whether or not the current template is a Haml template. # @@ -235,8 +231,6 @@ module Haml def is_haml? @haml_stack ? @haml_stack.size > 0 : false end - - include ActionViewMods if self.const_defined? "ActionViewMods" end end diff --git a/lib/haml/helpers/action_view_mods.rb b/lib/haml/helpers/action_view_mods.rb index c88283f9..1fca91ac 100644 --- a/lib/haml/helpers/action_view_mods.rb +++ b/lib/haml/helpers/action_view_mods.rb @@ -9,42 +9,24 @@ rescue LoadError end if action_view_included - class ActionView::Base - alias_method :old_concat, :concat unless instance_methods.include? "old_concat" - alias_method :old_form_tag, :form_tag unless instance_methods.include? "old_form_tag" - - alias_method :old_form_for, :form_for unless instance_methods.include? "old_form_for" - end - - module Haml + module ActionView + # This overrides various helpers in ActionView + # to make them work more effectively with Haml. module Helpers - # This module overrides various helpers in ActionView - # to make them work more effectively with Haml. - # It also defines several helper methods, - # available from a Haml template, - # which are only useful within the context of ActionView. - # It's not available unless ActionView is installed. - # - #-- - # Methods in this module should be nodoc'd. - #++ - module ActionViewMods - def self.included(othermod) # :nodoc: - othermod.class_eval do - action_view(true) - alias_method :capture_erb_with_buffer, :capture_haml_with_buffer - end - end - - def concat(string, binding = nil) # :nodoc: + # :stopdoc: + module TextHelper + def concat_with_haml(string, binding = nil) if is_haml? buffer.buffer.concat(string) else - old_concat(string, binding) + concat_without_haml(string, binding) end end - - def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &proc) # :nodoc: + alias_method_chain :concat, :haml + end + + module FormTagHelper + def form_tag_with_haml(url_for_options = {}, options = {}, *parameters_for_url, &proc) if block_given? && is_haml? oldproc = proc proc = bind_proc do |*args| @@ -54,29 +36,33 @@ if action_view_included tab_down end end - res = old_form_tag(url_for_options, options, *parameters_for_url, &proc) + "\n" + res = form_tag_without_haml(url_for_options, options, *parameters_for_url, &proc) + "\n" concat "\n" if block_given? && is_haml? res end - - def form_for(object_name, *args, &proc) # :nodoc: + alias_method_chain :form_tag, :haml + end + + module FormHelper + def form_for_with_haml(object_name, *args, &proc) if block_given? && is_haml? oldproc = proc proc = bind_proc do |*args| tab_up oldproc.call(*args) tab_down - end + end end - old_form_for(object_name, *args, &proc) + form_for_without_haml(object_name, *args, &proc) concat "\n" if block_given? && is_haml? end - - def generate_content_class_names - controller.controller_name + " " + controller.action_name - end + alias_method_chain :form_for, :haml end + + def generate_content_class_names + controller.controller_name + " " + controller.action_name + end + # :startdoc: end end end - diff --git a/test/haml/helper_test.rb b/test/haml/helper_test.rb index c3c1fdcf..ba692d78 100644 --- a/test/haml/helper_test.rb +++ b/test/haml/helper_test.rb @@ -62,9 +62,9 @@ class HelperTest < Test::Unit::TestCase begin ActionView::Base.new.render(:inline => "<%= concat('foo') %>") - rescue ArgumentError + rescue ArgumentError, NameError proper_behavior = true - end + end assert(proper_behavior) end From fa00635843a84a06e040f2b4cc53c3ee6292fad8 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sat, 31 Mar 2007 23:28:04 +0000 Subject: [PATCH 027/125] Set stable at v1.5.1 git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@467 7063305b-7217-0410-af8c-cdc13e5119b9 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index bc80560f..26ca5946 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.0 +1.5.1 From 58aa4a6de230e9f6d08b9c0be4ae4ff6d8b62752 Mon Sep 17 00:00:00 2001 From: nex3 Date: Mon, 9 Apr 2007 03:49:01 +0000 Subject: [PATCH 028/125] Do the same as last revision for stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@487 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/helpers/action_view_mods.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/haml/helpers/action_view_mods.rb b/lib/haml/helpers/action_view_mods.rb index 1fca91ac..95fc9e56 100644 --- a/lib/haml/helpers/action_view_mods.rb +++ b/lib/haml/helpers/action_view_mods.rb @@ -22,7 +22,8 @@ if action_view_included concat_without_haml(string, binding) end end - alias_method_chain :concat, :haml + alias_method :concat_without_haml, :concat + alias_method :concat, :concat_with_haml end module FormTagHelper @@ -40,7 +41,8 @@ if action_view_included concat "\n" if block_given? && is_haml? res end - alias_method_chain :form_tag, :haml + alias_method :form_tag_without_haml, :form_tag + alias_method :form_tag, :form_tag_with_haml end module FormHelper @@ -56,7 +58,8 @@ if action_view_included form_for_without_haml(object_name, *args, &proc) concat "\n" if block_given? && is_haml? end - alias_method_chain :form_for, :haml + alias_method :form_for_without_haml, :form_for + alias_method :form_for, :form_for_with_haml end def generate_content_class_names From bc5218e0d19473f5c69923e7134e181935fe74a5 Mon Sep 17 00:00:00 2001 From: nex3 Date: Mon, 9 Apr 2007 03:55:56 +0000 Subject: [PATCH 029/125] Make is_haml? work properly with partials for stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@488 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/engine.rb | 2 ++ lib/haml/helpers.rb | 2 +- lib/haml/helpers/action_view_mods.rb | 12 ++++++++++++ test/haml/helper_test.rb | 4 ++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index ef85868b..bb043ac2 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -202,6 +202,7 @@ END def do_precompile push_silent <<-END def _haml_render + @haml_is_haml = true _hamlout = @haml_stack[-1] _erbout = _hamlout.buffer END @@ -265,6 +266,7 @@ END # Close all the open tags @template_tabs.times { close } + push_silent "@haml_is_haml = false" push_silent "end" end diff --git a/lib/haml/helpers.rb b/lib/haml/helpers.rb index d851a0d1..77d50e7d 100644 --- a/lib/haml/helpers.rb +++ b/lib/haml/helpers.rb @@ -229,7 +229,7 @@ module Haml # also works in other ActionView templates, # where it will always return false. def is_haml? - @haml_stack ? @haml_stack.size > 0 : false + @haml_is_haml end end end diff --git a/lib/haml/helpers/action_view_mods.rb b/lib/haml/helpers/action_view_mods.rb index 95fc9e56..9321586f 100644 --- a/lib/haml/helpers/action_view_mods.rb +++ b/lib/haml/helpers/action_view_mods.rb @@ -10,6 +10,18 @@ end if action_view_included module ActionView + class Base # :nodoc: + def render_with_haml(*args) + was_haml = is_haml? + @haml_is_haml = false + res = render_without_haml(*args) + @haml_is_haml = was_haml + res + end + alias_method :render_without_haml, :render + alias_method :render, :render_with_haml + end + # This overrides various helpers in ActionView # to make them work more effectively with Haml. module Helpers diff --git a/test/haml/helper_test.rb b/test/haml/helper_test.rb index ba692d78..ebb61067 100644 --- a/test/haml/helper_test.rb +++ b/test/haml/helper_test.rb @@ -101,6 +101,10 @@ class HelperTest < Test::Unit::TestCase def test_is_haml assert(!ActionView::Base.new.is_haml?) + assert_equal("true\n", render("= is_haml?")) + assert_equal("true\n", render("= is_haml?", :action_view)) + assert_equal("false", @base.render(:inline => '<%= is_haml? %>')) + assert_equal("false\n", render("= render :inline => '<%= is_haml? %>'", :action_view)) end end From 7b467b0fe5ed769ee053ceb25ea84ed063e11f7f Mon Sep 17 00:00:00 2001 From: nex3 Date: Tue, 10 Apr 2007 05:03:15 +0000 Subject: [PATCH 030/125] :suppress_eval works properly in stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@492 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/engine.rb | 22 ++++++++++++---------- test/haml/engine_test.rb | 2 ++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index bb043ac2..a6e34872 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -313,7 +313,7 @@ END sub_line = line[1..-1] unless sub_line[0] == SILENT_COMMENT mbk = mid_block_keyword?(line) - push_silent(sub_line, !mbk) + push_silent(sub_line, !mbk, true) if (@block_opened && !mbk) || line[1..-1].split(' ', 2)[0] == "case" push_and_tabulate([:script]) end @@ -425,12 +425,14 @@ END # Evaluates text in the context of @scope_object, but # does not output the result. - def push_silent(text, add_index = false) - if add_index - @precompiled << "@haml_lineno = #{@index}\n#{text}\n" - else - # Not really DRY, but probably faster - @precompiled << "#{text}\n" + def push_silent(text, add_index = false, can_suppress = false) + unless can_suppress && @options[:suppress_eval] + if add_index + @precompiled << "@haml_lineno = #{@index}\n#{text}\n" + else + # Not really DRY, but probably faster + @precompiled << "#{text}\n" + end end end @@ -519,7 +521,7 @@ END # Closes a Ruby block. def close_block - push_silent "end" + push_silent "end", false, true @template_tabs -= 1 end @@ -543,7 +545,7 @@ END # Closes a loud Ruby block. def close_loud(command) - push_silent 'end' + push_silent 'end', false, true @precompiled << command @template_tabs -= 1 end @@ -594,7 +596,7 @@ END value_exists = !value.empty? attributes_hash = "nil" if attributes_hash.nil? || @options[:suppress_eval] - object_ref = "nil" unless object_ref + object_ref = "nil" if object_ref.nil? || @options[:suppress_eval] if @block_opened if atomic diff --git a/test/haml/engine_test.rb b/test/haml/engine_test.rb index b8da45e2..d37b9ab0 100644 --- a/test/haml/engine_test.rb +++ b/test/haml/engine_test.rb @@ -57,7 +57,9 @@ class EngineTest < Test::Unit::TestCase def test_stop_eval assert_equal("", render("= 'Hello'", :suppress_eval => true)) + assert_equal("", render("- _hamlout << 'foo'", :suppress_eval => true)) assert_equal("
      \n", render("#foo{:yes => 'no'}/", :suppress_eval => true)) + assert_equal("
      \n", render("%div[1]/", :suppress_eval => true)) begin assert_equal("", render(":ruby\n puts 'hello'", :suppress_eval => true)) From 71d53fb7acf85a19170763106463e5b05933a337 Mon Sep 17 00:00:00 2001 From: nex3 Date: Tue, 10 Apr 2007 05:06:10 +0000 Subject: [PATCH 031/125] Properly versioning 1.5.2. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@494 7063305b-7217-0410-af8c-cdc13e5119b9 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 26ca5946..4cda8f19 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.1 +1.5.2 From f095ecf74003d5a6ab133a6c2cd10a09df72bb40 Mon Sep 17 00:00:00 2001 From: nex3 Date: Mon, 16 Apr 2007 01:22:23 +0000 Subject: [PATCH 032/125] == docs in stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@496 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/haml.rb b/lib/haml.rb index 6250e09d..c5d1c784 100644 --- a/lib/haml.rb +++ b/lib/haml.rb @@ -524,6 +524,20 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # hi there reader! # yo #

      +# +# You can also use two equal signs, ==, +# along with conventional Ruby string-embedding syntax +# to easily embed Ruby code in otherwise static text. +# For example: +# +# %p +# == 1 + 1 = #{1 + 1} +# +# is compiled to: +# +#

      +# 1 + 1 = 2 +#

      # # ==== - # From 9a09c3dba17576124fec39efc5902663f365d873 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sat, 7 Jul 2007 18:51:34 +0000 Subject: [PATCH 033/125] Tagging version 1.7.0. git-svn-id: svn://hamptoncatlin.com/haml/tags/rel_1-7-0@543 7063305b-7217-0410-af8c-cdc13e5119b9 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index dc1e644a..bd8bf882 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.0 +1.7.0 From 70bd178d1153af0fc37c042e0d1a000a6a5973c7 Mon Sep 17 00:00:00 2001 From: nex3 Date: Tue, 10 Jul 2007 01:08:44 +0000 Subject: [PATCH 034/125] Minor documentation fix. Thanks, Benoit Caccinolo! git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@549 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/sass.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sass.rb b/lib/sass.rb index 6ce8b3c2..e649d620 100644 --- a/lib/sass.rb +++ b/lib/sass.rb @@ -313,7 +313,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # #main # :background-color = !main_color # p -# :background-color = !bg_color + 32 +# :background-color = !main_color + 32 # # is compiled to: # From 124fdd3742098d2d4ef5f0d185e98b34625b4adb Mon Sep 17 00:00:00 2001 From: nex3 Date: Wed, 18 Jul 2007 07:37:44 +0000 Subject: [PATCH 035/125] Same as above two in stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@569 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/engine.rb | 2 +- test/haml/results/just_stuff.xhtml | 1 - test/haml/templates/just_stuff.haml | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index 0d110ecc..7ec7865f 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -123,7 +123,7 @@ END :suppress_eval => false, :attr_wrapper => "'", :locals => {}, - :autoclose => ['meta', 'img', 'link', 'script', 'br', 'hr'], + :autoclose => ['meta', 'img', 'link', 'br', 'hr'], :filters => { 'sass' => Sass::Engine, 'plain' => Haml::Filters::Plain, diff --git a/test/haml/results/just_stuff.xhtml b/test/haml/results/just_stuff.xhtml index 75fe8e85..71b91959 100644 --- a/test/haml/results/just_stuff.xhtml +++ b/test/haml/results/just_stuff.xhtml @@ -44,7 +44,6 @@ testtest
      - diff --git a/test/haml/templates/just_stuff.haml b/test/haml/templates/just_stuff.haml index 2471807b..45d955be 100644 --- a/test/haml/templates/just_stuff.haml +++ b/test/haml/templates/just_stuff.haml @@ -56,7 +56,6 @@ - when :foo %meta{ :foo => 'bar'} %img -%script %hr %link %script Inline content From 8c242af91b39d6e75ff6c8b86343b59e0ca23564 Mon Sep 17 00:00:00 2001 From: nex3 Date: Thu, 19 Jul 2007 05:22:44 +0000 Subject: [PATCH 036/125] Removing use of String#first, which is only defined in ActiveSupport. Thanks to manveru. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@570 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/engine.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index 7ec7865f..e19bc6d2 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -709,7 +709,7 @@ END when '=', '~' parse = true - if value.first == '=' + if value[0] == ?= value = value[1..-1].strip.dump.gsub('\\#', '#') end end From fb47de76ae197d9cabd58436808847e13e1aa0ed Mon Sep 17 00:00:00 2001 From: nex3 Date: Tue, 24 Jul 2007 01:18:02 +0000 Subject: [PATCH 037/125] Fixing doc error. Thanks, Evgeny and Mislav!x git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@571 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/sass.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sass.rb b/lib/sass.rb index e649d620..ff8f7d5b 100644 --- a/lib/sass.rb +++ b/lib/sass.rb @@ -481,7 +481,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # # Sass allows you to choose between three different output styles # by setting the :style option. -# In Rails, this is done by setting Sass::Template.options[:style]; +# In Rails, this is done by setting Sass::Plugin.options[:style]; # outside Rails, it's done by passing an options hash with :style set. # # === :nested From cc81a7abaf8c127f9d14d200cd85a68a42d1b046 Mon Sep 17 00:00:00 2001 From: nex3 Date: Thu, 9 Aug 2007 03:05:11 +0000 Subject: [PATCH 038/125] Filters are accessed with to_s. Thanks to Nick S for pointing this out. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@578 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/engine.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index e19bc6d2..bb6a9f37 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -334,7 +334,7 @@ END end when FILTER name = line[1..-1].downcase - start_filtered(options[:filters][name] || name) + start_filtered(options[:filters][name.to_s] || name) when DOCTYPE if line[0...3] == '!!!' render_doctype(line) From 88ada9ed5afa1d72932819a8961bf404feec1ca4 Mon Sep 17 00:00:00 2001 From: nex3 Date: Thu, 16 Aug 2007 18:01:51 +0000 Subject: [PATCH 039/125] Fixing previous in stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@587 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/sass/constant/literal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sass/constant/literal.rb b/lib/sass/constant/literal.rb index b6e2b50d..34fbced9 100644 --- a/lib/sass/constant/literal.rb +++ b/lib/sass/constant/literal.rb @@ -9,7 +9,7 @@ class Sass::Constant::Literal # :nodoc: # The regular expression matching numbers. NUMBER = /^(-?\d*?\.?)(\d+)([^\d\s]*)$/ - html_color_matcher = Sass::Constant::Color::HTML4_COLORS.keys.join '|' + html_color_matcher = Sass::Constant::Color::HTML4_COLORS.keys.map { |c| "^#{c}$" }.join '|' # The regular expression matching colors. COLOR = /^\# (?: [\da-f]{3} | [\da-f]{6} ) | #{html_color_matcher}/ix From d8e41ce7febc54b5fed1a66a6189087c1cb5624b Mon Sep 17 00:00:00 2001 From: nex3 Date: Thu, 16 Aug 2007 18:31:23 +0000 Subject: [PATCH 040/125] Same fix as previous in stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@589 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/sass/engine.rb | 6 +++++- test/sass/engine_test.rb | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/sass/engine.rb b/lib/sass/engine.rb index 43d545e2..04e9536e 100644 --- a/lib/sass/engine.rb +++ b/lib/sass/engine.rb @@ -210,7 +210,11 @@ module Sass end def has_children?(index, tabs) - next_line = @lines[index] + next_line = ['//', 0] + while !next_line.nil? && next_line[0] == '//' && next_line[1] = 0 + next_line = @lines[index] + index += 1 + end next_line && next_line[1] > tabs end diff --git a/test/sass/engine_test.rb b/test/sass/engine_test.rb index f033e188..74d3ceb7 100644 --- a/test/sass/engine_test.rb +++ b/test/sass/engine_test.rb @@ -94,7 +94,11 @@ class SassEngineTest < Test::Unit::TestCase end end end - + + def test_empty_first_line + assert_equal("#a {\n b: c; }\n", Sass::Engine.new("#a\n\n b: c").render) + end + private def renders_correctly(name, options={}) From 03bd467a5151060a3e329aa28933a40e00c79bf5 Mon Sep 17 00:00:00 2001 From: nex3 Date: Thu, 16 Aug 2007 21:51:46 +0000 Subject: [PATCH 041/125] Same as previous in stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@591 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/buffer.rb | 18 +++++++++--------- test/haml/engine_test.rb | 8 ++++++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/haml/buffer.rb b/lib/haml/buffer.rb index d23f0d20..3af86fc9 100644 --- a/lib/haml/buffer.rb +++ b/lib/haml/buffer.rb @@ -60,18 +60,18 @@ module Haml if flattened result = Haml::Helpers.find_and_preserve(result) end - unless result.nil? - result = result.to_s - while result[-1] == 10 # \n - # String#chomp is slow - result = result[0...-1] - end - - result = result.gsub("\n", "\n#{tabs(tabulation)}") - push_text result, tabulation + + result = result.to_s + while result[-1] == 10 # \n + # String#chomp is slow + result = result[0...-1] end + + result = result.gsub("\n", "\n#{tabs(tabulation)}") + push_text result, tabulation nil end + def open_prerendered_tag(tag, tabulation) @buffer << "#{tabs(tabulation)}#{tag}" diff --git a/test/haml/engine_test.rb b/test/haml/engine_test.rb index 03dcad01..1fc776bc 100644 --- a/test/haml/engine_test.rb +++ b/test/haml/engine_test.rb @@ -58,6 +58,14 @@ class EngineTest < Test::Unit::TestCase assert_equal("

      \n Hello World\n

      \n", render("%p\n == Hello \#{who}", :locals => {:who => 'World'})) end + def test_nil_tag_value_should_render_as_empty + assert_equal("

      \n", render("%p= nil")) + end + + def test_tag_with_failed_if_should_render_as_empty + assert_equal("

      \n", render("%p= 'Hello' if false")) + end + # Options tests def test_stop_eval From 6888c0f9edbd9df4fa01eb8e27f8f88ee34903a2 Mon Sep 17 00:00:00 2001 From: nex3 Date: Wed, 22 Aug 2007 01:42:19 +0000 Subject: [PATCH 042/125] Literal attribute parsing error fixed. Thanks to smok for pointing this out. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@592 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/engine.rb | 2 +- test/haml/engine_test.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index bb6a9f37..fd84e3ef 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -100,7 +100,7 @@ module Haml TAG_REGEX = /[%]([-:\w]+)([-\w\.\#]*)(\{.*\})?(\[.*\])?([=\/\~]?)?(.*)?/ # The Regex that matches a literal string or symbol value - LITERAL_VALUE_REGEX = /^\s*(:(\w*)|(('|")([^\\\#]*?)\4))\s*$/ + LITERAL_VALUE_REGEX = /^\s*(:(\w*)|(('|")([^\\\#'"]*?)\4))\s*$/ FLAT_WARNING = < { :text => "first time" })) assert_equal("

      first time

      \n", render(template, :locals => { :text => "first time", :foo => 'bar' })) end + + def test_dynamc_attrs_shouldnt_register_as_literal_values + assert_equal("

      \n

      \n", render('%p{:a => "b#{1 + 1}c"}')) + assert_equal("

      \n

      \n", render("%p{:a => 'b' + (1 + 1).to_s + 'c'}")) + end def test_comps assert_equal(-1, "foo" <=> nil) From 302aa2f9dc081ac425724e18df6952b25e9b6f94 Mon Sep 17 00:00:00 2001 From: nex3 Date: Wed, 29 Aug 2007 15:12:49 +0000 Subject: [PATCH 043/125] Docfixes in trunk and stable. Thanks to Simon Peter Nicholls for suggesting them. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@597 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml.rb | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/haml.rb b/lib/haml.rb index f89e6c29..737e91a9 100644 --- a/lib/haml.rb +++ b/lib/haml.rb @@ -573,7 +573,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) #

      # hello there you! #

      -# +# # ===== Blocks # # Ruby blocks, like XHTML tags, don't need to be explicitly closed in Haml. @@ -623,6 +623,24 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # 2? #

      # +# ==== -# +# +# The hyphen followed immediately by the pound sign +# signifies a silent comment. +# Any text following this isn't rendered in the resulting document +# at all. +# +# For example: +# +# %p foo +# -# This is a comment +# %p bar +# +# is compiled to: +# +#

      foo

      +#

      bar

      +# # == Other Useful Things # # === Helpers From eb2ae18896ecab9759a638ee6dcb9a90e78388e7 Mon Sep 17 00:00:00 2001 From: nex3 Date: Fri, 31 Aug 2007 15:05:14 +0000 Subject: [PATCH 044/125] Fixing VERSION file for Stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@598 7063305b-7217-0410-af8c-cdc13e5119b9 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index bd8bf882..943f9cbc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.7.0 +1.7.1 From ed47b197e795a50582c765052079fdfb70e55c83 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sun, 2 Sep 2007 17:11:19 +0000 Subject: [PATCH 045/125] Fixing a screwy documentation error. Thanks to Kiril Angov for pointing this out. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@600 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/haml.rb b/lib/haml.rb index 737e91a9..a020ba1b 100644 --- a/lib/haml.rb +++ b/lib/haml.rb @@ -261,8 +261,9 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # and is compiled to: # #
      -#
      Broken record album
      -#
      What a cool item!
      +#
      +#
      What a cool item!
      +#
      #
      # # ==== = From 83400be434a5ba9f6983355024d9d24c7b5314b9 Mon Sep 17 00:00:00 2001 From: nex3 Date: Fri, 7 Sep 2007 09:13:03 +0000 Subject: [PATCH 046/125] ActionView::Base#render_with_haml passes along blocks as well. Thanks to Jonathan del Strother for the patch. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@603 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/helpers/action_view_mods.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/haml/helpers/action_view_mods.rb b/lib/haml/helpers/action_view_mods.rb index a8eaf208..a3deea2c 100644 --- a/lib/haml/helpers/action_view_mods.rb +++ b/lib/haml/helpers/action_view_mods.rb @@ -11,10 +11,10 @@ end if action_view_included module ActionView class Base # :nodoc: - def render_with_haml(*args) + def render_with_haml(*args, &block) was_haml = is_haml? @haml_is_haml = false - res = render_without_haml(*args) + res = render_without_haml(*args, &block) @haml_is_haml = was_haml res end From ef8eac1dbf5dbed00bf1e55ceb146249606e99cf Mon Sep 17 00:00:00 2001 From: nex3 Date: Sun, 21 Oct 2007 19:24:34 +0000 Subject: [PATCH 047/125] "%p..class" should raise an error. Thanks, Dylan Bruzenak! git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@607 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/engine.rb | 2 +- test/haml/engine_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index fd84e3ef..5a14aead 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -721,7 +721,7 @@ END attributes_hash = "{nil}" if attributes_hash.nil? || literal_attributes || @options[:suppress_eval] object_ref = "nil" if object_ref.nil? || @options[:suppress_eval] - if !attributes.empty? && '.#'.include?(attributes) + if attributes =~ /[\.#](\.|#|\z)/ raise SyntaxError.new("Illegal element: classes and ids must have values. Use %div instead.") end diff --git a/test/haml/engine_test.rb b/test/haml/engine_test.rb index b57f5b5f..c45597c0 100644 --- a/test/haml/engine_test.rb +++ b/test/haml/engine_test.rb @@ -154,7 +154,7 @@ class EngineTest < Test::Unit::TestCase "a\n%p~\nb", "a\n~\nb", "a\n~\n b", "%p~\n b", "%p/\n a", "%p\n \t%a b", "%a\n b\nc", "%a\n b\nc", ":notafilter\n This isn't\n a filter!", - ".{} a", "\#{} a", ".= 'foo'", "%a/ b" ] + ".{} a", "\#{} a", ".= 'foo'", "%a/ b", "%p..class", "%p..#." ] errs.each do |err| begin render(err) From f3ff721a7671c7dc2094f9d09effadc0fb072c30 Mon Sep 17 00:00:00 2001 From: nex3 Date: Thu, 1 Nov 2007 01:44:02 +0000 Subject: [PATCH 048/125] Getting rid of unecessary modification to core String and NilClass classes. Thanks to Ingo Weiss for pointing this out. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@608 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/buffer.rb | 25 +------------------------ test/haml/engine_test.rb | 5 ----- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/lib/haml/buffer.rb b/lib/haml/buffer.rb index 3af86fc9..ca7674f1 100644 --- a/lib/haml/buffer.rb +++ b/lib/haml/buffer.rb @@ -167,7 +167,7 @@ module Haml " #{a}=#{attr_wrapper}#{v}#{attr_wrapper}" end end - result.sort.join + result.compact.sort.join end # Returns whether or not the given value is short enough to be rendered @@ -211,26 +211,3 @@ module Haml end end end - -unless String.methods.include? 'old_comp' - class String # :nodoc - alias_method :old_comp, :<=> - - def <=>(other) - if other.is_a? NilClass - -1 - else - old_comp(other) - end - end - end - - class NilClass # :nodoc: - include Comparable - - def <=>(other) - other.nil? ? 0 : 1 - end - end -end - diff --git a/test/haml/engine_test.rb b/test/haml/engine_test.rb index c45597c0..9079d021 100644 --- a/test/haml/engine_test.rb +++ b/test/haml/engine_test.rb @@ -119,11 +119,6 @@ class EngineTest < Test::Unit::TestCase assert_equal("

      \n

      \n", render('%p{:a => "b#{1 + 1}c"}')) assert_equal("

      \n

      \n", render("%p{:a => 'b' + (1 + 1).to_s + 'c'}")) end - - def test_comps - assert_equal(-1, "foo" <=> nil) - assert_equal(1, nil <=> "foo") - end def test_rec_merge hash1 = {1=>2, 3=>{5=>7, 8=>9}} From 8e7405a3bc3ec89bbfa1270cde0361a25c5a7a52 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sat, 17 Nov 2007 09:19:17 +0000 Subject: [PATCH 049/125] Added an install task to the Rakefiles in trunk and stable. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@615 7063305b-7217-0410-af8c-cdc13e5119b9 --- Rakefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Rakefile b/Rakefile index b2abf66b..736ff1ba 100644 --- a/Rakefile +++ b/Rakefile @@ -67,6 +67,7 @@ unless ARGV[0] == 'benchmark' It was originally envisioned as a plugin for Ruby on Rails, but it can function as a stand-alone templating engine. END + #' readmes = FileList.new('*') do |list| list.exclude(/[a-z]/) @@ -93,6 +94,10 @@ unless ARGV[0] == 'benchmark' pkg.need_tar_bz2 = true end + task :install => [:package] do + sh %{sudo gem install pkg/haml-#{File.read('VERSION').strip}} + end + # ----- Documentation ----- require 'rake/rdoctask' From 6953636719c1eaa5de9ba9aadc763da52897b554 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sun, 18 Nov 2007 00:36:39 +0000 Subject: [PATCH 050/125] Static CSS @imports generate correct output. Thanks to Tim Pease. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@618 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/sass/engine.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sass/engine.rb b/lib/sass/engine.rb index 04e9536e..813a429f 100644 --- a/lib/sass/engine.rb +++ b/lib/sass/engine.rb @@ -292,7 +292,7 @@ module Sass engine = nil filename = find_file_to_import(filename) if filename =~ /\.css$/ - nodes << Tree::ValueNode.new("@import #{filename}", @options[:style]) + nodes << Tree::ValueNode.new("@import url(#{filename});", @options[:style]) else File.open(filename) do |file| new_options = @options.dup From 34d0e626c20fa71392ed370d91407013d2a81a06 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sun, 18 Nov 2007 09:30:44 +0000 Subject: [PATCH 051/125] Fixing tests broken by @import fix. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@620 7063305b-7217-0410-af8c-cdc13e5119b9 --- test/sass/results/import.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/sass/results/import.css b/test/sass/results/import.css index bd285caf..1f5ebbc2 100644 --- a/test/sass/results/import.css +++ b/test/sass/results/import.css @@ -22,6 +22,6 @@ body { font: Arial; background: blue; } #content.user.show #container.top #column.right { width: 600px; } #content.user.show #container.bottom { background: brown; } -@import basic.css -@import ../results/complex.css +@import url(basic.css); +@import url(../results/complex.css); nonimported { myconst: hello; otherconst: goodbye; } From c6f4a131f39d98114ea3e560090e7133910317ee Mon Sep 17 00:00:00 2001 From: nex3 Date: Sun, 18 Nov 2007 09:33:20 +0000 Subject: [PATCH 052/125] Added Merb Sass support for stable gem. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@621 7063305b-7217-0410-af8c-cdc13e5119b9 --- README | 27 ++++++++++-- Rakefile | 3 +- lib/haml.rb | 19 ++++++--- lib/haml/helpers/action_view_mods.rb | 1 + lib/sass.rb | 61 ++++++++++++++++++---------- lib/sass/plugin.rb | 36 +++++----------- lib/sass/plugin/merb.rb | 20 +++++++++ lib/sass/plugin/rails.rb | 18 ++++++++ test/sass/plugin_test.rb | 57 +++++++++++++++++++------- 9 files changed, 170 insertions(+), 72 deletions(-) create mode 100644 lib/sass/plugin/merb.rb create mode 100644 lib/sass/plugin/rails.rb diff --git a/README b/README index 46c01527..20225756 100644 --- a/README +++ b/README @@ -11,11 +11,18 @@ and providing elegant, easily understandable, and powerful syntax. == Using -There are two ways to use Haml and Sass. -The easiest is as a Rails plugin: -Simply type ./script/plugin install http://hamptoncatlin.com/haml/stable +There are several ways to use Haml and Sass. +They can be used as a plugins for Rails or Merb, +or embedded on their own in other applications. +The first step of all of these is to install the Haml gem: + + gem install haml + +To install Haml and Sass as a Rails plugin, +just run haml --rails path/to/rails/app and both Haml and Sass will be installed. -Views with the .haml extension will automatically use Haml. +Views with the .haml (or .html.haml for edge) +extension will automatically use Haml. Sass is a little more complicated; .sass files should be placed in public/stylesheets/sass, where they'll be automatically compiled @@ -23,6 +30,18 @@ to corresponding CSS files in public/stylesheets when needed (the Sass template directory is customizable... see the Sass module docs for details). +For Merb, .html.haml views will work without any further modification. +To enable Sass, you also need to add it add a dependency. +To do so, just add + + dependency "haml" + +to config/dependencies.rb. +Then it'll work just like it does in Rails. + +To use Haml and Sass programatically, +check out the RDocs for the Haml and Sass modules. + == Formatting === Haml diff --git a/Rakefile b/Rakefile index 736ff1ba..43aa7184 100644 --- a/Rakefile +++ b/Rakefile @@ -75,6 +75,7 @@ unless ARGV[0] == 'benchmark' end.to_a spec.executables = ['haml', 'html2haml', 'sass'] spec.files = FileList['lib/**/*', 'bin/*', 'test/**/*', 'Rakefile', 'init.rb'].to_a + readmes + spec.autorequire = ['haml', 'sass'] spec.homepage = 'http://haml.hamptoncatlin.com/' spec.has_rdoc = true spec.extra_rdoc_files = readmes @@ -95,7 +96,7 @@ unless ARGV[0] == 'benchmark' end task :install => [:package] do - sh %{sudo gem install pkg/haml-#{File.read('VERSION').strip}} + sh %{gem install pkg/haml-#{File.read('VERSION').strip}} end # ----- Documentation ----- diff --git a/lib/haml.rb b/lib/haml.rb index a020ba1b..239d7793 100644 --- a/lib/haml.rb +++ b/lib/haml.rb @@ -27,20 +27,29 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # as a plugin for Ruby on Rails, # and as a standalong Ruby module. # -# === Rails +# Sass can be used in several ways: +# As a template engine for Ruby on Rails or Merb, +# or as a standalone engine. +# The first step for all of these is to install the Haml gem: +# +# gem install haml +# +# To enable it as a Rails plugin, +# then run # -# Haml is most commonly used as a plugin. -# It can be installed as a plugin using the Rails plugin installer: +# haml --rails path/to/rails/app # -# ./script/plugin install http://svn.hamptoncatlin.com/haml/tags/stable +# Haml is enabled in Merb by default, +# so Merb users don't have to do anything more. # # Once it's installed, all view files with the ".haml" extension +# (or ".html.haml" for Merb or edge Rails) # will be compiled using Haml. # # You can access instance variables in Haml templates # the same way you do in ERb templates. # Helper methods are also available in Haml templates. -# For example: +# For example (this example uses Rails, but the principle for Merb is the same): # # # file: app/controllers/movies_controller.rb # diff --git a/lib/haml/helpers/action_view_mods.rb b/lib/haml/helpers/action_view_mods.rb index a3deea2c..4faa46e5 100644 --- a/lib/haml/helpers/action_view_mods.rb +++ b/lib/haml/helpers/action_view_mods.rb @@ -1,4 +1,5 @@ begin + raise LoadError if defined?(Merb::Plugins) require 'rubygems' require 'active_support' require 'action_controller' diff --git a/lib/sass.rb b/lib/sass.rb index ff8f7d5b..590f70ca 100644 --- a/lib/sass.rb +++ b/lib/sass.rb @@ -20,19 +20,29 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # # == Using Sass # -# Sass can be used in two ways: -# As a plugin for Ruby on Rails -# and as a standalone parser. +# Sass can be used in several ways: +# As a plugin for Ruby on Rails or Merb, +# or as a standalone parser. # Sass is bundled with Haml, # so if the Haml plugin or RubyGem is installed, # Sass will already be installed as a plugin or gem, respectively. +# The first step for all of these is to install the Haml gem: # -# To install Haml and Sass as a Ruby on Rails plugin, -# use the normal Rails plugin installer: +# gem install haml # -# ./script/plugin install http://svn.hamptoncatlin.com/haml/tags/stable +# To enable it as a Rails plugin, +# then run +# +# haml --rails path/to/rails/app +# +# To enable Sass in Merb, +# add # -# Sass templates in Rails don't quite function in the same way as views, +# dependency "haml" +# +# to config/dependencies.rb. +# +# Sass templates in Rails and Merb don't quite function in the same way as views, # because they don't contain dynamic content, # and so only need to be compiled when the template file has been updated. # By default (see options, below), @@ -41,11 +51,8 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # For instance, public/stylesheets/sass/main.sass would be compiled to public/stylesheets/main.css. # # Using Sass in Ruby code is very simple. -# First install the Haml/Sass RubyGem: -# -# gem install haml -# -# Then you can use it by including the "sass" gem +# After installing the Haml gem, +# you can use it by running require "sass" # and using Sass::Engine like so: # # engine = Sass::Engine.new("#main\n :background-color #0000ff") @@ -394,7 +401,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # but all constants in that file are made available in the current file. # # Sass looks for other Sass files in the working directory, -# and the Sass file directory under Rails. +# and the Sass file directory under Rails or Merb. # Additional search directories may be specified # using the :load_paths option (see below). # @@ -562,7 +569,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # time a controller is accessed, # as opposed to only when the template has been modified. # Defaults to false. -# Only has meaning within Ruby on Rails. +# Only has meaning within Ruby on Rails or Merb. # # [:always_check] Whether a Sass template should be checked for updates every # time a controller is accessed, @@ -573,24 +580,34 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # true otherwise. # Only has meaning within Ruby on Rails. # +# [:full_exception] Whether an error in the Sass code +# should cause Sass to provide a detailed description. +# If set to true, the specific error will be displayed +# along with a line number and source snippet. +# Otherwise, a simple uninformative error message will be displayed. +# Defaults to false in production mode, true otherwise. +# Only has meaning within Ruby on Rails or Merb. +# # [:template_location] The directory where Sass templates should be read from. -# Defaults to RAILS_ROOT + "/public/stylesheets/sass". -# Only has meaning within Ruby on Rails. +# Defaults to RAILS_ROOT + "/public/stylesheets/sass" +# or MERB_ROOT + "/public/stylesheets/sass". +# Only has meaning within Ruby on Rails or Merb. # # [:css_location] The directory where CSS output should be written to. -# Defaults to RAILS_ROOT + "/public/stylesheets". -# Only has meaning within Ruby on Rails. +# Defaults to RAILS_ROOT + "/public/stylesheets" +# or MERB_ROOT + "/public/stylesheets". +# Only has meaning within Ruby on Rails or Merb. # # [:filename] The filename of the file being rendered. # This is used solely for reporting errors, -# and is automatically set when using Rails. +# and is automatically set when using Rails or Merb. # # [:load_paths] An array of filesystem paths which should be searched # for Sass templates imported with the "@import" directive. -# This defaults to the working directory and, in Rails, -# whatever :template_location is -# (by default RAILS_ROOT + "/public/stylesheets/sass"). +# This defaults to the working directory and, in Rails or Merb, +# whatever :template_location is. # module Sass; end require 'sass/engine' +require 'sass/plugin' if defined?(Merb::Plugins) diff --git a/lib/sass/plugin.rb b/lib/sass/plugin.rb index fd63ebaf..65741cde 100644 --- a/lib/sass/plugin.rb +++ b/lib/sass/plugin.rb @@ -1,21 +1,17 @@ require 'sass/engine' -require 'rubygems' -require 'action_controller' - -RAILS_ROOT = '. 'unless self.class.const_defined?('RAILS_ROOT') -RAILS_ENV = 'production' unless self.class.const_defined?('RAILS_ENV') module Sass - # This module contains methods that ActionController calls - # to automatically update Sass templates that need updating. - # It wasn't designed to be used outside of the context of ActionController. + # This module contains methods to aid in using Sass + # as a stylesheet-rendering plugin for various systems. + # Currently Rails/ActionController and Merb are supported out of the box. module Plugin class << self @@options = { - :template_location => RAILS_ROOT + '/public/stylesheets/sass', - :css_location => RAILS_ROOT + '/public/stylesheets', + :template_location => './public/stylesheets/sass', + :css_location => './public/stylesheets', :always_update => false, - :always_check => RAILS_ENV != "production" + :always_check => true, + :full_exception => true } # Gets various options for Sass. See README for details. @@ -54,7 +50,7 @@ module Sass begin result = engine.render rescue Exception => e - if RAILS_ENV != "production" + if options[:full_exception] e_string = "#{e.class}: #{e.message}" if e.is_a? Sass::SyntaxError @@ -111,17 +107,5 @@ module Sass end end -# This module refers to the ActionController module that's part of Ruby on Rails. -# Sass can be used as an alternate templating engine for Rails, -# and includes some modifications to make this more doable. -# The documentation can be found -# here[http://rubyonrails.org/api/classes/ActionController/Base.html]. -module ActionController - class Base # :nodoc: - alias_method :sass_old_process, :process - def process(*args) - Sass::Plugin.update_stylesheets if Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check] - sass_old_process(*args) - end - end -end +require 'sass/plugin/rails' if defined?(ActionController) +require 'sass/plugin/merb' if defined?(Merb::Plugins) diff --git a/lib/sass/plugin/merb.rb b/lib/sass/plugin/merb.rb new file mode 100644 index 00000000..d89a9236 --- /dev/null +++ b/lib/sass/plugin/merb.rb @@ -0,0 +1,20 @@ +unless defined?(Sass::MERB_LOADED) + Sass::MERB_LOADED = true + + Sass::Plugin.options.merge!(:template_location => MERB_ROOT + '/public/stylesheets/sass', + :css_location => MERB_ROOT + '/public/stylesheets', + :always_check => MERB_ENV != "production", + :full_exception => MERB_ENV != "production") + config = Merb::Plugins.config[:sass] || Merb::Plugins.config["sass"] || {} + config.symbolize_keys! + Sass::Plugin.options.merge!(config) + + class MerbHandler # :nodoc: + def process_with_sass(request, response) + Sass::Plugin.update_stylesheets if Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check] + process_without_sass(request, response) + end + alias_method :process_without_sass, :process + alias_method :process, :process_with_sass + end +end diff --git a/lib/sass/plugin/rails.rb b/lib/sass/plugin/rails.rb new file mode 100644 index 00000000..f21a8f2e --- /dev/null +++ b/lib/sass/plugin/rails.rb @@ -0,0 +1,18 @@ +unless defined?(Sass::RAILS_LOADED) + Sass::RAILS_LOADED = true + + Sass::Plugin.options.merge!(:template_location => RAILS_ROOT + '/public/stylesheets/sass', + :css_location => RAILS_ROOT + '/public/stylesheets', + :always_check => RAILS_ENV != "production", + :full_exception => RAILS_ENV != "production") + + module ActionController # :nodoc: + class Base # :nodoc: + alias_method :sass_old_process, :process + def process(*args) + Sass::Plugin.update_stylesheets if Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check] + sass_old_process(*args) + end + end + end +end diff --git a/test/sass/plugin_test.rb b/test/sass/plugin_test.rb index 00c10be3..0239ead1 100644 --- a/test/sass/plugin_test.rb +++ b/test/sass/plugin_test.rb @@ -1,9 +1,14 @@ #!/usr/bin/env ruby -RAILS_ENV = 'testing' +MERB_ENV = RAILS_ENV = 'testing' +RAILS_ROOT = '.' + require 'test/unit' require 'fileutils' require File.dirname(__FILE__) + '/../../lib/sass' +require 'rubygems' + +require 'action_controller' require 'sass/plugin' class SassPluginTest < Test::Unit::TestCase @@ -14,14 +19,7 @@ class SassPluginTest < Test::Unit::TestCase def setup FileUtils.mkdir File.dirname(__FILE__) + '/tmp' - Sass::Plugin.options = { - :template_location => File.dirname(__FILE__) + '/templates', - :css_location => File.dirname(__FILE__) + '/tmp', - :style => :compact, - :load_paths => [File.dirname(__FILE__) + '/results'], - } - Sass::Plugin.options[:always_update] = true - + set_plugin_opts Sass::Plugin.update_stylesheets end @@ -40,7 +38,7 @@ class SassPluginTest < Test::Unit::TestCase assert !Sass::Plugin.stylesheet_needs_update?('basic') end - def test_exception_handling + def test_full_exception_handling File.delete(tempfile_loc('bork')) Sass::Plugin.update_stylesheets File.open(tempfile_loc('bork')) do |file| @@ -49,18 +47,18 @@ class SassPluginTest < Test::Unit::TestCase File.delete(tempfile_loc('bork')) end - def test_production_exception_handling - Sass.const_set('RAILS_ENV', 'production') + def test_nonfull_exception_handling + Sass::Plugin.options[:full_exception] = false File.delete(tempfile_loc('bork')) Sass::Plugin.update_stylesheets assert_equal("/* Internal stylesheet error */", File.read(tempfile_loc('bork'))) File.delete(tempfile_loc('bork')) - Sass::Plugin.const_set('RAILS_ENV', 'testing') + Sass::Plugin.options[:full_exception] = true end - def test_controller_process + def test_rails_update File.delete(tempfile_loc('basic')) assert Sass::Plugin.stylesheet_needs_update?('basic') @@ -69,6 +67,27 @@ class SassPluginTest < Test::Unit::TestCase assert !Sass::Plugin.stylesheet_needs_update?('basic') end + def test_merb_update + begin + require 'merb' + rescue LoadError + puts "\nmerb couldn't be loaded, skipping a test" + return + end + + require 'sass/plugin/merb' + MerbHandler.send(:define_method, :process_without_sass) { |*args| } + set_plugin_opts + + File.delete(tempfile_loc('basic')) + assert Sass::Plugin.stylesheet_needs_update?('basic') + + MerbHandler.new('.').process nil, nil + + assert !Sass::Plugin.stylesheet_needs_update?('basic') + end + + private def assert_renders_correctly(name) @@ -85,6 +104,16 @@ class SassPluginTest < Test::Unit::TestCase def result_loc(name) File.dirname(__FILE__) + "/results/#{name}.css" end + + def set_plugin_opts + Sass::Plugin.options = { + :template_location => File.dirname(__FILE__) + '/templates', + :css_location => File.dirname(__FILE__) + '/tmp', + :style => :compact, + :load_paths => [File.dirname(__FILE__) + '/results'], + :always_update => true, + } + end end module Sass::Plugin From 42cb91918c0fba7584782f963d05ff524a8ef221 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sun, 18 Nov 2007 09:54:14 +0000 Subject: [PATCH 053/125] A minor fix that should make this work on JRuby. Thanks to James Walker. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@622 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/engine.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index 5a14aead..ba3cfaeb 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -228,7 +228,7 @@ END old_uline = nil (@template + "\n-#\n-#").each_with_index do |line, index| spaces, tabs = count_soft_tabs(line) - uline = line.lstrip[0...-1] + uline = line.lstrip.chomp line = uline.rstrip if !line.empty? From b26117865ed683f8de312b71d4933cbe32b9486c Mon Sep 17 00:00:00 2001 From: nex3 Date: Sun, 18 Nov 2007 09:57:58 +0000 Subject: [PATCH 054/125] =?UTF-8?q?Adding=20input=20and=20area=20elements?= =?UTF-8?q?=20to=20the=20auto-closed=20tag=20lists=20for=20Haml.=20Thanks?= =?UTF-8?q?=20to=20Julien=20Thewys=20and=20Mislav=20Marohni=C4=87.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@623 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/engine.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index ba3cfaeb..32d2e426 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -123,7 +123,7 @@ END :suppress_eval => false, :attr_wrapper => "'", :locals => {}, - :autoclose => ['meta', 'img', 'link', 'br', 'hr'], + :autoclose => ['meta', 'img', 'link', 'br', 'hr', 'input', 'area'], :filters => { 'sass' => Sass::Engine, 'plain' => Haml::Filters::Plain, From 9815c6b7638107de786b8647f1785fb95fcb7156 Mon Sep 17 00:00:00 2001 From: nex3 Date: Sun, 18 Nov 2007 11:14:12 +0000 Subject: [PATCH 055/125] ActionPack stuff isn't loaded unless it's needed. Thanks to Bob Aman for the original patch. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@624 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/helpers/action_view_mods.rb | 13 +------------ test/benchmark.rb | 9 ++++++--- test/haml/engine_test.rb | 5 +++++ test/haml/helper_test.rb | 24 ++++++------------------ test/haml/runner.rb | 1 + test/profile.rb | 8 +++++--- 6 files changed, 24 insertions(+), 36 deletions(-) diff --git a/lib/haml/helpers/action_view_mods.rb b/lib/haml/helpers/action_view_mods.rb index 4faa46e5..ee2da7ad 100644 --- a/lib/haml/helpers/action_view_mods.rb +++ b/lib/haml/helpers/action_view_mods.rb @@ -1,15 +1,4 @@ -begin - raise LoadError if defined?(Merb::Plugins) - require 'rubygems' - require 'active_support' - require 'action_controller' - require 'action_view' - action_view_included = true -rescue LoadError - action_view_included = false -end - -if action_view_included +if defined?(ActionView) and not defined?(Merb::Plugins) module ActionView class Base # :nodoc: def render_with_haml(*args, &block) diff --git a/test/benchmark.rb b/test/benchmark.rb index 11a975b8..fb22216f 100644 --- a/test/benchmark.rb +++ b/test/benchmark.rb @@ -1,9 +1,12 @@ +require 'rubygems' +require 'active_support' +require 'action_controller' +require 'action_view' + require File.dirname(__FILE__) + '/../lib/haml' require 'haml/template' require 'sass/engine' -require 'rubygems' -require 'active_support' -require 'action_view' + require 'benchmark' require 'stringio' diff --git a/test/haml/engine_test.rb b/test/haml/engine_test.rb index 9079d021..b7e1bea1 100644 --- a/test/haml/engine_test.rb +++ b/test/haml/engine_test.rb @@ -1,5 +1,10 @@ #!/usr/bin/env ruby +require 'rubygems' +require 'active_support' +require 'action_controller' +require 'action_view' + require 'test/unit' require File.dirname(__FILE__) + '/../../lib/haml' require 'haml/engine' diff --git a/test/haml/helper_test.rb b/test/haml/helper_test.rb index 735b4872..f0f30d45 100644 --- a/test/haml/helper_test.rb +++ b/test/haml/helper_test.rb @@ -1,5 +1,10 @@ #!/usr/bin/env ruby +require 'rubygems' +require 'active_support' +require 'action_controller' +require 'action_view' + require 'test/unit' require File.dirname(__FILE__) + '/../../lib/haml' require 'haml/template' @@ -72,24 +77,7 @@ class HelperTest < Test::Unit::TestCase def test_action_view_included assert(Haml::Helpers.action_view?) end - - def test_action_view_not_included - #This is for 100% rcov, rather than any real testing purposes. - Kernel.module_eval do - alias_method :old_require, :require - def require(string) - raise LoadError if string == "action_view" - old_require string - end - end - - load File.dirname(__FILE__) + '/../../lib/haml/helpers/action_view_mods.rb' - - Kernel.module_eval do - alias_method :require, :old_require - end - end - + def test_form_tag result = render("- form_tag 'foo' do\n %p bar\n %strong baz", :action_view) should_be = "
      \n

      bar

      \n baz\n
      \n" diff --git a/test/haml/runner.rb b/test/haml/runner.rb index e6ba1cea..5567a2d7 100644 --- a/test/haml/runner.rb +++ b/test/haml/runner.rb @@ -1,5 +1,6 @@ require 'rubygems' require 'active_support' +require 'action_controller' require 'action_view' require '../../lib/haml/template' require 'fileutils' diff --git a/test/profile.rb b/test/profile.rb index 26275bc7..62969e91 100644 --- a/test/profile.rb +++ b/test/profile.rb @@ -1,8 +1,10 @@ -require File.dirname(__FILE__) + '/../lib/haml' -require 'haml/template' require 'rubygems' require 'active_support' +require 'action_controller' require 'action_view' + +require File.dirname(__FILE__) + '/../lib/haml' +require 'haml/template' require 'profiler' require 'stringio' @@ -46,7 +48,7 @@ module Haml # automatically look for a haml template. # # Returns the results of the profiling as a string. - def profile(runs = 100, template_name = 'standard') + def profile(runs = 100, template_name = 'standard') AbstractProfiler.profile(runs) { @base.render template_name } end end From 80747f708bdddc6c8b98fe454dede7ef797e1aef Mon Sep 17 00:00:00 2001 From: nex3 Date: Mon, 19 Nov 2007 03:36:21 +0000 Subject: [PATCH 056/125] A couple documentation fixes. Thanks, Rich Morin. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@625 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/haml.rb b/lib/haml.rb index 239d7793..85f0fea8 100644 --- a/lib/haml.rb +++ b/lib/haml.rb @@ -25,7 +25,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # # Haml can be used in two ways: # as a plugin for Ruby on Rails, -# and as a standalong Ruby module. +# and as a standalone Ruby module. # # Sass can be used in several ways: # As a template engine for Ruby on Rails or Merb, @@ -59,7 +59,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # end # end # -# # file: app/views/movies/index.haml +# -# file: app/views/movies/index.haml # # #content # .title @@ -161,7 +161,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # @user = CrazyUser.find(15) # end # -# # file: app/views/users/show.haml +# -# file: app/views/users/show.haml # # %div[@user] # %bar[290]/ @@ -366,7 +366,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # # # -# If you're not using the UTF-8 characterset for your document, +# If you're not using the UTF-8 character set for your document, # you can specify which encoding should appear # in the XML prolog in a similar way. # For example: @@ -383,16 +383,16 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # wraps all text after it in an HTML comment. # For example: # -# %billabong -# / This is the billabong element -# I like billabongs! +# %peanutbutterjelly +# / This is the peanutbutterjelly element +# I like sandwiches! # # is compiled to: # -# -# -# I like billabongs! -# +# +# +# I like sandwiches! +# # # The forward slash can also wrap indented sections of code. For example: # From 5f422c284c03afd1ea963a2eccdc8cec149f8b48 Mon Sep 17 00:00:00 2001 From: nex3 Date: Mon, 19 Nov 2007 03:45:01 +0000 Subject: [PATCH 057/125] Don't install ri with rake install to avoid it dieing due to the Unhandled Special bug. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@626 7063305b-7217-0410-af8c-cdc13e5119b9 --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 43aa7184..1942f61e 100644 --- a/Rakefile +++ b/Rakefile @@ -96,7 +96,7 @@ unless ARGV[0] == 'benchmark' end task :install => [:package] do - sh %{gem install pkg/haml-#{File.read('VERSION').strip}} + sh %{gem install --no-ri pkg/haml-#{File.read('VERSION').strip}} end # ----- Documentation ----- From 562a0ce9727c6f70b0a0475e9d9b33f964a159a1 Mon Sep 17 00:00:00 2001 From: nex3 Date: Mon, 19 Nov 2007 03:48:42 +0000 Subject: [PATCH 058/125] Tagging version 1.7.2. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@627 7063305b-7217-0410-af8c-cdc13e5119b9 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 943f9cbc..f8a696c8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.7.1 +1.7.2 From bc3ccf934ee52c95b0fa5136da636dcbff599d22 Mon Sep 17 00:00:00 2001 From: nex3 Date: Thu, 22 Nov 2007 04:07:07 +0000 Subject: [PATCH 059/125] Fixing behavior of empty strings as attributes. Thanks to dschn (real name unknown) for pointing this out. git-svn-id: svn://hamptoncatlin.com/haml/tags/stable@629 7063305b-7217-0410-af8c-cdc13e5119b9 --- lib/haml/buffer.rb | 4 ++-- lib/haml/engine.rb | 4 ++-- test/haml/engine_test.rb | 8 ++++++++ test/haml/results/just_stuff.xhtml | 1 - test/haml/templates/just_stuff.haml | 1 - 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/haml/buffer.rb b/lib/haml/buffer.rb index ca7674f1..6d7483f3 100644 --- a/lib/haml/buffer.rb +++ b/lib/haml/buffer.rb @@ -154,8 +154,8 @@ module Haml # the result. def build_attributes(attributes = {}) result = attributes.collect do |a,v| - v = v.to_s - unless v.nil? || v.empty? + unless v.nil? + v = v.to_s attr_wrapper = @options[:attr_wrapper] if v.include? attr_wrapper if v.include? @other_quote_char diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index 32d2e426..bf85d7c5 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -665,8 +665,8 @@ END @other_quote_char = @options[:attr_wrapper] == '"' ? "'" : '"' result = attributes.collect do |a,v| - v = v.to_s - unless v.nil? || v.empty? + unless v.nil? + v = v.to_s attr_wrapper = @options[:attr_wrapper] if v.include? attr_wrapper if v.include? @other_quote_char diff --git a/test/haml/engine_test.rb b/test/haml/engine_test.rb index b7e1bea1..b17ffdef 100644 --- a/test/haml/engine_test.rb +++ b/test/haml/engine_test.rb @@ -71,6 +71,14 @@ class EngineTest < Test::Unit::TestCase assert_equal("

      \n", render("%p= 'Hello' if false")) end + def test_static_attributes_with_empty_attr + assert_equal("\n", render("%img{:src => '/foo.png', :alt => ''}")) + end + + def test_dynamic_attributes_with_empty_attr + assert_equal("\n", render("%img{:width => nil, :src => '/foo.png', :alt => String.new}")) + end + # Options tests def test_stop_eval diff --git a/test/haml/results/just_stuff.xhtml b/test/haml/results/just_stuff.xhtml index 71b91959..5d782ea5 100644 --- a/test/haml/results/just_stuff.xhtml +++ b/test/haml/results/just_stuff.xhtml @@ -29,7 +29,6 @@ stuff followed by whitespace there can even be sub-tags! Or script! --> -

      class attribute shouldn't appear!

      # I like sandwiches! # -# +# # The forward slash can also wrap indented sections of code. For example: -# +# # / # %p This doesn't render... # %div # %h1 Because it's commented out! -# +# # is compiled to: -# +# # -# +# # You can also use Internet Explorer conditional comments # (about)[http://www.quirksmode.org/css/condcom.html] # by enclosing the condition in square brackets after the /. # For example: -# +# # /[if IE] # %a{ :href => 'http://www.mozilla.com/en-US/firefox/' } # %h1 Get Firefox -# +# # is compiled to: -# +# # -# +# # ==== \ -# +# # The backslash character escapes the first character of a line, # allowing use of otherwise interpreted characters as plain text. # For example: -# +# # %title # = @title # \- MySite -# +# # is compiled to: -# +# # # MyPage # - MySite # -# +# # ==== | -# +# # The pipe character designates a multiline string. # It's placed at the end of a line # and means that all following lines that end with | # will be evaluated as though they were on the same line. # For example: -# +# # %whoo # %hoo I think this might get | # pretty long so I should | @@ -498,9 +498,9 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # multiline so it doesn't | # look awful. | # %p This is short. -# +# # is compiled to: -# +# # # # I think this might get pretty long so I should probably make it multiline so it doesn't look awful. @@ -567,21 +567,21 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # (BlueCloth takes precedence if both are installed). # # You can also define your own filters (see Setting Options, below). -# +# # === Ruby evaluators -# +# # ==== = -# +# # The equals character is followed by Ruby code, # which is evaluated and the output inserted into the document as plain text. # For example: -# +# # %p # = ['hi', 'there', 'reader!'].join " " # = "yo" -# +# # is compiled to: -# +# #

      # hi there reader! # yo @@ -600,31 +600,31 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) #

      # 1 + 1 = 2 #

      -# +# # ==== - -# +# # The hyphen character makes the text following it into "silent script": # Ruby script that is evaluated, but not output. -# +# # It is not recommended that you use this widely; # almost all processing code and logic should be restricted # to the Controller, the Helper, or partials. -# +# # For example: -# +# # - foo = "hello" # - foo << " there" # - foo << " you!" # %p= foo -# +# # is compiled to: -# +# #

      # hello there you! #

      -# +# # ===== Blocks -# +# # Ruby blocks, like XHTML tags, don't need to be explicitly closed in Haml. # Rather, they're automatically closed, based on indentation. # A block begins whenever the indentation is increased @@ -632,13 +632,13 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # It ends when the indentation decreases # (as long as it's not an +else+ clause or something similar). # For example: -# +# # - (42...47).each do |i| # %p= i # %p See, I can count! -# +# # is compiled to: -# +# #

      # 42 #

      @@ -654,9 +654,9 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) #

      # 46 #

      -# +# # Another example: -# +# # %p # - case 2 # - when 1 @@ -665,9 +665,9 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # = "2?" # - when 3 # = "3." -# +# # is compiled to: -# +# #

      # 2? #

      @@ -678,7 +678,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # signifies a silent comment. # Any text following this isn't rendered in the resulting document # at all. -# +# # For example: # # %p foo @@ -704,7 +704,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # #

      foo

      #

      bar

      -# +# # == Other Useful Things # # === Helpers @@ -714,19 +714,19 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # creating nicely indented output for user-defined helpers, # and other useful things. # The helpers are all documented in the Haml::Helpers and Haml::Helpers::ActionViewExtensions modules. -# +# # === Haml Options -# +# # Options can be set by setting the hash Haml::Template.options # from environment.rb in Rails, # or by passing an options hash to Haml::Engine. # Available options are: -# +# # [:suppress_eval] Whether or not attribute hashes and Ruby scripts # designated by = or ~ should be # evaluated. If this is true, said scripts are # rendered as empty strings. Defaults to false. -# +# # [:attr_wrapper] The character that should wrap element attributes. # This defaults to ' (an apostrophe). Characters # of this type within the attributes will be escaped diff --git a/lib/haml/buffer.rb b/lib/haml/buffer.rb index 4bdac1d3..155f5ae9 100644 --- a/lib/haml/buffer.rb +++ b/lib/haml/buffer.rb @@ -49,7 +49,7 @@ module Haml # Have to push every line in by the extra user set tabulation text.gsub!(/^/m, ' ' * @tabulation) end - + @buffer << "#{text}" @real_tabs += tab_change end @@ -58,17 +58,17 @@ module Haml # instance_eval. def push_script(result, flattened, close_tag = nil) tabulation = @real_tabs - + if flattened result = Haml::Helpers.find_and_preserve(result) end - + result = result.to_s while result[-1] == ?\n # String#chomp is slow result = result[0...-1] end - + if close_tag && Buffer.one_liner?(result) @buffer << result @buffer << "\n" @@ -77,10 +77,10 @@ module Haml if close_tag @buffer << "\n" end - + result = result.gsub(/^/m, tabs(tabulation)) @buffer << "#{result}\n" - + if close_tag @buffer << "#{tabs(tabulation-1)}\n" @real_tabs -= 1 @@ -93,7 +93,7 @@ module Haml # element, formats it, and adds it to the buffer. def open_tag(name, atomic, try_one_line, class_id, obj_ref, content, *attributes_hashes) tabulation = @real_tabs - + attributes = class_id attributes_hashes.each do |attributes_hash| attributes_hash.keys.each { |key| attributes_hash[key.to_s] = attributes_hash.delete(key) } diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index de4bfc43..5959e201 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -84,7 +84,7 @@ END # If it's a Binding or Proc object, # Haml uses it as the second argument to Kernel#eval; # otherwise, Haml just uses its #instance_eval context. - # + # # Note that Haml modifies the evaluation context # (either the scope object or the "self" object of the scope binding). # It extends Haml::Helpers, and various instance variables are set @@ -193,7 +193,7 @@ END # # Haml::Engine.new(".upcased= upcase").def_method(String, :upcased_div) # "foobar".upcased_div #=> "
      FOOBAR
      \n" - # + # # The first argument of the defined method is a hash of local variable names to values. # However, due to an unfortunate Ruby quirk, # the local variables which can be assigned must be pre-declared. @@ -209,7 +209,7 @@ END # obj = Object.new # Haml::Engine.new("%p= foo").def_method(obj, :render) # obj.render(:foo => "Hello!") #=> NameError: undefined local variable or method `foo' - # + # # Note that Haml modifies the evaluation context # (either the scope object or the "self" object of the scope binding). # It extends Haml::Helpers, and various instance variables are set diff --git a/lib/haml/exec.rb b/lib/haml/exec.rb index 0b0df67b..8d3a42aa 100644 --- a/lib/haml/exec.rb +++ b/lib/haml/exec.rb @@ -21,7 +21,7 @@ module Haml @opts.parse!(@args) process_result - + @options rescue Exception => e raise e if e.is_a? SystemExit @@ -45,7 +45,7 @@ module Haml def get_line(exception) exception.backtrace[0].scan(/:(\d+)/)[0] end - + private def set_opts(opts) @@ -110,7 +110,7 @@ Description: Options: END - + opts.on('--rails RAILS_DIR', "Install Haml and Sass from the Gem to a Rails project") do |dir| original_dir = dir diff --git a/lib/haml/filters.rb b/lib/haml/filters.rb index c548aa12..326358b5 100644 --- a/lib/haml/filters.rb +++ b/lib/haml/filters.rb @@ -59,7 +59,7 @@ module Haml return rescue LoadError; end # RCov doesn't see this, but it is run end - + begin @required = reqs[-1] require @required @@ -74,7 +74,7 @@ module Haml end end end - + class RedCloth < LazyLoaded def initialize(text) super('redcloth') @@ -85,7 +85,7 @@ module Haml @engine.to_html end end - + # Uses RedCloth to provide only Textile (not Markdown) parsing class Textile < RedCloth def render diff --git a/lib/haml/helpers.rb b/lib/haml/helpers.rb index 0e4e8236..ea26a88d 100644 --- a/lib/haml/helpers.rb +++ b/lib/haml/helpers.rb @@ -35,7 +35,7 @@ module Haml # end # context.init_haml_helpers # context.open :p, "Stuff" - # + # def init_haml_helpers @haml_is_haml = true @haml_stack = [Haml::Buffer.new] @@ -45,7 +45,7 @@ module Haml # call-seq: # find_and_preserve(input) # find_and_preserve {...} - # + # # Isolates the whitespace-sensitive tags in the string and uses preserve # to convert any endlines inside them into HTML entities for endlines. def find_and_preserve(input = '', &block) @@ -107,14 +107,14 @@ module Haml def list_of(array, &block) # :yields: item to_return = array.collect do |i| result = capture_haml(i, &block) - + if result.count("\n") > 1 result.gsub!("\n", "\n ") result = "\n #{result.strip}\n" else result.strip! end - + "
    • #{result}
    • " end to_return.join("\n") @@ -163,7 +163,7 @@ module Haml def tab_down(i = 1) haml_buffer.tabulation -= i end - + # Surrounds the given block of Haml code with the given characters, # with no whitespace in between. # For example: @@ -187,10 +187,10 @@ module Haml def surround(front, back = nil, &block) back ||= front output = capture_haml(&block) - + "#{front}#{output.chomp}#{back}\n" end - + # Prepends the given character to the beginning of the Haml block, # with no whitespace between. # For example: @@ -205,7 +205,7 @@ module Haml def precede(char, &block) "#{char}#{capture_haml(&block).chomp}\n" end - + # Appends the given character to the end of the Haml block, # with no whitespace between. # For example: @@ -222,7 +222,7 @@ module Haml def succeed(char, &block) "#{capture_haml(&block).chomp}#{char}\n" end - + # Captures the result of the given block of Haml code, # gets rid of the excess indentation, # and returns it as a string. @@ -254,7 +254,7 @@ module Haml # between when the opening and closing tags are output. # If the block is a Haml block or outputs text using puts, # the text will be properly indented. - # + # # For example, # # open :table do @@ -320,14 +320,14 @@ Use the #haml_tag method instead. END haml_tag(*args, &block) end - + private # Gets a reference to the current Haml::Buffer object. def haml_buffer @haml_stack[-1] end - + # Gives a proc the same local "_hamlout" and "_erbout" variables # that the current template has. def haml_bind_proc(&proc) @@ -335,23 +335,23 @@ END _erbout = _hamlout.buffer proc { |*args| proc.call(*args) } end - + # Performs the function of capture_haml, assuming local_buffer # is where the output of block goes. def capture_haml_with_buffer(local_buffer, *args, &block) position = local_buffer.length - + block.call *args - + captured = local_buffer.slice!(position..-1) - + min_tabs = nil captured.each do |line| tabs = line.index(/[^ ]/) min_tabs ||= tabs min_tabs = min_tabs > tabs ? tabs : min_tabs end - + result = captured.map do |line| line[min_tabs..-1] end @@ -359,7 +359,7 @@ END end # Returns whether or not the current template is a Haml template. - # + # # This function, unlike other Haml::Helpers functions, # also works in other ActionView templates, # where it will always return false. diff --git a/lib/haml/helpers/action_view_extensions.rb b/lib/haml/helpers/action_view_extensions.rb index 53329b3b..567297ae 100644 --- a/lib/haml/helpers/action_view_extensions.rb +++ b/lib/haml/helpers/action_view_extensions.rb @@ -31,7 +31,7 @@ if defined?(ActionView) # # .entry # :color #00f - # + # def page_class controller.controller_name + " " + controller.action_name end diff --git a/lib/haml/helpers/action_view_mods.rb b/lib/haml/helpers/action_view_mods.rb index 30ca9d7a..62f3f280 100644 --- a/lib/haml/helpers/action_view_mods.rb +++ b/lib/haml/helpers/action_view_mods.rb @@ -66,7 +66,7 @@ if defined?(ActionView) and not defined?(Merb::Plugins) module FormHelper def form_for_with_haml(object_name, *args, &proc) if block_given? && is_haml? - oldproc = proc + oldproc = proc proc = haml_bind_proc do |*args| tab_up oldproc.call(*args) diff --git a/lib/haml/html.rb b/lib/haml/html.rb index 26c8ffd9..927ca6cc 100644 --- a/lib/haml/html.rb +++ b/lib/haml/html.rb @@ -59,7 +59,7 @@ module Haml String.new else lines = text.split("\n") - + lines.map do |line| line.strip! "#{tabulate(tabs)}#{'\\' if Haml::Engine::SPECIAL_CHARACTERS.include?(line[0])}#{line}\n" @@ -73,7 +73,7 @@ module Haml def self.options @@options end - + TEXT_REGEXP = /^(\s*).*$/ class ::Hpricot::Doc @@ -129,14 +129,14 @@ module Haml class ::Hpricot::Elem def to_haml(tabs = 0) - output = "#{tabulate(tabs)}" + output = "#{tabulate(tabs)}" if HTML.options[:rhtml] && name[0...5] == 'haml:' return output + HTML.send("haml_tag_#{name[5..-1]}", CGI.unescapeHTML(self.innerHTML)) end output += "%#{name}" unless name == 'div' && (attributes.include?('id') || attributes.include?('class')) - + if attributes output += "##{attributes['id']}" if attributes['id'] attributes['class'].split(' ').each { |c| output += ".#{c}" } if attributes['class'] @@ -144,10 +144,10 @@ module Haml remove_attribute('class') output += haml_attributes if attributes.length > 0 end - + output += "/" if children.length == 0 output += "\n" - + self.children.each do |child| output += child.to_haml(tabs + 1) end diff --git a/lib/haml/precompiler.rb b/lib/haml/precompiler.rb index 2c1c4362..8936a695 100644 --- a/lib/haml/precompiler.rb +++ b/lib/haml/precompiler.rb @@ -176,7 +176,7 @@ END close until @to_close_stack.empty? flush_merged_text end - + # Processes and deals with lowering indentation. def process_indent(line) return unless line.tabs <= @template_tabs && @template_tabs > 0 @@ -217,7 +217,7 @@ END else push_plain text end end - + # Returns whether or not the text is a silent script text with one # of Ruby's mid-block keywords. def mid_block_keyword?(text) @@ -237,7 +237,7 @@ END @multiline.text << text[0...-1] return true end - + # A multiline string has just been activated, start adding the lines if is_multiline?(text) && (MULTILINE_STARTERS.include? text[0]) @multiline = Line.new text[0...-1], nil, line.index, nil, line.tabs @@ -262,7 +262,7 @@ END # Evaluates text in the context of the scope object, but # does not output the result. def push_silent(text, can_suppress = false) - flush_merged_text + flush_merged_text return if can_suppress && options[:suppress_eval] @precompiled << "#{text};" end @@ -274,11 +274,11 @@ END @tab_change += tab_change @try_one_liner = try_one_liner end - + def push_text(text, tab_change = 0, try_one_liner = false) push_merged_text("#{text}\n", tab_change, try_one_liner) end - + def flush_merged_text return if @merged_text.empty? @@ -288,7 +288,7 @@ END @merged_text = '' @tab_change = 0 @try_one_liner = false - end + end # Renders a block of text as plain text. # Also checks for an illegally opened block. @@ -322,12 +322,12 @@ END @precompiled << out end end - + # Causes text to be evaluated, and Haml::Helpers#find_and_flatten # to be run on it afterwards. def push_flat_script(text) flush_merged_text - + raise SyntaxError.new("Tag has no content.") if text.empty? push_script(text, true) end @@ -373,7 +373,7 @@ END close_tag = has_conditional ? "" : "-->" push_text(close_tag, -1) end - + # Closes a loud Ruby block. def close_loud(command) push_silent 'end', true @@ -400,7 +400,7 @@ END @haml_comment = false @template_tabs -= 1 end - + # Iterates through the classes and ids supplied through . # and # syntax, and returns a hash with them as attributes, # that can then be merged with another attributes hash. @@ -429,8 +429,8 @@ END # $5 holds the value matched by a string $2 || $5 end - - def parse_static_hash(text) + + def parse_static_hash(text) return {} unless text attributes = {} @@ -452,7 +452,7 @@ END def self.build_attributes(attr_wrapper, attributes = {}) quote_escape = attr_wrapper == '"' ? """ : "'" other_quote_char = attr_wrapper == '"' ? "'" : '"' - + result = attributes.collect do |attr, value| next if value.nil? @@ -473,7 +473,7 @@ END def prerender_tag(name, atomic, attributes) "<#{name}#{Precompiler.build_attributes(@options[:attr_wrapper], attributes)}#{atomic ? ' />' : '>'}" end - + # Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value def parse_tag(line) raise SyntaxError.new("Invalid tag: \"#{line}\"") unless match = line.scan(/[%]([-:\w]+)([-\w\.\#]*)(.*)/)[0] @@ -495,7 +495,7 @@ END # render that tag to @precompiled. def render_tag(line) tag_name, attributes, attributes_hash, object_ref, action, value = parse_tag(line) - + raise SyntaxError.new("Illegal element: classes and ids must have values.") if attributes =~ /[\.#](\.|#|\z)/ case action @@ -505,7 +505,7 @@ END parse = true value = unescape_interpolation(value[1..-1].strip) if value[0] == ?= end - + if parse && @options[:suppress_eval] parse = false value = '' @@ -524,7 +524,7 @@ END raise SyntaxError.new("Atomic tags can't have content.") if atomic && !value.empty? atomic = true if !@block_opened && value.empty? && @options[:autoclose].include?(tag_name) - + if object_ref == "nil" && attributes_hash.nil? && !flattened && (parse || Buffer.one_liner?(value)) # This means that we can render the tag directly to text and not process it in the buffer tag_closed = !value.empty? && Buffer.one_liner?(value) && !parse @@ -541,7 +541,7 @@ END attributes_hash = ', ' + attributes_hash if attributes_hash push_silent "_hamlout.open_tag(#{tag_name.inspect}, #{atomic.inspect}, #{(!value.empty?).inspect}, #{attributes.inspect}, #{object_ref}, #{content}#{attributes_hash})" end - + return if atomic if value.empty? @@ -549,7 +549,7 @@ END @output_tabs += 1 return end - + if parse flush_merged_text push_script(value, flattened, tag_name) @@ -567,13 +567,13 @@ END conditional, content = line.scan(COMMENT_REGEX)[0] content.strip! conditional << ">" if conditional - + if @block_opened && !content.empty? raise SyntaxError.new('Illegal Nesting: Nesting within a tag that already has content is illegal.') end open = "" : "-->"}") @@ -587,7 +587,7 @@ END close end end - + # Renders an XHTML doctype or XML shebang. def render_doctype(line) raise SyntaxError.new("Illegal Nesting: Nesting within a header command is illegal.") if @block_opened @@ -654,7 +654,7 @@ END raise SyntaxError.new("Unbalanced brackets.") end - + # Counts the tabulation of a line. def count_soft_tabs(line) @@ -665,7 +665,7 @@ END end [spaces, spaces/2] end - + # Pushes value onto @to_close_stack and increases # @template_tabs. def push_and_tabulate(value) diff --git a/lib/haml/template/patch.rb b/lib/haml/template/patch.rb index 8c855eb6..28f63cac 100644 --- a/lib/haml/template/patch.rb +++ b/lib/haml/template/patch.rb @@ -26,7 +26,7 @@ module ActionView def compile_haml(template, file_name, local_assigns) render_symbol = assign_method_name(:haml, template, file_name) locals = local_assigns.keys - + @@template_args[render_symbol] ||= {} locals_keys = @@template_args[render_symbol].keys | locals @@template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h } diff --git a/lib/haml/template/plugin.rb b/lib/haml/template/plugin.rb index df054ddf..af357c82 100644 --- a/lib/haml/template/plugin.rb +++ b/lib/haml/template/plugin.rb @@ -13,7 +13,7 @@ module Haml def compilable? true end - + def line_offset self.class.line_offset end @@ -26,13 +26,13 @@ module Haml options = Haml::Template.options.dup Haml::Engine.new(template, options).send(:precompiled_with_ambles, []) end - + def cache_fragment(block, name = {}, options = nil) @view.fragment_for(block, name, options) do eval("_hamlout.buffer", block.binding) end end - + def read_template_file(template_path, extension) File.read(template_path) end diff --git a/lib/sass.rb b/lib/sass.rb index 7cbc3ed9..59f369fb 100644 --- a/lib/sass.rb +++ b/lib/sass.rb @@ -11,7 +11,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # and implements various features that are useful # for creating manageable stylesheets. # -# == Features +# == Features # # * Whitespace active # * Well-formatted output @@ -32,9 +32,9 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # # To enable it as a Rails plugin, # then run -# +# # haml --rails path/to/rails/app -# +# # To enable Sass in Merb, # add # @@ -157,12 +157,12 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # # #main # :width 97% -# +# # p, div # :font-size 2em # a # :font-weight bold -# +# # pre # :font-size 3em # @@ -635,7 +635,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # #main { color: #fff; background-color: #000; } # #main p { width: 10em; } # -# .huge { font-size: 10em; font-weight: bold; text-decoration: underline; } +# .huge { font-size: 10em; font-weight: bold; text-decoration: underline; } # # === :compressed # @@ -645,7 +645,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # It's not meant to be human-readable. # For example: # -# #main{color:#fff;background-color:#000}#main p{width:10em}.huge{font-size:10em;font-weight:bold;text-decoration:underline} +# #main{color:#fff;background-color:#000}#main p{width:10em}.huge{font-size:10em;font-weight:bold;text-decoration:underline} # # == Sass Options # @@ -668,7 +668,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # For example: color: #0f3 # or width = !main_width. # By default, either syntax is valid. -# +# # [:never_update] Whether the CSS files should never be updated, # even if the template file changes. # Setting this to true may give small performance gains. @@ -680,7 +680,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # as opposed to only when the template has been modified. # Defaults to false. # Only has meaning within Ruby on Rails or Merb. -# +# # [:always_check] Whether a Sass template should be checked for updates every # time a controller is accessed, # as opposed to only when the Rails server starts. @@ -715,7 +715,7 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) # for Sass templates imported with the "@import" directive. # This defaults to the working directory and, in Rails or Merb, # whatever :template_location is. -# +# module Sass; end require 'sass/engine' diff --git a/lib/sass/constant.rb b/lib/sass/constant.rb index 27d7cd58..f38930e7 100644 --- a/lib/sass/constant.rb +++ b/lib/sass/constant.rb @@ -8,13 +8,13 @@ module Sass # Whitespace characters WHITESPACE = [?\ , ?\t, ?\n, ?\r] - + # The character used to escape values ESCAPE_CHAR = ?\\ # The character used to open and close strings STRING_CHAR = ?" - + # A mapping of syntactically-significant characters # to parsed symbols SYMBOLS = { @@ -32,13 +32,13 @@ module Sass # The regular expression used to parse constants MATCH = /^#{Regexp.escape(CONSTANT_CHAR.chr)}([^\s#{(SYMBOLS.keys + [ ?= ]).map {|c| Regexp.escape("#{c.chr}") }.join}]+)\s*=\s*(.+)/ - + # First-order operations FIRST_ORDER = [:times, :div, :mod] - + # Second-order operations SECOND_ORDER = [:plus, :minus] - + class << self def parse(value, constants, line) begin @@ -53,21 +53,21 @@ module Sass raise e end end - + private - + def tokenize(value) escaped = false is_string = false beginning_of_token = true str = '' to_return = [] - + reset_str = Proc.new do to_return << str unless str.empty? '' end - + value.each_byte do |byte| unless escaped if byte == ESCAPE_CHAR @@ -97,7 +97,7 @@ module Sass str = reset_str.call next end - + symbol = SYMBOLS[byte] # Adjacent values without an operator should be concatenated @@ -135,28 +135,28 @@ module Sass end end end - + escaped = false beginning_of_token = false str << byte.chr end - + if is_string raise Sass::SyntaxError.new("Unterminated string: #{value.dump}") end str = reset_str.call to_return end - + def parenthesize(value) parenthesize_helper(0, value, value.length)[0] end - + def parenthesize_helper(i, value, value_len, return_after_expr = false) to_return = [] beginning = i token = value[i] - + while i < value_len && token != :close if token == :open to_return.push(*value[beginning...i]) @@ -194,13 +194,13 @@ module Sass else i += 1 end - + token = value[i] end to_return.push(*value[beginning...i]) return to_return, i + 1 end - + #-- # TODO: Don't pass around original value; # have Constant.parse automatically add it to exception. @@ -234,7 +234,7 @@ module Sass end end end - + def get_constant(value, constants) to_return = constants[value] raise SyntaxError.new("Undefined constant: \"!#{value}\"") unless to_return diff --git a/lib/sass/constant/color.rb b/lib/sass/constant/color.rb index db59a975..473023cf 100644 --- a/lib/sass/constant/color.rb +++ b/lib/sass/constant/color.rb @@ -21,9 +21,9 @@ module Sass::Constant # :nodoc: 'teal' => 0x008080, 'aqua' => 0x00ffff } - + REGEXP = /\##{"([0-9a-fA-F]{1,2})" * 3}/ - + def parse(value) if (value =~ REGEXP) @value = value.scan(REGEXP)[0].map { |num| num.ljust(2, num).to_i(16) } @@ -32,7 +32,7 @@ module Sass::Constant # :nodoc: @value = (0..2).map{ |n| color >> (n << 3) & 0xff }.reverse end end - + def plus(other) if other.is_a? Sass::Constant::String Sass::Constant::String.from_value(self.to_s + other.to_s) @@ -40,7 +40,7 @@ module Sass::Constant # :nodoc: piecewise(other, :+) end end - + def minus(other) if other.is_a? Sass::Constant::String raise NoMethodError.new(nil, :minus) @@ -48,7 +48,7 @@ module Sass::Constant # :nodoc: piecewise(other, :-) end end - + def times(other) if other.is_a? Sass::Constant::String raise NoMethodError.new(nil, :times) @@ -56,7 +56,7 @@ module Sass::Constant # :nodoc: piecewise(other, :*) end end - + def div(other) if other.is_a? Sass::Constant::String raise NoMethodError.new(nil, :div) @@ -64,7 +64,7 @@ module Sass::Constant # :nodoc: piecewise(other, :/) end end - + def mod(other) if other.is_a? Sass::Constant::String raise NoMethodError.new(nil, :mod) @@ -72,24 +72,24 @@ module Sass::Constant # :nodoc: piecewise(other, :%) end end - + def to_s red, green, blue = @value.map { |num| num.to_s(16).rjust(2, '0') } "##{red}#{green}#{blue}" end - + protected - + def self.filter_value(value) value.map { |num| num.to_i } end - + private - + def piecewise(other, operation) other_num = other.is_a? Number other_val = other.value - + rgb = [] for i in (0...3) res = @value[i].send(operation, other_num ? other_val : other_val[i]) diff --git a/lib/sass/constant/literal.rb b/lib/sass/constant/literal.rb index 34fbced9..cb9df479 100644 --- a/lib/sass/constant/literal.rb +++ b/lib/sass/constant/literal.rb @@ -13,7 +13,7 @@ class Sass::Constant::Literal # :nodoc: # The regular expression matching colors. COLOR = /^\# (?: [\da-f]{3} | [\da-f]{6} ) | #{html_color_matcher}/ix - + def self.parse(value) case value when NUMBER @@ -24,11 +24,11 @@ class Sass::Constant::Literal # :nodoc: Sass::Constant::String.new(value) end end - + def initialize(value = nil) self.parse(value) if value end - + def perform self end @@ -36,15 +36,15 @@ class Sass::Constant::Literal # :nodoc: def concat(other) Sass::Constant::String.from_value("#{self.to_s} #{other.to_s}") end - + attr_reader :value - + protected - + def self.filter_value(value) value end - + def self.from_value(value) instance = self.new instance.instance_variable_set('@value', self.filter_value(value)) diff --git a/lib/sass/constant/number.rb b/lib/sass/constant/number.rb index d822f13b..59cf3851 100644 --- a/lib/sass/constant/number.rb +++ b/lib/sass/constant/number.rb @@ -10,7 +10,7 @@ module Sass::Constant # :nodoc: @value = first.empty? ? second.to_i : "#{first}#{second}".to_f @unit = unit unless unit.empty? end - + def plus(other) if other.is_a? Number operate(other, :+) @@ -20,7 +20,7 @@ module Sass::Constant # :nodoc: Sass::Constant::String.from_value(self.to_s + other.to_s) end end - + def minus(other) if other.is_a? Number operate(other, :-) @@ -28,7 +28,7 @@ module Sass::Constant # :nodoc: raise NoMethodError.new(nil, :minus) end end - + def times(other) if other.is_a? Number operate(other, :*) @@ -38,7 +38,7 @@ module Sass::Constant # :nodoc: raise NoMethodError.new(nil, :times) end end - + def div(other) if other.is_a? Number operate(other, :/) @@ -46,7 +46,7 @@ module Sass::Constant # :nodoc: raise NoMethodError.new(nil, :div) end end - + def mod(other) if other.is_a? Number operate(other, :%) @@ -54,13 +54,13 @@ module Sass::Constant # :nodoc: raise NoMethodError.new(nil, :mod) end end - + def to_s value = @value value = value.to_i if value % 1 == 0.0 "#{value}#{@unit}" end - + protected def self.from_value(value, unit=nil) @@ -68,7 +68,7 @@ module Sass::Constant # :nodoc: instance.instance_variable_set('@unit', unit) instance end - + def operate(other, operation) unit = nil if other.unit.nil? diff --git a/lib/sass/constant/operation.rb b/lib/sass/constant/operation.rb index 3033af50..20adc45d 100644 --- a/lib/sass/constant/operation.rb +++ b/lib/sass/constant/operation.rb @@ -9,13 +9,13 @@ module Sass::Constant # :nodoc: @operand2 = operand2 @operator = operator end - + def to_s self.perform.to_s end - + protected - + def perform literal1 = @operand1.perform literal2 = @operand2.perform diff --git a/lib/sass/constant/string.rb b/lib/sass/constant/string.rb index f8e00a60..ea638311 100644 --- a/lib/sass/constant/string.rb +++ b/lib/sass/constant/string.rb @@ -2,11 +2,11 @@ require 'sass/constant/literal' module Sass::Constant # :nodoc: class String < Literal # :nodoc: - + def parse(value) @value = value end - + def plus(other) Sass::Constant::String.from_value(self.to_s + other.to_s) end @@ -14,7 +14,7 @@ module Sass::Constant # :nodoc: def funcall(other) Sass::Constant::String.from_value("#{self.to_s}(#{other.to_s})") end - + def to_s @value end diff --git a/lib/sass/css.rb b/lib/sass/css.rb index 3fd56f7b..f8ea4cd1 100644 --- a/lib/sass/css.rb +++ b/lib/sass/css.rb @@ -105,7 +105,7 @@ module Sass build_tree.to_sass rescue Exception => err line = @template.string[0...@template.pos].split("\n").size - + err.backtrace.unshift "(css):#{line}" raise err end @@ -164,12 +164,12 @@ module Sass whitespace assert_match /:/ - + value = '' while @template.scan(/[^;\s\}]+/) value << @template[0] << whitespace end - + assert_match /(;|(?=\}))/ rule << Tree::AttrNode.new(name, value, nil) end @@ -241,7 +241,7 @@ module Sass # color: red # baz # color: blue - # + # def nest_rules(root) rules = OrderedHash.new root.children.select { |c| Tree::RuleNode === c }.each do |child| @@ -271,7 +271,7 @@ module Sass # # foo bar baz # color: red - # + # def flatten_rules(root) root.children.each { |child| flatten_rule(child) if child.is_a?(Tree::RuleNode) } end diff --git a/lib/sass/engine.rb b/lib/sass/engine.rb index 91b9b1bf..807db911 100755 --- a/lib/sass/engine.rb +++ b/lib/sass/engine.rb @@ -39,7 +39,7 @@ module Sass # The character used to denote a compiler directive. DIRECTIVE_CHAR = ?@ - + # Designates a non-parsed rule. ESCAPE_CHAR = ?\\ diff --git a/lib/sass/plugin/merb.rb b/lib/sass/plugin/merb.rb index 93e7e00e..f85209f4 100644 --- a/lib/sass/plugin/merb.rb +++ b/lib/sass/plugin/merb.rb @@ -15,13 +15,13 @@ unless defined?(Sass::MERB_LOADED) :always_check => env != "production", :full_exception => env != "production") config = Merb::Plugins.config[:sass] || Merb::Plugins.config["sass"] || {} - + if defined? config.symbolize_keys! config.symbolize_keys! end Sass::Plugin.options.merge!(config) - + if version[0] > 0 || version[1] >= 9 class Merb::Rack::Application # :nodoc: diff --git a/lib/sass/tree/attr_node.rb b/lib/sass/tree/attr_node.rb index d75e62f6..9c04226b 100644 --- a/lib/sass/tree/attr_node.rb +++ b/lib/sass/tree/attr_node.rb @@ -3,12 +3,12 @@ require 'sass/tree/node' module Sass::Tree class AttrNode < ValueNode attr_accessor :name - + def initialize(name, value, style) @name = name super(value, style) end - + def to_s(tabs, parent_name = nil) if value[-1] == ?; raise Sass::SyntaxError.new("Invalid attribute: #{declaration.dump} (This isn't CSS!)", @line) @@ -34,7 +34,7 @@ module Sass::Tree children.each do |kid| to_return << "#{kid.to_s(tabs, real_name)}" << join_string end - + (@style == :compressed && parent_name) ? to_return : to_return[0...-1] end diff --git a/lib/sass/tree/node.rb b/lib/sass/tree/node.rb index 037e68d4..cf536d3c 100644 --- a/lib/sass/tree/node.rb +++ b/lib/sass/tree/node.rb @@ -16,7 +16,7 @@ module Sass end @children << child end - + def to_s result = String.new children.each do |child| diff --git a/lib/sass/tree/rule_node.rb b/lib/sass/tree/rule_node.rb index ca499b4f..37a8d47f 100644 --- a/lib/sass/tree/rule_node.rb +++ b/lib/sass/tree/rule_node.rb @@ -21,7 +21,7 @@ module Sass::Tree def continued? rule[-1] == ?, end - + def to_s(tabs, super_rules = nil) attributes = [] sub_rules = [] @@ -52,7 +52,7 @@ module Sass::Tree per_rule_indent + r.gsub(/,$/, '').gsub(rule_split, rule_separator).rstrip end.join(line_separator) end - + children.each do |child| if child.is_a? RuleNode sub_rules << child @@ -60,7 +60,7 @@ module Sass::Tree attributes << child end end - + to_return = '' if !attributes.empty? old_spaces = ' ' * (tabs - 1) @@ -77,7 +77,7 @@ module Sass::Tree to_return << "#{total_rule} {\n#{attributes}#{end_attrs}}\n" end end - + tabs += 1 unless attributes.empty? || @style != :nested sub_rules.each do |sub| to_return << sub.to_s(tabs, total_rule) diff --git a/lib/sass/tree/value_node.rb b/lib/sass/tree/value_node.rb index e7b43d90..b6a30051 100644 --- a/lib/sass/tree/value_node.rb +++ b/lib/sass/tree/value_node.rb @@ -3,7 +3,7 @@ require 'sass/tree/node' module Sass::Tree class ValueNode < Node attr_accessor :value - + def initialize(value, style) @value = value super(style) From 2418afd38ebc0bdad5a7e2005f3296fb10a3e965 Mon Sep 17 00:00:00 2001 From: Olek Poplavsky Date: Sun, 13 Apr 2008 20:13:38 -0400 Subject: [PATCH 117/125] Fixed problem with Sass partials in subdirectories rendering to CSS. --- lib/sass/plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/sass/plugin.rb b/lib/sass/plugin.rb index 0da00cb5..d5ff135f 100644 --- a/lib/sass/plugin.rb +++ b/lib/sass/plugin.rb @@ -119,7 +119,7 @@ module Sass end def forbid_update?(name) - name[0] == ?_ + name.sub(/^.*\//, '')[0] == ?_ end def stylesheet_needs_update?(name) From afc34dec6b43c674e64e5d2eb62e3f42b64d6a16 Mon Sep 17 00:00:00 2001 From: Nathan Weizenbaum Date: Sun, 13 Apr 2008 20:55:37 -0700 Subject: [PATCH 118/125] Get rid of a redundancy in a regular expression. --- lib/haml/precompiler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/haml/precompiler.rb b/lib/haml/precompiler.rb index 8936a695..542d9e84 100644 --- a/lib/haml/precompiler.rb +++ b/lib/haml/precompiler.rb @@ -476,7 +476,7 @@ END # Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value def parse_tag(line) - raise SyntaxError.new("Invalid tag: \"#{line}\"") unless match = line.scan(/[%]([-:\w]+)([-\w\.\#]*)(.*)/)[0] + raise SyntaxError.new("Invalid tag: \"#{line}\"") unless match = line.scan(/%([-:\w]+)([-\w\.\#]*)(.*)/)[0] tag_name, attributes, rest = match if rest[0] == ?{ scanner = StringScanner.new(rest) From 8ad882aa2d5193f88d3f522d4c264ce4aef53c49 Mon Sep 17 00:00:00 2001 From: Nathan Weizenbaum Date: Thu, 17 Apr 2008 17:24:50 -0700 Subject: [PATCH 119/125] Make haml --check work. It wasn't properly requiring the stringio library. --- lib/haml/exec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/haml/exec.rb b/lib/haml/exec.rb index 8d3a42aa..2ab8f1ff 100644 --- a/lib/haml/exec.rb +++ b/lib/haml/exec.rb @@ -145,6 +145,7 @@ END end opts.on('-c', '--check', "Just check syntax, don't evaluate.") do + require 'stringio' @options[:check_syntax] = true @options[:output] = StringIO.new end From ef40c30ab6e0011437aeddbada220f3965aec574 Mon Sep 17 00:00:00 2001 From: Nathan Weizenbaum Date: Thu, 17 Apr 2008 17:26:32 -0700 Subject: [PATCH 120/125] Monkeypatch Rails 2.0.2 to get compile errors to display correctly. --- lib/haml/template/plugin.rb | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/lib/haml/template/plugin.rb b/lib/haml/template/plugin.rb index af357c82..3e7f6b2b 100644 --- a/lib/haml/template/plugin.rb +++ b/lib/haml/template/plugin.rb @@ -44,4 +44,39 @@ if defined? ActionView::Template and ActionView::Template.respond_to? :register_ else ActionView::Base end.register_template_handler(:haml, Haml::Template) + +# In Rails 2.0.2, ActionView::TemplateError took arguments +# that we can't fill in from the Haml::Template context. +# Thus, we've got to monkeypatch ActionView::Base to catch the error. +if ActionView::TemplateError.instance_method(:initialize).arity == 5 + class ActionView::Base + def compile_template(handler, template, file_name, local_assigns) + render_symbol = assign_method_name(handler, template, file_name) + + # Move begin up two lines so it captures compilation exceptions. + begin + render_source = create_template_source(handler, template, render_symbol, local_assigns.keys) + line_offset = @@template_args[render_symbol].size + handler.line_offset + + file_name = 'compiled-template' if file_name.blank? + CompiledTemplates.module_eval(render_source, file_name, -line_offset) + rescue Exception => e # errors from template code + if logger + logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}" + logger.debug "Function body: #{render_source}" + logger.debug "Backtrace: #{e.backtrace.join("\n")}" + end + + # There's no way to tell Haml about the filename, + # so we've got to insert it ourselves. + e.backtrace[0].gsub!('(haml)', file_name) if e.is_a?(Haml::Error) + + raise ActionView::TemplateError.new(extract_base_path_from(file_name) || view_paths.first, file_name || template, @assigns, template, e) + end + + @@compile_time[render_symbol] = Time.now + # logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger + end + end +end # :startdoc: From 6b5355ddf9df6d92e4cb7a651aa7e1b7a9b96366 Mon Sep 17 00:00:00 2001 From: Nathan Weizenbaum Date: Thu, 17 Apr 2008 18:08:58 -0700 Subject: [PATCH 121/125] Make error reporting work with Rails 2.0.{0,1} --- lib/haml/template/patch.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/haml/template/patch.rb b/lib/haml/template/patch.rb index 28f63cac..304cb084 100644 --- a/lib/haml/template/patch.rb +++ b/lib/haml/template/patch.rb @@ -42,7 +42,14 @@ module ActionView logger.debug "Backtrace: #{e.backtrace.join("\n")}" end - raise TemplateError.new(@base_path, file_name || template, @assigns, template, e) + base_path = if defined?(extract_base_path_from) + # Rails 2.0.x + extract_base_path_from(file_name) || view_paths.first + else + # Rails <=1.2.6 + @base_path + end + raise ActionView::TemplateError.new(base_path, file_name || template, @assigns, template, e) end @@compile_time[render_symbol] = Time.now From 188d27eb03223dd4e336608f707430a0ba10d14c Mon Sep 17 00:00:00 2001 From: Nathan Weizenbaum Date: Thu, 17 Apr 2008 12:26:56 -0700 Subject: [PATCH 122/125] Don't add a backtrace entry for Haml::Error if there's no line number. --- lib/haml/engine.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index 5959e201..8a1bfe8d 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -74,7 +74,7 @@ END precompile rescue Haml::Error - $!.backtrace.unshift "#{@options[:filename]}:#{@index}" + $!.backtrace.unshift "#{@options[:filename]}:#{@index}" if @index raise end From b767fd1419a5d4cb7f87913a7ac4337e13fe7449 Mon Sep 17 00:00:00 2001 From: Nathan Weizenbaum Date: Thu, 17 Apr 2008 18:12:27 -0700 Subject: [PATCH 123/125] Fix error line reporting. Syntax errors involving a line being improperly indented were detected on the previous line, and thus had their line numbers wrong. Conflicts: lib/haml/precompiler.rb --- lib/haml/engine.rb | 2 +- lib/haml/error.rb | 16 +++++++++++++++- lib/haml/precompiler.rb | 20 ++++++++++---------- test/haml/engine_test.rb | 2 +- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index 8a1bfe8d..c1b6d843 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -74,7 +74,7 @@ END precompile rescue Haml::Error - $!.backtrace.unshift "#{@options[:filename]}:#{@index}" if @index + $!.backtrace.unshift "#{@options[:filename]}:#{@index + $!.line_offset}" if @index raise end diff --git a/lib/haml/error.rb b/lib/haml/error.rb index 6002556f..5e45f913 100644 --- a/lib/haml/error.rb +++ b/lib/haml/error.rb @@ -1,6 +1,20 @@ module Haml # The abstract type of exception raised by Haml code. - class Error < StandardError; end + class Error < StandardError + # :stopdoc: + + # By default, an error is taken to refer to the line of the template + # that was being processed when the exception was raised. + # However, if line_offset is non-zero, it's added to that line number + # to get the line to report for the error. + attr_reader :line_offset + + def initialize(message = nil, line_offset = 0) + super(message) + @line_offset = line_offset + end + # :startdoc: + end # SyntaxError is the type of exception raised when Haml encounters an # ill-formatted document. diff --git a/lib/haml/precompiler.rb b/lib/haml/precompiler.rb index 542d9e84..481840ae 100644 --- a/lib/haml/precompiler.rb +++ b/lib/haml/precompiler.rb @@ -158,7 +158,7 @@ END end if old_line.spaces != old_line.tabs * 2 - raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.") + raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.", 1) end unless old_line.text.empty? || @haml_comment @@ -166,7 +166,7 @@ END end if !flat? && line.tabs - old_line.tabs > 1 - raise SyntaxError.new("Illegal Indentation: Indenting more than once per line is illegal.") + raise SyntaxError.new("Illegal Indentation: Indenting more than once per line is illegal.", 1) end old_line = line newline @@ -293,7 +293,7 @@ END # Renders a block of text as plain text. # Also checks for an illegally opened block. def push_plain(text) - raise SyntaxError.new("Illegal Nesting: Nesting within plain text is illegal.") if @block_opened + raise SyntaxError.new("Illegal Nesting: Nesting within plain text is illegal.", 1) if @block_opened push_text text end @@ -518,10 +518,10 @@ END attributes = parse_class_and_id(attributes) Buffer.merge_attrs(attributes, static_attributes) if static_attributes - raise SyntaxError.new("Illegal Nesting: Nesting within an atomic tag is illegal.") if @block_opened && atomic - raise SyntaxError.new("Illegal Nesting: Content can't be both given on the same line as %#{tag_name} and nested within it.") if @block_opened && !value.empty? - raise SyntaxError.new("Tag has no content.") if parse && value.empty? - raise SyntaxError.new("Atomic tags can't have content.") if atomic && !value.empty? + raise SyntaxError.new("Illegal Nesting: Nesting within an atomic tag is illegal.", 1) if @block_opened && atomic + raise SyntaxError.new("Illegal Nesting: Content can't be both given on the same line as %#{tag_name} and nested within it.", 1) if @block_opened && !value.empty? + raise SyntaxError, "Tag has no content." if parse && value.empty? + raise SyntaxError, "Atomic tags can't have content." if atomic && !value.empty? atomic = true if !@block_opened && value.empty? && @options[:autoclose].include?(tag_name) @@ -569,7 +569,7 @@ END conditional << ">" if conditional if @block_opened && !content.empty? - raise SyntaxError.new('Illegal Nesting: Nesting within a tag that already has content is illegal.') + raise SyntaxError.new('Illegal Nesting: Nesting within a tag that already has content is illegal.', 1) end open = "