mirror of
https://github.com/haml/haml.git
synced 2022-11-09 12:33:31 -05:00
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
8 changed files with 330 additions and 25 deletions
4
TODO
4
TODO
|
@ -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
|
||||
|
|
65
lib/haml.rb
65
lib/haml.rb
|
@ -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>'</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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
77
lib/haml/filters.rb
Normal 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:
|
48
test/haml/results/filters.xhtml
Normal file
48
test/haml/results/filters.xhtml
Normal 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?
|
||||
|
|
@ -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)
|
||||
|
|
42
test/haml/templates/filters.haml
Normal file
42
test/haml/templates/filters.haml
Normal 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?"
|
Loading…
Reference in a new issue