Creating the next set of branches.
That is, the buffered branch is being removed and replaced with the edge branch. This marks the beginning of the edge branch. git-svn-id: svn://hamptoncatlin.com/haml/branches/edge@80 7063305b-7217-0410-af8c-cdc13e5119b9
This commit is contained in:
parent
4266688e30
commit
89a10b9dfe
346
README
346
README
|
@ -22,6 +22,9 @@ HAML was originally created by Hampton Catlin (hcatlin). Help with the
|
|||
Ruby On Rails implementation and much of the documentation by
|
||||
Jeff Hardy (packagethief).
|
||||
|
||||
Nathan Weizenbaum (Nex3) contribued the buffered-engine code along with many
|
||||
other enhancements including the silent-line syntax ("-").
|
||||
|
||||
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*.
|
||||
|
@ -50,72 +53,185 @@ is compiled to:
|
|||
|
||||
== Characters with meaning to Haml
|
||||
|
||||
Haml responds to certain special characters. To create an element in the form of
|
||||
<tt><element></element></tt> use the <tt>%</tt> character, immediately followed
|
||||
by the element name. To specify attributes, include a hash of attributes inside
|
||||
curly braces. Example:
|
||||
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.
|
||||
|
||||
==== %
|
||||
|
||||
This element 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
|
||||
<tt><element></element></tt>. For example:
|
||||
|
||||
%one
|
||||
%meta{:content => 'something'}/
|
||||
%two
|
||||
%three Hey there
|
||||
|
||||
|
||||
is compiled to:
|
||||
|
||||
<one>
|
||||
<two>
|
||||
<meta content='something' />
|
||||
<three>Hey there</three>
|
||||
</two>
|
||||
</one>
|
||||
|
||||
Any string is a valid element name; Haml will automatically generate opening and
|
||||
closing tags for any element. When you want to force the output of a
|
||||
self-closing tag, use the forward slash character. Example:
|
||||
closing tags for any element.
|
||||
|
||||
%br/ # => <br />
|
||||
%meta{:http-equiv => 'Content-Type', :content => 'text/html'}/
|
||||
# => <meta http-equiv='Content-Type' content='text/html' />
|
||||
==== {}
|
||||
|
||||
HTML div elements are assumed when no <tt>%tag</tt> is present and the line is
|
||||
preceeded by either the <tt>#</tt> or the <tt>.</tt> characters. This convention
|
||||
uses familiar CSS semantics: <tt>#</tt> denotes the id of the element,
|
||||
<tt>.</tt> denotes its class name. Example:
|
||||
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. At
|
||||
the moment, though, it doesn't see local variables. The hash is placed after
|
||||
the tag is defined. For example:
|
||||
|
||||
#collection
|
||||
.item
|
||||
Broken record album
|
||||
|
||||
is the same as:
|
||||
%head{ :name => "doc_head" }
|
||||
%script{ 'type' => "text/" + "javascript", :src => "javascripts/script_#{2 + 7}" }
|
||||
|
||||
%div{:id => collection}
|
||||
%div{:class => 'item'}
|
||||
Broken record album
|
||||
is compiled to:
|
||||
|
||||
and is comiled to:
|
||||
<head name="doc_head">
|
||||
<script src='javascripts/script_9' type='text/javascript'>
|
||||
</script>
|
||||
</head>
|
||||
|
||||
==== []
|
||||
|
||||
<div id='collection'>
|
||||
<div class='item'>Broken record album</div>
|
||||
Square brackets follow a tag definiton 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:
|
||||
|
||||
<div class="crazy_user" id="crazy_user_15">
|
||||
<bar class="fixnum" id="fixnum_581" />
|
||||
Hello!
|
||||
</div>
|
||||
|
||||
There is a shortcut when you want to specify either the id or class attributes
|
||||
of an element: follow the element name with either the <tt>#</tt> or the
|
||||
<tt>.</tt> characters. Example:
|
||||
This is based off of DHH's SimplyHelpful syntax as presented at RailsConf Europe 2006.
|
||||
|
||||
#things
|
||||
==== /
|
||||
|
||||
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:
|
||||
|
||||
<br />
|
||||
<meta http-equiv='Content-Type' content='text/html' />
|
||||
|
||||
==== . and #
|
||||
|
||||
The period and pound sign are borrowed from CSS and used as shortcuts to specify the
|
||||
<tt>class</tt> and <tt>id</tt> attributes of an element, respectively. They are
|
||||
placed immediately after the tag, and before an attributes hash. For example:
|
||||
|
||||
div#things
|
||||
%span#rice Chicken Fried
|
||||
%p.beans The magical fruit
|
||||
%p.beans{ :food => 'true' } The magical fruit
|
||||
%h1.class#id La La La
|
||||
|
||||
is compiled to:
|
||||
|
||||
<div id='things'>
|
||||
<span id='rice'>Chicken Fried</span>
|
||||
<p class='beans'>The magical fruit</p>
|
||||
<p class='beans' food='true'>The magical fruit</p>
|
||||
<h1 class='class' id='id'>La La La</h1>
|
||||
</div>
|
||||
|
||||
=== Specifying a document type
|
||||
==== Assumed Divs
|
||||
|
||||
When describing xhtml documents with Haml, you can have a document type
|
||||
Because the div element is used so often, it is the default element. If you only
|
||||
define a class and/or id using the <tt>.</tt> or <tt>#</tt> 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:
|
||||
|
||||
<div id='collection'>
|
||||
<div class='item'>Broken record album</div>
|
||||
<div class='description'>What a cool item!</div>
|
||||
</div>
|
||||
|
||||
==== = and ~
|
||||
|
||||
<tt>=</tt> and <tt>~</tt> are placed at the end of a tag definition, after class,
|
||||
id, and attribute declarations. They're just shortcuts for inserting Ruby code
|
||||
into an element. They work the same as <tt>=</tt> and <tt>~</tt> without a tag;
|
||||
see below for documentation of those. For example:
|
||||
|
||||
%p= "hello"
|
||||
%h1~ 1 + 2
|
||||
|
||||
is the same as:
|
||||
|
||||
%p
|
||||
= "hello"
|
||||
%h1
|
||||
~ 1 + 2
|
||||
|
||||
and is compiled to:
|
||||
|
||||
<p>
|
||||
hello
|
||||
</p>
|
||||
<h1>
|
||||
3
|
||||
</h1>
|
||||
|
||||
=== XHTML Helpers
|
||||
|
||||
==== No Special Character
|
||||
|
||||
If no special character appears at the beginning of a line, it is rendered as plain
|
||||
text. For example:
|
||||
|
||||
%gee
|
||||
%whiz
|
||||
Wow this is cool!
|
||||
|
||||
is compiled to:
|
||||
|
||||
<gee>
|
||||
<whiz>
|
||||
Wow this is cool!
|
||||
</whiz>
|
||||
</gee>
|
||||
|
||||
==== !!!
|
||||
|
||||
When describing XHTML documents with Haml, you can have a document type
|
||||
generated automatically by including the characters <tt>!!!</tt> as the first
|
||||
line in your document. Example:
|
||||
|
||||
|
@ -139,11 +255,158 @@ is compiled to:
|
|||
<p>Sign my guestbook</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
==== /
|
||||
|
||||
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:
|
||||
|
||||
<billabong>
|
||||
<!-- This is the billabong element -->
|
||||
I like billabongs!
|
||||
</billabong>
|
||||
|
||||
==== |
|
||||
|
||||
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 <tt>|</tt> 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:
|
||||
|
||||
%hoo I think this might get |
|
||||
pretty long so I should |
|
||||
probably make it |
|
||||
multiline so it doesn't |
|
||||
look awful. |
|
||||
|
||||
=== 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:
|
||||
|
||||
<p>
|
||||
hi there reader!
|
||||
yo
|
||||
</p>
|
||||
|
||||
==== ~
|
||||
|
||||
The tilde character works the same as the equals character, but the output is
|
||||
modified in such a way that newlines in whitespace-sensitive elements work
|
||||
properly. For example:
|
||||
|
||||
%foo
|
||||
= "Woah <pre> this is \n</pre> crazy"
|
||||
%foo2
|
||||
~ "Woah <pre> this is \n</pre> crazy"
|
||||
|
||||
is compiled to:
|
||||
|
||||
<foo>
|
||||
Woah <pre> this is
|
||||
</pre> crazy
|
||||
</foo>
|
||||
<foo2>
|
||||
Woah <pre> this is 
</pre> crazy
|
||||
</foo2>
|
||||
|
||||
==== -
|
||||
|
||||
The hyphen character makes the text following it into "silent script", or
|
||||
Ruby script that is evaluated, but not output.
|
||||
|
||||
<b>It is not reccomended that you use this widely; almost all processing
|
||||
code and logic should be kept to the Controller, the Helper, or partials.</b>
|
||||
|
||||
For example:
|
||||
|
||||
- foo = "hello"
|
||||
- foo << " there"
|
||||
- foo << " you!"
|
||||
%p= foo
|
||||
|
||||
is compiled to:
|
||||
|
||||
<p>
|
||||
hello there you!
|
||||
</p>
|
||||
|
||||
===== Blocks
|
||||
|
||||
Like XHTML tags, you don't need to explicity close your Ruby blocks in
|
||||
HAML. Rather, they're automatically closed based on tabs. A block begins
|
||||
whenever the indentation is increased after a silent script command, and
|
||||
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:
|
||||
|
||||
<p>
|
||||
42
|
||||
</p>
|
||||
<p>
|
||||
43
|
||||
</p>
|
||||
<p>
|
||||
44
|
||||
</p>
|
||||
<p>
|
||||
45
|
||||
</p>
|
||||
<p>
|
||||
46
|
||||
</p>
|
||||
|
||||
Another example:
|
||||
|
||||
%p
|
||||
- case 2
|
||||
- when 1
|
||||
= "1!"
|
||||
- when 2
|
||||
= "2?"
|
||||
- when 3
|
||||
= "3."
|
||||
|
||||
is compiled to:
|
||||
|
||||
<p>
|
||||
2?
|
||||
</p>
|
||||
|
||||
== Using Haml as a Rails plugin
|
||||
|
||||
Write Rails templates with the .haml extension. Example:
|
||||
|
||||
# file: app/views/movies/teen_wolf.haml
|
||||
|
||||
%html
|
||||
%head
|
||||
%title= "Teen Wolf (1985)"
|
||||
|
@ -176,12 +439,9 @@ is compiled to:
|
|||
</html>
|
||||
|
||||
You can access instance variables in Haml templates the same way you do in ERb
|
||||
templates. Helper methods are also available in Haml templates. To specify that
|
||||
a line should be evaulated as Ruby, use the <tt>=</tt> character at the begining
|
||||
of a line, or immediately following an element name. The return value of the
|
||||
method call will be inserted into the stream. Example:
|
||||
templates. Helper methods are also available in Haml templates. Example:
|
||||
|
||||
file: app/controllers/movies_controller.rb
|
||||
# file: app/controllers/movies_controller.rb
|
||||
|
||||
class MoviesController < ApplicationController
|
||||
def index
|
||||
|
@ -189,14 +449,14 @@ method call will be inserted into the stream. Example:
|
|||
end
|
||||
end
|
||||
|
||||
file: app/views/movies/index.haml
|
||||
# file: app/views/movies/index.haml
|
||||
|
||||
#content
|
||||
.title
|
||||
%h1= @title
|
||||
= link_to 'Home', home_url
|
||||
|
||||
is be compiled to:
|
||||
may be compiled to:
|
||||
|
||||
<div id='content'>
|
||||
<div class='title'>
|
||||
|
@ -206,8 +466,6 @@ is be compiled to:
|
|||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
Copyright (c) 2006 Hampton Catlin
|
||||
Licensed under the MIT License
|
||||
|
|
86
Rakefile
86
Rakefile
|
@ -1,7 +1,19 @@
|
|||
require 'rubygems'
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
||||
|
||||
volatile_requires = ['rcov/rcovtask']
|
||||
not_loaded = []
|
||||
volatile_requires.each do |file|
|
||||
begin
|
||||
require file
|
||||
rescue LoadError
|
||||
not_loaded.push file
|
||||
end
|
||||
end
|
||||
|
||||
# ----- Default: Testing ------
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
@ -13,23 +25,79 @@ Rake::TestTask.new(:test) do |t|
|
|||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Benchmark HAML against ERb. The benchmark routine is run 100. Use TIMES=n to override'
|
||||
# ----- Benchmarking -----
|
||||
|
||||
temp_desc = <<END
|
||||
Benchmark HAML against ERb.
|
||||
TIMES=n sets the number of runs. Defaults to 100.
|
||||
END
|
||||
desc temp_desc.chomp
|
||||
task :benchmark do
|
||||
puts '-'*51, "+ Benchmark: HAML vs. ERb", '-'*51
|
||||
require 'test/benchmark'
|
||||
|
||||
puts '-'*51, "Benchmark: HAML vs. ERb", '-'*51
|
||||
puts "Running benchmark #{ENV['TIMES']} times..." if ENV['TIMES']
|
||||
puts `ruby test/benchmark.rb #{ENV['TIMES']}`
|
||||
args = []
|
||||
args.push ENV['TIMES'].to_i if ENV['TIMES']
|
||||
benchmarker = Haml::Benchmarker.new
|
||||
puts benchmarker.benchmark(*args)
|
||||
puts '-'*51
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the haml plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
# ----- Documentation -----
|
||||
|
||||
rdoc_task = Proc.new do |rdoc|
|
||||
rdoc.title = 'Haml'
|
||||
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')
|
||||
end
|
||||
|
||||
task :rcov do
|
||||
`rcov test/*.rb`
|
||||
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')
|
||||
rdoc.rdoc_files = Rake::FileList.new(*rdoc.rdoc_files.to_a)
|
||||
rdoc.rdoc_files.include('lib/haml/buffer.rb')
|
||||
end
|
||||
|
||||
# ----- Coverage -----
|
||||
|
||||
unless not_loaded.include? 'rcov/rcovtask'
|
||||
Rcov::RcovTask.new do |t|
|
||||
t.libs << "test"
|
||||
t.test_files = FileList['test/*_test.rb']
|
||||
t.verbose = true
|
||||
end
|
||||
end
|
||||
|
||||
# ----- Profiling -----
|
||||
|
||||
temp_desc = <<END
|
||||
Run a profile of HAML.
|
||||
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'
|
||||
|
||||
puts '-'*51, "Profiling HAML::Template", '-'*51
|
||||
|
||||
args = []
|
||||
args.push ENV['TIMES'].to_i if ENV['TIMES']
|
||||
args.push ENV['FILE'] if ENV['FILE']
|
||||
|
||||
profiler = Haml::Profiler.new
|
||||
res = profiler.profile(*args)
|
||||
puts res
|
||||
|
||||
puts '-'*51
|
||||
end
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
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. <tt><p>Hello world</p></tt>
|
||||
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
|
||||
|
||||
# Creates a new buffer.
|
||||
def initialize
|
||||
@buffer = ""
|
||||
@one_liner_pending = false
|
||||
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)
|
||||
if @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 = find_and_flatten(result)
|
||||
end
|
||||
unless result.nil?
|
||||
result = result.to_s.chomp.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)
|
||||
attributes = {}
|
||||
attributes.merge!(parse_object_ref(obj_ref)) if obj_ref
|
||||
attributes.merge!(parse_class_and_id(class_id)) if class_id
|
||||
attributes.merge!(attributes_hash) unless attributes_hash.nil? || attributes_hash.empty?
|
||||
|
||||
@buffer << "#{tabs(tabulation)}<#{name}#{build_attributes(attributes)}"
|
||||
@one_liner_pending = false
|
||||
if atomic
|
||||
@buffer << " />\n"
|
||||
else
|
||||
if try_one_line
|
||||
@one_liner_pending = true
|
||||
@buffer << ">"
|
||||
else
|
||||
@buffer << ">\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Creates a closing tag with the given name.
|
||||
def close_tag(name, tabulation)
|
||||
if @one_liner_pending
|
||||
@buffer << "</#{name}>\n"
|
||||
@one_liner_pending = false
|
||||
else
|
||||
push_text("</#{name}>", tabulation)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Gets <tt>count</tt> tabs. Mostly for internal use.
|
||||
def tabs(count)
|
||||
' ' * count
|
||||
end
|
||||
|
||||
# Iterates through the classes and ids supplied through <tt>.</tt>
|
||||
# and <tt>#</tt> 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-Z_()]+)/) 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)
|
||||
ref = ref[0]
|
||||
class_name = ref.class.to_s.underscore
|
||||
{:id => "#{class_name}_#{ref.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|
|
||||
unless v.nil?
|
||||
first_quote_type = v.to_s.scan(/['"]/).first
|
||||
quote_type = (first_quote_type == "'") ? '"' : "'"
|
||||
"#{a.to_s}=#{quote_type}#{v.to_s}#{quote_type}"
|
||||
end
|
||||
end
|
||||
result = result.compact.join(' ')
|
||||
(attributes.empty? ? String.new : String.new(' ')) + result
|
||||
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
|
|
@ -1,198 +1,330 @@
|
|||
require File.dirname(__FILE__) + '/helpers'
|
||||
require File.dirname(__FILE__) + '/buffer'
|
||||
require 'profiler'
|
||||
|
||||
module Haml #:nodoc:
|
||||
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.load('templates/really_cool_template.haml')
|
||||
# haml_engine = Haml::Engine.new(template)
|
||||
# output = haml_engine.to_html
|
||||
# puts output
|
||||
class Engine
|
||||
include Haml::Helpers
|
||||
|
||||
# Set the maximum length for a line to be considered a one-liner
|
||||
# Lines <= the maximum will be rendered on one line,
|
||||
# i.e. <tt><p>Hello world</p></tt>
|
||||
ONE_LINER_LENGTH = 50
|
||||
|
||||
# Keeps track of the ASCII values of the characters that begin a
|
||||
# specially-interpreted line.
|
||||
SPECIAL_CHARACTERS = %w(# . = ~ % /).collect { |c| c[0] }
|
||||
MULTILINE_CHAR_VALUE = '|'[0]
|
||||
MULTILINE_STARTERS = SPECIAL_CHARACTERS - ["/"[0]]
|
||||
|
||||
# The value of the character that designates that a line is part
|
||||
# of a multiline string.
|
||||
MULTILINE_CHAR_VALUE = '|'[0]
|
||||
|
||||
# Characters that designate that a multiline string may be about
|
||||
# to begin.
|
||||
MULTILINE_STARTERS = SPECIAL_CHARACTERS - ["/"[0]]
|
||||
|
||||
# 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 <tt>%p no!</tt>, because <tt>else</tt>
|
||||
# is a member of this array.
|
||||
MID_BLOCK_KEYWORDS = ['else', 'elsif', 'rescue', 'ensure', 'when']
|
||||
|
||||
# Creates a new instace of Haml::Engine to compile the given
|
||||
# template string.
|
||||
#
|
||||
# Available options are:
|
||||
#
|
||||
# [<tt>scope_object</tt>] The object within which the template will
|
||||
# be compiled, via instance_eval. For a Rails
|
||||
# application, this will typically be an
|
||||
# instance of ActionView::Base. If not specified,
|
||||
# this defaults to an instance of the Object class.
|
||||
# [<tt>suppress_eval</tt>] Whether or not attribute hashes and Ruby scripts
|
||||
# designated by <tt>=</tt> or <tt>~</tt> should be
|
||||
# evaluated. If this is true, said scripts are
|
||||
# rendered as empty strings. Defaults to false.
|
||||
def initialize(template, options = {})
|
||||
#turn each of the options into instance variables for the object
|
||||
options.each { |k,v| eval("@#{k} = v") }
|
||||
|
||||
@template = template #String
|
||||
@result, @to_close_queue = String.new, []
|
||||
@buffer = Haml::Buffer.new
|
||||
@precompiled = String.new
|
||||
@to_close_stack = []
|
||||
@tabulation = 0
|
||||
@scope_object = Object.new if @scope_object.nil?
|
||||
end
|
||||
|
||||
# Processes the template and returns the resulting (X)HTML code as
|
||||
# a string.
|
||||
def to_html
|
||||
# Process each line of the template
|
||||
@template.each_with_index do |line, index|
|
||||
count, line = count_soft_tabs(line)
|
||||
surpress_render, line, count = handle_multiline(count, line)
|
||||
suppress_render = handle_multiline(count, line, index)
|
||||
|
||||
if !surpress_render && count && line
|
||||
count, line = process_line(count, line)
|
||||
if !suppress_render && count && line
|
||||
count, line = process_line(count, line, index)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Make sure an ending multiline gets closed
|
||||
handle_multiline(0, nil, 0)
|
||||
|
||||
# Close all the open tags
|
||||
@to_close_queue.length.times { close_tag }
|
||||
|
||||
@to_close_stack.length.times { close }
|
||||
|
||||
# Compile the @precompiled buffer
|
||||
compile
|
||||
|
||||
# Return the result string
|
||||
@result
|
||||
@buffer.buffer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_line(count, line)
|
||||
if line.strip[0, 3] == '!!!'
|
||||
@result << %|<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n|
|
||||
# Processes a single line of HAML. <tt>count</tt> does *not* represent the
|
||||
# line number; rather, it represents the tabulation count (the number of
|
||||
# spaces before the line divided by two).
|
||||
#
|
||||
# This method doesn't return anything; it simply processes the line and
|
||||
# adds the appropriate code to <tt>@precompiled</tt>.
|
||||
def process_line(count, line, index)
|
||||
if line.lstrip[0, 3] == '!!!'
|
||||
push_text '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
||||
|
||||
else
|
||||
if count <= @to_close_queue.size && @to_close_queue.size > 0
|
||||
(@to_close_queue.size - count).times { close_tag }
|
||||
if count > @to_close_stack.size
|
||||
|
||||
# Indentation has been increased without a new tag
|
||||
if @latest_command == 45 # '-'
|
||||
|
||||
# The indentation was increased after silent script,
|
||||
# it must be a block
|
||||
@to_close_stack.push '_haml_end_block'
|
||||
end
|
||||
|
||||
elsif count <= @to_close_stack.size && @to_close_stack.size > 0 &&
|
||||
(line.length == 0 || line[0] != 45 || !MID_BLOCK_KEYWORDS.include?(line[1..-1].split[0]))
|
||||
|
||||
# The tabulation has gone down, and it's not because of one of
|
||||
# Ruby's mid-block keywords
|
||||
(@to_close_stack.size - count).times { close }
|
||||
end
|
||||
|
||||
case line[0..0]
|
||||
when '.', '#'
|
||||
render_div(line)
|
||||
when '%'
|
||||
render_tag(line)
|
||||
when '/'
|
||||
render_comment(line)
|
||||
when '='
|
||||
add template_eval(line[1, line.length]).to_s
|
||||
when '~'
|
||||
add find_and_flatten(template_eval(line[1, line.length])).to_s
|
||||
else
|
||||
add line.strip
|
||||
|
||||
if line.length > 0
|
||||
@latest_command = line[0]
|
||||
case @latest_command
|
||||
when 46, 35 # '.', '#'
|
||||
render_div(line, index)
|
||||
when 37 # '%'
|
||||
render_tag(line, index)
|
||||
when 47 # '/'
|
||||
render_comment(line)
|
||||
when 61 # '='
|
||||
push_script(line[1..-1], false, index)
|
||||
when 126 # '~'
|
||||
push_script(line[1..-1], true, index)
|
||||
when 45 # '-'
|
||||
sub_line = line[1..-1]
|
||||
unless sub_line[0] == 35 # '#'
|
||||
push_silent(sub_line, index)
|
||||
else
|
||||
@latest_command = 35
|
||||
end
|
||||
else
|
||||
push_text line.strip
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
return count, line
|
||||
end
|
||||
|
||||
def handle_multiline(count, line)
|
||||
# Deals with all the logic of figuring out whether a given line is
|
||||
# the beginning, continuation, or end of a multiline sequence. Like
|
||||
# process_line, <tt>count</tt> represents the tabulation, not line
|
||||
# number.
|
||||
#
|
||||
# This returns whether or not the line should be
|
||||
# rendered normally.
|
||||
def handle_multiline(count, line, index)
|
||||
# Multilines are denoting by ending with a `|` (124)
|
||||
if (line[-1] == MULTILINE_CHAR_VALUE) && @multiline_buffer
|
||||
if line && (line[-1] == MULTILINE_CHAR_VALUE) && @multiline_buffer
|
||||
# A multiline string is active, and is being continued
|
||||
@multiline_buffer += line[0...-1]
|
||||
supress_render = true
|
||||
elsif (line[-1] == MULTILINE_CHAR_VALUE) && (MULTILINE_STARTERS.include? line[0])
|
||||
suppress_render = true
|
||||
elsif line && (line[-1] == MULTILINE_CHAR_VALUE) && (MULTILINE_STARTERS.include? line[0])
|
||||
# A multiline string has just been activated, start adding the lines
|
||||
@multiline_buffer = line[0...-1]
|
||||
@multiline_count = count
|
||||
supress_render = true
|
||||
@multiline_index = index
|
||||
suppress_render = true
|
||||
elsif @multiline_buffer
|
||||
# A multiline string has just ended, make line into the result
|
||||
process_line(@multiline_count, @multiline_buffer)
|
||||
process_line(@multiline_count, @multiline_buffer, @multiline_index)
|
||||
@multiline_buffer = nil
|
||||
supress_render = false
|
||||
suppress_render = false
|
||||
end
|
||||
|
||||
return suppress_render
|
||||
end
|
||||
|
||||
# Takes <tt>@precompiled</tt>, a string buffer of Ruby code, and
|
||||
# evaluates it in the context of <tt>@scope_object</tt>, after preparing
|
||||
# <tt>@scope_object</tt>. The code in <tt>@precompiled</tt> populates
|
||||
# <tt>@buffer</tt> with the compiled XHTML code.
|
||||
def compile
|
||||
# Set the local variables pointing to the buffer
|
||||
buffer = @buffer
|
||||
@scope_object.instance_eval do
|
||||
@haml_stack ||= Array.new
|
||||
@haml_stack.push(buffer)
|
||||
self.class.instance_eval { include Haml::Helpers }
|
||||
|
||||
class << self
|
||||
attr :haml_lineno
|
||||
end
|
||||
end
|
||||
|
||||
return supress_render, line, count
|
||||
end
|
||||
|
||||
def add(line)
|
||||
return if line.nil?
|
||||
line.to_s.each_line do |me|
|
||||
@result << tabs(@to_close_queue.size) << me.chomp << "\n"
|
||||
@precompiled = <<END
|
||||
_hamlout = @haml_stack[-1]
|
||||
_erbout = _hamlout.buffer
|
||||
#{@precompiled}
|
||||
END
|
||||
|
||||
# Evaluate the buffer in the context of the scope object
|
||||
begin
|
||||
@scope_object.instance_eval @precompiled
|
||||
rescue Exception => e
|
||||
filename = "(haml)"
|
||||
if @scope_object.methods.include? "haml_filename"
|
||||
# For some reason that I can't figure out,
|
||||
# @scope_object.methods.include? "haml_filename" && @scope_object.haml_filename
|
||||
# is false when it shouldn't be. Nested if statements work, though.
|
||||
|
||||
if @scope_object.haml_filename
|
||||
filename = "#{@scope_object.haml_filename}.haml"
|
||||
end
|
||||
end
|
||||
e.backtrace.unshift "#{filename}:#{@scope_object.haml_lineno}"
|
||||
raise e
|
||||
end
|
||||
|
||||
# Get rid of the current buffer
|
||||
@scope_object.instance_eval do
|
||||
@haml_stack.pop
|
||||
end
|
||||
end
|
||||
|
||||
def build_attributes(attributes = {})
|
||||
result = attributes.collect { |a,v|
|
||||
unless v.nil?
|
||||
first_quote_type = v.to_s.scan(/['"]/).first
|
||||
quote_type = (first_quote_type == "'") ? '"' : "'"
|
||||
"#{a.to_s}=#{quote_type}#{v.to_s}#{quote_type}"
|
||||
end
|
||||
}
|
||||
result = result.compact.join(' ')
|
||||
(attributes.empty? ? String.new : String.new(' ')) + result
|
||||
end
|
||||
|
||||
def open_tag(name, attributes = {})
|
||||
add "<#{name.to_s}#{build_attributes(attributes)}>"
|
||||
@to_close_queue.push name
|
||||
end
|
||||
|
||||
def close_tag
|
||||
add "</#{@to_close_queue.pop}>"
|
||||
end
|
||||
|
||||
def one_line_tag(name, value, attributes = {})
|
||||
add "<#{name.to_s}#{build_attributes(attributes)}>#{value}</#{name.to_s}>"
|
||||
end
|
||||
|
||||
def one_liner?(value)
|
||||
value.length <= ONE_LINER_LENGTH && value.scan(/\n/).empty?
|
||||
end
|
||||
|
||||
def print_tag(name, value, attributes = {})
|
||||
unless value.empty?
|
||||
if one_liner? value
|
||||
one_line_tag(name, value, attributes)
|
||||
else
|
||||
open_tag(name, attributes)
|
||||
add value
|
||||
close_tag
|
||||
end
|
||||
# Evaluates <tt>text</tt> in the context of <tt>@scope_object</tt>, but
|
||||
# does not output the result.
|
||||
def push_silent(text, index = nil)
|
||||
if index
|
||||
@precompiled << "@haml_lineno = #{index + 1}\n#{text}\n"
|
||||
else
|
||||
open_tag(name, attributes)
|
||||
add value
|
||||
# Not really DRY, but probably faster
|
||||
@precompiled << "#{text}\n"
|
||||
end
|
||||
end
|
||||
|
||||
# Creates single line tags, i.e. <tt><hello /></tt>
|
||||
def atomic_tag(name, attributes = {})
|
||||
add "<#{name.to_s}#{build_attributes(attributes)} />"
|
||||
# Adds <tt>text</tt> to <tt>@buffer</tt> with appropriate tabulation
|
||||
# without parsing it.
|
||||
def push_text(text)
|
||||
@precompiled << "_hamlout.push_text(#{text.dump}, #{@tabulation})\n"
|
||||
end
|
||||
|
||||
def parse_class_and_id(list)
|
||||
attributes = {}
|
||||
list.scan(/([#.])([-a-zA-Z_()]+)/).each do |type, property|
|
||||
case type
|
||||
when '.'
|
||||
attributes[:class] = property
|
||||
when '#'
|
||||
attributes[:id] = property
|
||||
end
|
||||
end
|
||||
attributes
|
||||
end
|
||||
|
||||
def render_tag(line)
|
||||
line.scan(/[%]([-_a-z1-9]+)([-_a-z\.\#]*)(\{.*\})?(\[.*\])?([=\/\~]?)?(.*)?/).each do |tag_name, attributes, attributes_hash, object_ref, action, value|
|
||||
attributes = parse_class_and_id(attributes.to_s)
|
||||
|
||||
#SimplyHelpful style logic with the [@model] helper
|
||||
if object_ref && (object_ref = template_eval(object_ref).first)
|
||||
class_name = object_ref.class.to_s.underscore
|
||||
attributes.merge!(:id => "#{class_name}_#{object_ref.id}", :class => class_name)
|
||||
end
|
||||
|
||||
unless (attributes_hash.nil? || attributes_hash.empty?)
|
||||
# Determine whether to eval the attributes hash in the context of a template
|
||||
add_attributes = template_eval(attributes_hash)
|
||||
attributes.merge!(add_attributes)
|
||||
end
|
||||
|
||||
case action
|
||||
when '/'
|
||||
atomic_tag(tag_name, attributes)
|
||||
when '=', '~'
|
||||
value = template_eval(value)
|
||||
value = find_and_flatten(value) if action == '~'
|
||||
print_tag(tag_name, value.to_s, attributes)
|
||||
else
|
||||
print_tag(tag_name, value.to_s.strip, attributes)
|
||||
end
|
||||
# Causes <tt>text</tt> to be evaluated in the context of
|
||||
# <tt>@scope_object</tt> and the result to be added to <tt>@buffer</tt>.
|
||||
#
|
||||
# If <tt>flattened</tt> is true, Haml::Helpers#find_and_flatten is run on
|
||||
# the result before it is added to <tt>@buffer</tt>
|
||||
def push_script(text, flattened, index)
|
||||
unless @suppress_eval
|
||||
push_silent("haml_temp = #{text}", index)
|
||||
@precompiled << "haml_temp = _hamlout.push_script(haml_temp, #{@tabulation}, #{flattened})\n"
|
||||
end
|
||||
end
|
||||
|
||||
def render_div(line)
|
||||
render_tag('%div' + line)
|
||||
# Closes the most recent item in <tt>@to_close_stack</tt>.
|
||||
def close
|
||||
tag = @to_close_stack.pop
|
||||
if tag == '_haml_end_block'
|
||||
close_block
|
||||
else
|
||||
close_tag tag
|
||||
end
|
||||
end
|
||||
|
||||
def render_comment(line)
|
||||
add "<!-- #{line[1..line.length].strip} -->"
|
||||
# Puts a line in <tt>@precompiled</tt> that will add the closing tag of
|
||||
# the most recently opened tag.
|
||||
def close_tag(tag)
|
||||
@tabulation -= 1
|
||||
@precompiled << "_hamlout.close_tag(#{tag.dump}, #{@tabulation})\n"
|
||||
end
|
||||
|
||||
def template_eval(args)
|
||||
!@suppress_eval ? @scope_object.instance_eval(args) : ""
|
||||
# Closes a Ruby block.
|
||||
def close_block
|
||||
push_silent "end"
|
||||
end
|
||||
|
||||
# Parses a line that will render as an XHTML tag, and adds the code that will
|
||||
# render that tag to <tt>@precompiled</tt>.
|
||||
def render_tag(line, index)
|
||||
line.scan(/[%]([-_a-z1-9]+)([-_a-z\.\#]*)(\{.*\})?(\[.*\])?([=\/\~]?)?(.*)?/) do |tag_name, attributes, attributes_hash, object_ref, action, value|
|
||||
value = value.to_s
|
||||
|
||||
case action
|
||||
when '/'
|
||||
atomic = true
|
||||
when '=', '~'
|
||||
flattened = (action == '~')
|
||||
parse = true
|
||||
else
|
||||
value = value.strip
|
||||
end
|
||||
|
||||
value_exists = !value.empty?
|
||||
attributes_hash = "nil" unless attributes_hash
|
||||
object_ref = "nil" unless object_ref
|
||||
|
||||
@precompiled << "_hamlout.open_tag(#{tag_name.inspect}, #{@tabulation}, #{atomic.inspect}, #{value_exists.inspect}, #{attributes.inspect}, #{attributes_hash}, #{object_ref})\n"
|
||||
|
||||
unless atomic
|
||||
@to_close_stack.push tag_name
|
||||
@tabulation += 1
|
||||
|
||||
if value_exists
|
||||
if parse
|
||||
push_script(value, flattened, index)
|
||||
else
|
||||
push_text(value)
|
||||
end
|
||||
close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Renders a line that creates an XHTML tag and has an implicit div because of
|
||||
# <tt>.</tt> or <tt>#</tt>.
|
||||
def render_div(line, index)
|
||||
render_tag('%div' + line, index)
|
||||
end
|
||||
|
||||
# Renders an XHTML comment.
|
||||
def render_comment(line)
|
||||
push_text "<!-- #{line[1..line.length].strip} -->"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
module Haml
|
||||
# This module contains various helpful methods to make it easier to do
|
||||
# various tasks. Haml::Helpers is automatically included in the context
|
||||
# that a HAML template is parsed in, so all these methods are at your
|
||||
# disposal from within the template.
|
||||
module Helpers
|
||||
# Flatten will take any string, find all the endlines (via \n)
|
||||
# and convert them to html entities for endlines.
|
||||
# Takes any string, finds all the endlines and converts them to
|
||||
# html entities for endlines so they'll render correctly in
|
||||
# whitespace-sensitive tags.
|
||||
def flatten(input)
|
||||
input.gsub(/\n/, '
').gsub(/\r/, '')
|
||||
end
|
||||
|
||||
# Isolates the whitespace-sensitive tags in the string and uses flatten
|
||||
# to convert any endlines inside them into html entities.
|
||||
def find_and_flatten(input)
|
||||
input.scan(/<(textarea|code|pre)[^>]*>(.*?)<\/\1>/im).each do |thing|
|
||||
input = input.gsub(thing[1], flatten(thing[1]))
|
||||
|
@ -13,19 +20,15 @@ module Haml
|
|||
input
|
||||
end
|
||||
|
||||
def tabs(count)
|
||||
' ' * count
|
||||
end
|
||||
|
||||
# Counts the tabulation of a line. Mostly for internal use.
|
||||
def count_soft_tabs(line)
|
||||
line.index(/[^ ]/) ? [line.index(/[^ ]/)/2, line.strip] : []
|
||||
end
|
||||
|
||||
# List_for is a really nifty little helper that helps
|
||||
# cleanup your code. Basically, give it an array of
|
||||
# objects, and then pass in a block that tells how
|
||||
# what to put out, and you will get each block item
|
||||
# in rows of <li> tags.
|
||||
# Takes an array and a block and iterates the array,
|
||||
# yielding each element to the block and putting the
|
||||
# result into <tt>li</tt> elements, creating a list
|
||||
# of the results of the block. For example:
|
||||
#
|
||||
# For instance:
|
||||
# list_of([['hello'], ['yall']]) { |i| i[0] }
|
||||
|
@ -36,7 +39,7 @@ module Haml
|
|||
# <li>hello</li>
|
||||
# <li>yall</li>
|
||||
#
|
||||
def list_of(array)
|
||||
def list_of(array) # :yields: item
|
||||
(array.collect { |i| "<li>#{yield(i)}</li>" }).join("\n")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
require File.dirname(__FILE__) + '/engine'
|
||||
require 'active_support'
|
||||
require 'action_view'
|
||||
|
||||
module Haml
|
||||
class Template
|
||||
|
@ -29,4 +31,16 @@ module Haml
|
|||
Haml::Engine.new(template, :scope_object => @view).to_html
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ActionView
|
||||
class Base
|
||||
attr :haml_filename, true
|
||||
|
||||
alias haml_old_render_file render_file
|
||||
def render_file(template_path, use_full_path = true, local_assigns = {})
|
||||
@haml_filename = File.basename(template_path)
|
||||
haml_old_render_file(template_path, use_full_path, local_assigns)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,14 +1,58 @@
|
|||
require 'rubygems'
|
||||
require 'action_view'
|
||||
|
||||
require File.dirname(__FILE__) + '/../lib/haml/template'
|
||||
require 'rubygems'
|
||||
require 'active_support'
|
||||
require 'action_view'
|
||||
require 'benchmark'
|
||||
require 'stringio'
|
||||
|
||||
ActionView::Base.register_template_handler("haml", Haml::Template)
|
||||
@base = ActionView::Base.new(File.dirname(__FILE__))
|
||||
|
||||
RUNS = (ARGV[0] || 100).to_i
|
||||
|
||||
Benchmark.bm do |b|
|
||||
b.report("haml: ") { RUNS.times { @base.render "templates/standard" } }
|
||||
b.report("erb: ") { RUNS.times { @base.render "rhtml/standard" } }
|
||||
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. If <tt>template_name</tt> is specified,
|
||||
# looks for a haml template in ./templates and an rhtml template in
|
||||
# ./rhtml with the name <tt>template_name</tt>. Otherwise, uses
|
||||
# <tt>haml_template</tt> and <tt>rhtml_template</tt> as the location of
|
||||
# the templates.
|
||||
#
|
||||
# Returns the results of the benchmarking as a string.
|
||||
#
|
||||
# :call-seq:
|
||||
# benchmark(runs = 100, template_name = 'standard')
|
||||
# benchmark(runs = 100, haml_template, rhtml_template)
|
||||
#
|
||||
def benchmark(runs = 100, template_name = 'standard', other_template = nil)
|
||||
if other_template.nil?
|
||||
haml_template = "templates/#{template_name}"
|
||||
rhtml_template = "rhtml/#{template_name}"
|
||||
else
|
||||
haml_template = template_name
|
||||
rhtml_template = other_template
|
||||
end
|
||||
|
||||
old_stdout = $stdout
|
||||
$stdout = StringIO.new
|
||||
|
||||
Benchmark.bmbm do |b|
|
||||
b.report("haml:") { runs.times { @base.render haml_template } }
|
||||
b.report("erb:") { runs.times { @base.render rhtml_template } }
|
||||
end
|
||||
|
||||
$stdout.pos = 0
|
||||
to_return = $stdout.read
|
||||
$stdout = old_stdout
|
||||
|
||||
to_return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../lib/haml/engine'
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../lib/haml/helpers'
|
||||
|
||||
|
@ -24,11 +26,6 @@ class HelperTest < Test::Unit::TestCase
|
|||
"<pre>Two
lines</pre>\n<pre>a
b
c</pre>")
|
||||
end
|
||||
|
||||
def test_tabs_should_render_correctly
|
||||
assert_equal(" ", tabs(1))
|
||||
assert_equal(" ", tabs(5))
|
||||
end
|
||||
|
||||
def test_list_of_should_render_correctly
|
||||
assert_equal("<li>1</li>\n<li>2</li>", (list_of([1, 2]) { |i| i.to_s}))
|
||||
assert_equal("<li>1</li>", (list_of([[1]]) { |i| i.first}))
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
require File.dirname(__FILE__) + '/../lib/haml/template'
|
||||
require 'rubygems'
|
||||
require 'active_support'
|
||||
require 'action_view'
|
||||
require 'profiler'
|
||||
require 'stringio'
|
||||
|
||||
module Haml
|
||||
# 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__), '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')
|
||||
# Runs the profiler, collects information
|
||||
Profiler__::start_profile
|
||||
runs.times { @base.render template_name }
|
||||
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
|
||||
return result
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1 +1,25 @@
|
|||
&&&&&&&&&&&
|
||||
<div>
|
||||
<p class='title'>Title</p>
|
||||
<p class='text'>
|
||||
Woah this is really crazy
|
||||
I mean wow,
|
||||
man.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class='title'>Title</p>
|
||||
<p class='text'>
|
||||
Woah this is really crazy
|
||||
I mean wow,
|
||||
man.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class='title'>Title</p>
|
||||
<p class='text'>
|
||||
Woah this is really crazy
|
||||
I mean wow,
|
||||
man.
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
<h1>Hello</h1>
|
||||
<div>World</div>
|
||||
</div>
|
||||
<div class='article full' id='article_1'>boo</div>
|
||||
<div class='article full' id='article_1'>boo</div>
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<div>
|
||||
<h1>I can count!</h1>
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
<h1>I know my ABCs!</h1>
|
||||
<ul>
|
||||
<li>a</li>
|
||||
<li>b</li>
|
||||
<li>c</li>
|
||||
<li>d</li>
|
||||
<li>e</li>
|
||||
<li>f</li>
|
||||
<li>g</li>
|
||||
<li>h</li>
|
||||
<li>i</li>
|
||||
<li>j</li>
|
||||
<li>k</li>
|
||||
<li>l</li>
|
||||
<li>m</li>
|
||||
<li>n</li>
|
||||
<li>o</li>
|
||||
<li>p</li>
|
||||
<li>q</li>
|
||||
<li>r</li>
|
||||
<li>s</li>
|
||||
<li>t</li>
|
||||
<li>u</li>
|
||||
<li>v</li>
|
||||
<li>w</li>
|
||||
<li>x</li>
|
||||
<li>y</li>
|
||||
<li>z</li>
|
||||
</ul>
|
||||
<h1>I can catch errors!</h1>
|
||||
Oh no! "uninitialized constant Foo" happened!
|
||||
<p>
|
||||
"false" is:
|
||||
false
|
||||
</p>
|
||||
</div>
|
|
@ -2,7 +2,7 @@
|
|||
<html xml-lang='en-US'>
|
||||
<head>
|
||||
<title>Hampton Catlin Is Totally Awesome</title>
|
||||
<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
|
||||
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
|
||||
</head>
|
||||
<body>
|
||||
<!-- You're In my house now! -->
|
||||
|
@ -19,6 +19,17 @@
|
|||
PipesIgnored|PipesIgnored|PipesIgnored|
|
||||
1|2|3
|
||||
</p>
|
||||
<div class='silent'>
|
||||
this shouldn't evaluate but now it should!
|
||||
</div>
|
||||
<ul class='really cool'>
|
||||
<li>a</li>
|
||||
<li>b</li>
|
||||
<li>c</li>
|
||||
<li>d</li>
|
||||
<li>e</li>
|
||||
<li>f</li>
|
||||
</ul>
|
||||
<div class='of_divs_with_underscore' id='combo'>with this text</div>
|
||||
<div class='footer'>
|
||||
<strong class='shout'>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xml-lang='en-US'>
|
||||
<head>
|
||||
<title><%= "Hampton Catlin Is Totally Awesome" %></title>
|
||||
<title>Hampton Catlin Is Totally Awesome</title>
|
||||
<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
|
||||
</head>
|
||||
<body>
|
||||
|
@ -11,8 +11,32 @@
|
|||
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 %>
|
||||
</div>
|
||||
<div id='body'><%= " Quotes should be loved! Just like people!" %></div>
|
||||
Wow.
|
||||
<p>
|
||||
<%= "Holy cow " +
|
||||
"multiline " +
|
||||
"tags! " +
|
||||
"A pipe (|) even!" %>
|
||||
<%= [1, 2, 3].collect { |n| "PipesIgnored|" } %>
|
||||
<%= [1, 2, 3].collect { |n|
|
||||
n.to_s
|
||||
}.join("|") %>
|
||||
</p>
|
||||
<div class='silent'>
|
||||
<% foo = String.new
|
||||
foo << "this"
|
||||
foo << " shouldn't"
|
||||
foo << " evaluate" %>
|
||||
<%= foo + "but now it should!" %>
|
||||
<%# Woah crap a comment! %>
|
||||
</div>
|
||||
<ul class='really cool'>
|
||||
<% ('a'..'f').each do |a|%>
|
||||
<li><%= a %>
|
||||
<% end %>
|
||||
<div class='of_divs_with_underscore' id='combo'><%= @should_eval = "with this text" %></div>
|
||||
<div class='footer'>
|
||||
<strong class='shout'>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'test/unit'
|
||||
require 'rubygems'
|
||||
require 'active_support'
|
||||
|
@ -7,6 +9,17 @@ require File.dirname(__FILE__) + '/../lib/haml/template'
|
|||
require File.dirname(__FILE__) + '/mocks/article'
|
||||
|
||||
class TemplateTest < Test::Unit::TestCase
|
||||
# These are specific lines of templates that, for one reason or
|
||||
# another, might not be exactly equivalent to the pre-rendered
|
||||
# version.
|
||||
EXCEPTIONS = {
|
||||
'standard' => [
|
||||
# Line 4 has many attributes; because attributes aren't sorted,
|
||||
# this can vary unpredictably.
|
||||
4
|
||||
]
|
||||
}
|
||||
|
||||
def setup
|
||||
ActionView::Base.register_template_handler("haml", Haml::Template)
|
||||
@base = ActionView::Base.new(File.dirname(__FILE__) + "/../test/templates/")
|
||||
|
@ -24,8 +37,15 @@ class TemplateTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def assert_renders_correctly(name)
|
||||
load_result(name).split("\n").zip(@base.render(name).split("\n")).each do |pair|
|
||||
assert_equal(pair.first, pair.last)
|
||||
load_result(name).split("\n").zip(@base.render(name).split("\n")).each_with_index do |pair, line|
|
||||
if (EXCEPTIONS['name'].nil? || EXCEPTIONS['name'].include?(line))
|
||||
if pair.first != pair.last
|
||||
puts "\nWarning: line #{line} of template \"#{name}\" may have rendered incorrectly."
|
||||
end
|
||||
else
|
||||
message = "template: #{name}\nline: #{line}"
|
||||
assert_equal(pair.first, pair.last, message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -34,7 +54,8 @@ class TemplateTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def test_templates_should_render_correctly
|
||||
%w{very_basic standard helpers whitespace_handling original_engine list helpful}.each do |template|
|
||||
%w{very_basic standard helpers whitespace_handling
|
||||
original_engine list helpful silent_script}.each do |template|
|
||||
assert_renders_correctly template
|
||||
end
|
||||
end
|
||||
|
@ -66,4 +87,30 @@ class TemplateTest < Test::Unit::TestCase
|
|||
def test_template_renders_should_eval
|
||||
assert_equal("2\n", render("= 1+1"))
|
||||
end
|
||||
|
||||
def test_exceptions_should_work_correctly
|
||||
template = <<END
|
||||
%p
|
||||
%h1 Hello!
|
||||
= "lots of lines"
|
||||
- raise "Oh no!"
|
||||
%p
|
||||
this is after the exception
|
||||
%strong yes it is!
|
||||
ho ho ho.
|
||||
END
|
||||
@base.haml_filename = "(test)"
|
||||
begin
|
||||
render(template.chomp)
|
||||
rescue Exception => e
|
||||
assert_equal("(test).haml:4", e.backtrace[0])
|
||||
end
|
||||
|
||||
@base.haml_filename = nil
|
||||
begin
|
||||
render(template.chomp)
|
||||
rescue Exception => e
|
||||
assert_equal("(haml):4", e.backtrace[0])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1 +1,10 @@
|
|||
= h("&&&&&&&&&&&") #this is an ActionView Helper... should load
|
||||
= 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
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
%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
|
||||
- Foo.silly
|
||||
- rescue NameError => e
|
||||
= "Oh no! \"#{e}\" happened!"
|
||||
%p
|
||||
"false" is:
|
||||
- if false
|
||||
= "true"
|
||||
- else
|
||||
= "false"
|
|
@ -21,6 +21,16 @@
|
|||
= [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!
|
||||
%ul.really.cool
|
||||
- ('a'..'f').each do |a|
|
||||
%li= a
|
||||
#combo.of_divs_with_underscore= @should_eval = "with this text"
|
||||
.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"
|
||||
|
|
Loading…
Reference in New Issue