Filters: implemented!
git-svn-id: svn://hamptoncatlin.com/haml/trunk@291 7063305b-7217-0410-af8c-cdc13e5119b9
This commit is contained in:
parent
50890f523a
commit
2fa28d63e3
4
TODO
4
TODO
|
@ -6,6 +6,8 @@ Documentation:
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
Exceptions thrown by Sass code should have their own class
|
Exceptions thrown by Sass code should have their own class
|
||||||
|
Exceptions in general should be a lot nicer
|
||||||
Filters for Haml
|
Filters for Haml
|
||||||
Haml and Sass should throw syntax errors rather than blithely parsing ill-formatted documents
|
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
|
||||||
|
|
65
lib/haml.rb
65
lib/haml.rb
|
@ -391,6 +391,59 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
||||||
# probably make it |
|
# probably make it |
|
||||||
# multiline so it doesn't |
|
# multiline so it doesn't |
|
||||||
# look awful. |
|
# 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
|
# === Ruby evaluators
|
||||||
#
|
#
|
||||||
|
@ -615,6 +668,18 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
||||||
# of this type within the attributes will be escaped
|
# of this type within the attributes will be escaped
|
||||||
# (e.g. by replacing them with <tt>'</tt>) if
|
# (e.g. by replacing them with <tt>'</tt>) if
|
||||||
# the character is an apostrophe or a quotation mark.
|
# 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
|
# [<tt>:locals</tt>] The local variables that will be available within the
|
||||||
# template. For instance, if <tt>:locals</tt> is
|
# template. For instance, if <tt>:locals</tt> is
|
||||||
|
|
|
@ -203,22 +203,25 @@ module Haml
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class String # :nodoc
|
unless String.methods.include? 'old_comp'
|
||||||
alias_method :old_comp, :<=>
|
class String # :nodoc
|
||||||
def <=>(other)
|
alias_method :old_comp, :<=>
|
||||||
if other.is_a? NilClass
|
|
||||||
-1
|
def <=>(other)
|
||||||
else
|
if other.is_a? NilClass
|
||||||
old_comp(other)
|
-1
|
||||||
|
else
|
||||||
|
old_comp(other)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class NilClass # :nodoc:
|
||||||
|
include Comparable
|
||||||
|
|
||||||
|
def <=>(other)
|
||||||
|
other.nil? ? 0 : 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class NilClass # :nodoc:
|
|
||||||
include Comparable
|
|
||||||
|
|
||||||
def <=>(other)
|
|
||||||
other.nil? ? 0 : 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
require 'haml/helpers'
|
require 'haml/helpers'
|
||||||
require 'haml/buffer'
|
require 'haml/buffer'
|
||||||
|
require 'haml/filters'
|
||||||
|
|
||||||
module Haml
|
module Haml
|
||||||
# This is the class where all the parsing and processing of the 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.
|
# Designates a non-parsed line.
|
||||||
ESCAPE = ?\\
|
ESCAPE = ?\\
|
||||||
|
|
||||||
|
# Designates a block of filtered text.
|
||||||
|
FILTER = ?:
|
||||||
|
|
||||||
# Designates a non-parsed line. Not actually a character.
|
# Designates a non-parsed line. Not actually a character.
|
||||||
PLAIN_TEXT = -1
|
PLAIN_TEXT = -1
|
||||||
|
|
||||||
|
@ -61,7 +65,8 @@ module Haml
|
||||||
SCRIPT,
|
SCRIPT,
|
||||||
FLAT_SCRIPT,
|
FLAT_SCRIPT,
|
||||||
SILENT_SCRIPT,
|
SILENT_SCRIPT,
|
||||||
ESCAPE
|
ESCAPE,
|
||||||
|
FILTER
|
||||||
]
|
]
|
||||||
|
|
||||||
# The value of the character that designates that a line is part
|
# The value of the character that designates that a line is part
|
||||||
|
@ -99,8 +104,32 @@ module Haml
|
||||||
@options = {
|
@options = {
|
||||||
:suppress_eval => false,
|
:suppress_eval => false,
|
||||||
:attr_wrapper => "'",
|
:attr_wrapper => "'",
|
||||||
:locals => {}
|
:locals => {},
|
||||||
}.merge options
|
: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]
|
@precompiled = @options[:precompiled]
|
||||||
|
|
||||||
@template = template.strip #String
|
@template = template.strip #String
|
||||||
|
@ -155,7 +184,7 @@ module Haml
|
||||||
old_tabs = nil
|
old_tabs = nil
|
||||||
(@template + "\n-#").each_with_index do |line, index|
|
(@template + "\n-#").each_with_index do |line, index|
|
||||||
spaces, tabs = count_soft_tabs(line)
|
spaces, tabs = count_soft_tabs(line)
|
||||||
line = line.strip
|
line.strip!
|
||||||
|
|
||||||
if !line.empty?
|
if !line.empty?
|
||||||
if old_line
|
if old_line
|
||||||
|
@ -181,9 +210,13 @@ module Haml
|
||||||
old_spaces = spaces
|
old_spaces = spaces
|
||||||
old_tabs = tabs
|
old_tabs = tabs
|
||||||
elsif @flat_spaces != -1
|
elsif @flat_spaces != -1
|
||||||
push_flat(old_line, old_spaces)
|
process_indent(old_tabs, old_line) unless old_line.empty?
|
||||||
old_line = ''
|
|
||||||
old_spaces = 0
|
if @flat_spaces != -1
|
||||||
|
push_flat(old_line, old_spaces)
|
||||||
|
old_line = ''
|
||||||
|
old_spaces = 0
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -231,6 +264,9 @@ module Haml
|
||||||
push_and_tabulate([:script])
|
push_and_tabulate([:script])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
when FILTER
|
||||||
|
name = line[1..-1].downcase
|
||||||
|
start_filtered(options[:filters][name] || name)
|
||||||
when DOCTYPE
|
when DOCTYPE
|
||||||
if line[0...3] == '!!!'
|
if line[0...3] == '!!!'
|
||||||
render_doctype(line)
|
render_doctype(line)
|
||||||
|
@ -358,7 +394,12 @@ module Haml
|
||||||
# Adds +text+ to <tt>@buffer</tt> while flattening text.
|
# Adds +text+ to <tt>@buffer</tt> while flattening text.
|
||||||
def push_flat(text, spaces)
|
def push_flat(text, spaces)
|
||||||
tabulation = spaces - @flat_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
|
end
|
||||||
|
|
||||||
# Causes <tt>text</tt> to be evaluated in the context of
|
# Causes <tt>text</tt> to be evaluated in the context of
|
||||||
|
@ -402,6 +443,8 @@ module Haml
|
||||||
close_flat value
|
close_flat value
|
||||||
when :loud
|
when :loud
|
||||||
close_loud value
|
close_loud value
|
||||||
|
when :filtered
|
||||||
|
close_filtered value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -444,6 +487,23 @@ module Haml
|
||||||
@template_tabs -= 1
|
@template_tabs -= 1
|
||||||
end
|
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
|
# Parses a line that will render as an XHTML tag, and adds the code that will
|
||||||
# render that tag to <tt>@precompiled</tt>.
|
# render that tag to <tt>@precompiled</tt>.
|
||||||
def render_tag(line, index)
|
def render_tag(line, index)
|
||||||
|
@ -541,6 +601,13 @@ module Haml
|
||||||
@flat_spaces = @template_tabs * 2
|
@flat_spaces = @template_tabs * 2
|
||||||
end
|
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.
|
# Counts the tabulation of a line.
|
||||||
def count_soft_tabs(line)
|
def count_soft_tabs(line)
|
||||||
spaces = line.index(/[^ ]/)
|
spaces = line.index(/[^ ]/)
|
||||||
|
|
|
@ -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:
|
|
@ -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?
|
||||||
|
|
|
@ -12,7 +12,8 @@ require File.dirname(__FILE__) + '/mocks/article'
|
||||||
class TemplateTest < Test::Unit::TestCase
|
class TemplateTest < Test::Unit::TestCase
|
||||||
@@templates = %w{ very_basic standard helpers
|
@@templates = %w{ very_basic standard helpers
|
||||||
whitespace_handling original_engine list helpful
|
whitespace_handling original_engine list helpful
|
||||||
silent_script tag_parsing just_stuff partials }
|
silent_script tag_parsing just_stuff partials
|
||||||
|
filters }
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
ActionView::Base.register_template_handler("haml", Haml::Template)
|
ActionView::Base.register_template_handler("haml", Haml::Template)
|
||||||
|
|
|
@ -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?"
|
Loading…
Reference in New Issue