Filters: implemented!

git-svn-id: svn://hamptoncatlin.com/haml/trunk@291 7063305b-7217-0410-af8c-cdc13e5119b9
This commit is contained in:
nex3 2007-01-19 17:03:47 +00:00
parent 50890f523a
commit 2fa28d63e3
8 changed files with 330 additions and 25 deletions

4
TODO
View File

@ -6,6 +6,8 @@ Documentation:
Features:
Exceptions thrown by Sass code should have their own class
Exceptions in general should be a lot nicer
Filters for Haml
Haml and Sass should throw syntax errors rather than blithely parsing ill-formatted documents
There should be a way to represent options in-document
There should be a way to represent options in-document
Haml and Sass executables should return meaningful exit codes

View File

@ -391,6 +391,59 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
# 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
#
# <p>
# <h1>Textile</h1>
#
# <p>Hello, <em>World</em></p>
# </p>
#
# Haml has the following filters defined:
#
# [plain] Does not parse the filtered text.
#
# [ruby] Parses the filtered text with the normal Ruby interpreter.
# All output sent to <tt>$stdout</tt>, like with +puts+,
# is output into the Haml document.
# Not available if the <tt>suppress_eval</tt> option is set to true.
#
# [erb] Parses the filtered text with ERB, like an RHTML template.
# Not available if the <tt>suppress_eval</tt> 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
#
@ -615,6 +668,18 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
# of this type within the attributes will be escaped
# (e.g. by replacing them with <tt>&apos;</tt>) if
# the character is an apostrophe or a quotation mark.
#
# [<tt>:filters</tt>] 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.
# * An optional <tt>haml_scope_object=</tt> method
# that takes a reference to the object
# that Ruby code in Haml is evaluated within.
#
# [<tt>:locals</tt>] The local variables that will be available within the
# template. For instance, if <tt>:locals</tt> is

View File

@ -203,22 +203,25 @@ module Haml
end
end
class String # :nodoc
alias_method :old_comp, :<=>
def <=>(other)
if other.is_a? NilClass
-1
else
old_comp(other)
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
class NilClass # :nodoc:
include Comparable
def <=>(other)
other.nil? ? 0 : 1
end
end

View File

@ -1,5 +1,6 @@
require 'haml/helpers'
require 'haml/buffer'
require 'haml/filters'
module Haml
# This is the class where all the parsing and processing of the Haml
@ -47,6 +48,9 @@ module Haml
# 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
@ -61,7 +65,8 @@ module Haml
SCRIPT,
FLAT_SCRIPT,
SILENT_SCRIPT,
ESCAPE
ESCAPE,
FILTER
]
# The value of the character that designates that a line is part
@ -99,8 +104,32 @@ module Haml
@options = {
:suppress_eval => false,
:attr_wrapper => "'",
:locals => {}
}.merge options
:locals => {},
:filters => {
'sass' => Sass::Engine,
'plain' => Haml::Filters::Plain
}
}
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
})
elsif !NOT_LOADED.include? 'bluecloth'
@options[:filters]['markdown'] = Haml::Filters::Markdown
end
@options.merge! options
@precompiled = @options[:precompiled]
@template = template.strip #String
@ -155,7 +184,7 @@ module Haml
old_tabs = nil
(@template + "\n-#").each_with_index do |line, index|
spaces, tabs = count_soft_tabs(line)
line = line.strip
line.strip!
if !line.empty?
if old_line
@ -181,9 +210,13 @@ module Haml
old_spaces = spaces
old_tabs = tabs
elsif @flat_spaces != -1
push_flat(old_line, old_spaces)
old_line = ''
old_spaces = 0
process_indent(old_tabs, old_line) unless old_line.empty?
if @flat_spaces != -1
push_flat(old_line, old_spaces)
old_line = ''
old_spaces = 0
end
end
end
@ -231,6 +264,9 @@ module Haml
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)
@ -358,7 +394,12 @@ module Haml
# Adds +text+ to <tt>@buffer</tt> while flattening text.
def push_flat(text, spaces)
tabulation = spaces - @flat_spaces
@precompiled << "_hamlout.push_text(#{text.dump}, #{tabulation > -1 ? tabulation : 0}, true)\n"
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 <tt>text</tt> to be evaluated in the context of
@ -402,6 +443,8 @@ module Haml
close_flat value
when :loud
close_loud value
when :filtered
close_filtered value
end
end
@ -444,6 +487,23 @@ module Haml
@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'
push_text("You must have the RedCloth gem installed to use #{filter}")
else
push_text("Filter \"#{filter}\" is not defined!")
end
else
push_text(filter.new(@filter_buffer).render.rstrip.gsub("\n", "\n#{' ' * @output_tabs}"))
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 <tt>@precompiled</tt>.
def render_tag(line, index)
@ -541,6 +601,13 @@ module Haml
@flat_spaces = @template_tabs * 2
end
# Starts a filtered block.
def start_filtered(filter)
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(/[^ ]/)

77
lib/haml/filters.rb Normal file
View File

@ -0,0 +1,77 @@
# This file contains redefinitions of and wrappers around various text
# filters so they can be used as Haml filters.
# :stopdoc:
require 'erb'
require 'sass/engine'
require 'stringio'
volatile_requires = ['rubygems', 'redcloth', 'bluecloth']
NOT_LOADED = []
volatile_requires.each do |file|
begin
require file
rescue LoadError
NOT_LOADED.push file
end
end
class ERB; alias_method :render, :result; end
unless NOT_LOADED.include? 'bluecloth'
class BlueCloth; alias_method :render, :to_html; end
end
module Haml
module Filters
class Plain
def initialize(text)
@text = text
end
def render
@text
end
end
class Ruby
def initialize(text)
@text = text
end
def render
old_stdout = $stdout
$stdout = StringIO.new
Object.new.instance_eval(@text)
old_stdout, $stdout = $stdout, old_stdout
old_stdout.pos = 0
old_stdout.read
end
end
unless NOT_LOADED.include? 'redcloth'
class ::RedCloth; alias_method :render, :to_html; end
# Uses RedCloth to provide only Textile (not Markdown) parsing
class Textile < RedCloth
def render
self.to_html(:textile)
end
end
unless defined?(BlueCloth)
# Uses RedCloth to provide only Markdown (not Textile) parsing
class Markdown < RedCloth
def render
self.to_html(:markdown)
end
end
else
Markdown = BlueCloth
end
end
end
end
# :startdoc:

View File

@ -0,0 +1,48 @@
<style>
p { border-style: dotted; border-width: 10px; border-color: #ff00ff; }
h1 { font-weight: normal; }
</style>
<h1>Foo</h1>
<pre><code>This is preformatted!
Look at that!
Wowie-zowie!</code></pre>
<p><strong>boldilicious!</strong></p>
This
Is
Plain
Text
%strong right?
a
b
c
d
e
f
g
h
i
j
<ul>
<li>Foo</li>
<li>Bar</li>
<li>BAZ!</li>
</ul>
Text!
Hello, World!
How are you doing today?

View File

@ -12,7 +12,8 @@ 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 partials }
silent_script tag_parsing just_stuff partials
filters }
def setup
ActionView::Base.register_template_handler("haml", Haml::Template)

View File

@ -0,0 +1,42 @@
%style
:sass
p
:border
:style dotted
:width 10px
:color #ff00ff
h1
:font-weight normal
:redcloth
Foo
===
This is preformatted!
Look at that!
Wowie-zowie!
*boldilicious!*
: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?"