parent
4e22c87074
commit
dcfc38eb09
@ -1,39 +0,0 @@
|
||||
*0.2.3*
|
||||
|
||||
* (12 Nov 2005) fixed bug with old behavior of #blank? [Michael Schuerig]
|
||||
* (12 Nov 2005) updated tests to use ActiveRecord Schema
|
||||
|
||||
*0.2.2*
|
||||
|
||||
* (3 Nov 2005) added documentation note to #acts_as_versioned [Martin Jul]
|
||||
|
||||
*0.2.1*
|
||||
|
||||
* (6 Oct 2005) renamed dirty? to changed? to keep it uniform. it was aliased to keep it backwards compatible.
|
||||
|
||||
*0.2*
|
||||
|
||||
* (6 Oct 2005) added find_versions and find_version class methods.
|
||||
|
||||
* (6 Oct 2005) removed transaction from create_versioned_table().
|
||||
this way you can specify your own transaction around a group of operations.
|
||||
|
||||
* (30 Sep 2005) fixed bug where find_versions() would order by 'version' twice. (found by Joe Clark)
|
||||
|
||||
* (26 Sep 2005) added :sequence_name option to acts_as_versioned to set the sequence name on the versioned model
|
||||
|
||||
*0.1.3* (18 Sep 2005)
|
||||
|
||||
* First RubyForge release
|
||||
|
||||
*0.1.2*
|
||||
|
||||
* check if module is already included when acts_as_versioned is called
|
||||
|
||||
*0.1.1*
|
||||
|
||||
* Adding tests and rdocs
|
||||
|
||||
*0.1*
|
||||
|
||||
* Initial transfer from Rails ticket: http://dev.rubyonrails.com/ticket/1974
|
@ -1,3 +0,0 @@
|
||||
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'acts_as_versioned')
|
||||
|
@ -1,401 +0,0 @@
|
||||
# Copyright (c) 2005 Rick Olson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
module ActiveRecord #:nodoc:
|
||||
module Acts #:nodoc:
|
||||
# Specify this act if you want to save a copy of the row in a versioned table. This assumes there is a
|
||||
# versioned table ready and that your model has a version field. This works with optimisic locking if the lock_version
|
||||
# column is present as well.
|
||||
#
|
||||
# The class for the versioned model is derived the first time it is seen. Therefore, if you change your database schema you have to restart
|
||||
# your container for the changes to be reflected. In development mode this usually means restarting WEBrick.
|
||||
#
|
||||
# class Page < ActiveRecord::Base
|
||||
# # assumes pages_versions table
|
||||
# acts_as_versioned
|
||||
# end
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# page = Page.create(:title => 'hello world!')
|
||||
# page.version # => 1
|
||||
#
|
||||
# page.title = 'hello world'
|
||||
# page.save
|
||||
# page.version # => 2
|
||||
# page.versions.size # => 2
|
||||
#
|
||||
# page.revert_to(1) # using version number
|
||||
# page.title # => 'hello world!'
|
||||
#
|
||||
# page.revert_to(page.versions.last) # using versioned instance
|
||||
# page.title # => 'hello world'
|
||||
#
|
||||
# See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options
|
||||
module Versioned
|
||||
def self.included(base) # :nodoc:
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# == Configuration options
|
||||
#
|
||||
# * <tt>class_name</tt> - versioned model class name (default: PageVersion in the above example)
|
||||
# * <tt>table_name</tt> - versioned model table name (default: page_versions in the above example)
|
||||
# * <tt>foreign_key</tt> - foreign key used to relate the versioned model to the original model (default: page_id in the above example)
|
||||
# * <tt>inheritance_column</tt> - name of the column to save the model's inheritance_column value for STI. (default: versioned_type)
|
||||
# * <tt>version_column</tt> - name of the column in the model that keeps the version number (default: version)
|
||||
# * <tt>sequence_name</tt> - name of the custom sequence to be used by the versioned model.
|
||||
# * <tt>limit</tt> - number of revisions to keep, defaults to unlimited
|
||||
# * <tt>if</tt> - symbol of method to check before saving a new version. If this method returns false, a new version is not saved.
|
||||
# For finer control, pass either a Proc or modify Model#version_condition_met?
|
||||
#
|
||||
# acts_as_versioned :if => Proc.new { |auction| !auction.expired? }
|
||||
#
|
||||
# or...
|
||||
#
|
||||
# class Auction
|
||||
# def version_condition_met? # totally bypasses the <tt>:if</tt> option
|
||||
# !expired?
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# * <tt>if_changed</tt> - Simple way of specifying attributes that are required to be changed before saving a model. This takes
|
||||
# either a symbol or array of symbols.
|
||||
#
|
||||
# == Database Schema
|
||||
#
|
||||
# The model that you're versioning needs to have a 'version' attribute. The model is versioned
|
||||
# into a table called #{model}_versions where the model name is singlular. The _versions table should
|
||||
# contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field.
|
||||
#
|
||||
# A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance,
|
||||
# then that field is reflected in the versioned model as 'versioned_type' by default.
|
||||
#
|
||||
# Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table
|
||||
# method, perfect for a migration. It will also create the version column if the main model does not already have it.
|
||||
#
|
||||
# class AddVersions < ActiveRecord::Migration
|
||||
# def self.up
|
||||
# # create_versioned_table takes the same options hash
|
||||
# # that create_table does
|
||||
# Post.create_versioned_table
|
||||
# end
|
||||
#
|
||||
# def self.down
|
||||
# Post.drop_versioned_table
|
||||
# end
|
||||
# end
|
||||
def acts_as_versioned(options = {})
|
||||
# don't allow multiple calls
|
||||
return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods)
|
||||
|
||||
class_eval do
|
||||
include ActiveRecord::Acts::Versioned::ActMethods
|
||||
cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column,
|
||||
:version_column, :max_version_limit, :track_changed_attributes, :version_condition, :version_sequence_name
|
||||
attr_accessor :changed_attributes
|
||||
end
|
||||
|
||||
self.versioned_class_name = options[:class_name] || "#{self.to_s.demodulize}Version"
|
||||
self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key
|
||||
self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{Inflector.underscore(Inflector.demodulize(class_name_of_active_record_descendant(self)))}_versions#{table_name_suffix}"
|
||||
self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}"
|
||||
self.version_column = options[:version_column] || 'version'
|
||||
self.version_sequence_name = options[:sequence_name]
|
||||
self.max_version_limit = options[:limit].to_i
|
||||
self.version_condition = options[:if] || true
|
||||
|
||||
class_eval do
|
||||
has_many :versions,
|
||||
:class_name => "ActiveRecord::Acts::Versioned::#{versioned_class_name}",
|
||||
:foreign_key => "#{versioned_foreign_key}",
|
||||
:order => 'version'
|
||||
before_save :set_new_version
|
||||
after_create :save_version_on_create
|
||||
after_update :save_version
|
||||
after_save :clear_old_versions
|
||||
after_save :clear_changed_attributes
|
||||
|
||||
unless options[:if_changed].nil?
|
||||
self.track_changed_attributes = true
|
||||
options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
|
||||
options[:if_changed].each do |attr_name|
|
||||
define_method("#{attr_name}=") do |value|
|
||||
(self.changed_attributes ||= []) << attr_name.to_s unless self.changed?(attr_name) or self.send(attr_name) == value
|
||||
write_attribute(attr_name.to_s, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# create the dynamic versioned model
|
||||
# maybe if i sit down long enough i can think up a better way to do this.
|
||||
dynamic_model = <<-EOV
|
||||
class ActiveRecord::Acts::Versioned::#{versioned_class_name} < ActiveRecord::Base
|
||||
set_table_name "#{versioned_table_name}"
|
||||
belongs_to :#{self.to_s.demodulize.underscore}, :class_name => "#{self.to_s}"
|
||||
EOV
|
||||
|
||||
dynamic_model += %Q{set_sequence_name "#{version_sequence_name}"\n} if version_sequence_name
|
||||
|
||||
eval dynamic_model + 'end'
|
||||
end
|
||||
end
|
||||
|
||||
module ActMethods
|
||||
def self.included(base) # :nodoc:
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
# Saves a version of the model if applicable
|
||||
def save_version
|
||||
save_version_on_create if save_version?
|
||||
end
|
||||
|
||||
# Saves a version of the model in the versioned table. This is called in the after_save callback by default
|
||||
def save_version_on_create
|
||||
rev = self.class.versioned_class.new
|
||||
self.clone_versioned_model(self, rev)
|
||||
rev.version = send(self.class.version_column)
|
||||
rev.send("#{self.class.versioned_foreign_key}=", self.id)
|
||||
rev.save
|
||||
end
|
||||
|
||||
# Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
|
||||
# Override this method to set your own criteria for clearing old versions.
|
||||
def clear_old_versions
|
||||
return if self.class.max_version_limit == 0
|
||||
excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit
|
||||
if excess_baggage > 0
|
||||
sql = "DELETE FROM #{self.class.versioned_table_name} WHERE version <= #{excess_baggage} AND #{self.class.versioned_foreign_key} = #{self.id}"
|
||||
self.class.versioned_class.connection.execute sql
|
||||
end
|
||||
end
|
||||
|
||||
# Finds a specific version of this model.
|
||||
def find_version(version)
|
||||
return version if version.is_a?(self.class.versioned_class)
|
||||
return nil if version.is_a?(ActiveRecord::Base)
|
||||
find_versions(:conditions => ['version = ?', version], :limit => 1).first
|
||||
end
|
||||
|
||||
# Finds versions of this model. Takes an options hash like <tt>find</tt>
|
||||
def find_versions(options = {})
|
||||
versions.find(:all, options)
|
||||
end
|
||||
|
||||
# Reverts a model to a given version. Takes either a version number or an instance of the versioned model
|
||||
def revert_to(version)
|
||||
if version.is_a?(self.class.versioned_class)
|
||||
return false unless version.send(self.class.versioned_foreign_key) == self.id and !version.new_record?
|
||||
else
|
||||
return false unless version = find_version(version)
|
||||
end
|
||||
self.clone_versioned_model(version, self)
|
||||
self.send("#{self.class.version_column}=", version.version)
|
||||
true
|
||||
end
|
||||
|
||||
# Reverts a model to a given version and saves the model.
|
||||
# Takes either a version number or an instance of the versioned model
|
||||
def revert_to!(version)
|
||||
revert_to(version) ? save_without_revision : false
|
||||
end
|
||||
|
||||
# Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created.
|
||||
def save_without_revision
|
||||
old_lock_value = ActiveRecord::Base.lock_optimistically
|
||||
ActiveRecord::Base.lock_optimistically = false if old_lock_value
|
||||
disable_acts_as_versioned_callbacks
|
||||
save_result = self.save
|
||||
enable_acts_as_versioned_callbacks
|
||||
ActiveRecord::Base.lock_optimistically = true if old_lock_value
|
||||
save_result
|
||||
end
|
||||
|
||||
# Returns an array of attribute keys that are versioned. See non_versioned_fields
|
||||
def versioned_attributes
|
||||
self.attributes.keys.select { |k| !self.class.non_versioned_fields.include?(k) }
|
||||
end
|
||||
|
||||
# If called with no parameters, gets whether the current model has changed and needs to be versioned.
|
||||
# If called with a single parameter, gets whether the parameter has changed.
|
||||
def changed?(attr_name = nil)
|
||||
attr_name.nil? ?
|
||||
(!self.class.track_changed_attributes or (changed_attributes and changed_attributes.length > 0)) :
|
||||
(changed_attributes and changed_attributes.include?(attr_name.to_s))
|
||||
end
|
||||
|
||||
# keep old dirty? method
|
||||
alias_method :dirty?, :changed?
|
||||
|
||||
# Clones a model. Used when saving a new version or reverting a model's version.
|
||||
def clone_versioned_model(orig_model, new_model)
|
||||
self.versioned_attributes.each do |key|
|
||||
new_model.send("#{key}=", orig_model.attributes[key]) if orig_model.attribute_present?(key)
|
||||
end
|
||||
|
||||
if orig_model.is_a?(self.class.versioned_class)
|
||||
new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column]
|
||||
elsif new_model.is_a?(self.class.versioned_class)
|
||||
new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column]
|
||||
end
|
||||
end
|
||||
|
||||
# Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>.
|
||||
def save_version?
|
||||
version_condition_met? and changed?
|
||||
end
|
||||
|
||||
# Checks condition set in the :if option to check whether a revision should be created or not. Override this for
|
||||
# custom version condition checking.
|
||||
def version_condition_met?
|
||||
case
|
||||
when version_condition.is_a?(Symbol)
|
||||
send(version_condition)
|
||||
when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
|
||||
version_condition.call(self)
|
||||
else
|
||||
version_condition
|
||||
end
|
||||
end
|
||||
|
||||
def previous_version(current_version = self.version)
|
||||
self.versions.find(:first, :conditions => [ 'version < ?', current_version ], :order => 'version desc')
|
||||
end
|
||||
|
||||
protected
|
||||
# sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version.
|
||||
def set_new_version
|
||||
self.send("#{self.class.version_column}=", self.next_version) if new_record? or (!locking_enabled? and save_version?)
|
||||
end
|
||||
|
||||
# Gets the next available version for the current record, or 1 for a new record
|
||||
def next_version
|
||||
return 1 if new_record?
|
||||
connection.select_one("SELECT MAX(version)+1 AS next_version FROM #{self.class.versioned_table_name} WHERE #{self.class.versioned_foreign_key} = #{self.id}")['next_version'] || 1
|
||||
end
|
||||
|
||||
# clears current changed attributes. Called after save.
|
||||
def clear_changed_attributes
|
||||
self.changed_attributes = []
|
||||
end
|
||||
|
||||
private
|
||||
unless defined?(ACTS_AS_VERSIONED_CALLBACKS)
|
||||
ACTS_AS_VERSIONED_CALLBACKS = [:set_new_version, :save_version_on_create, :save_version, :clear_changed_attributes]
|
||||
end
|
||||
|
||||
ACTS_AS_VERSIONED_CALLBACKS.each do |attr_name|
|
||||
alias_method "orig_#{attr_name}".to_sym, attr_name
|
||||
end
|
||||
|
||||
def empty_callback() end #:nodoc:
|
||||
|
||||
def enable_acts_as_versioned_callbacks
|
||||
self.class.class_eval do
|
||||
ACTS_AS_VERSIONED_CALLBACKS.each do |attr_name|
|
||||
alias_method attr_name, "orig_#{attr_name}".to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def disable_acts_as_versioned_callbacks
|
||||
self.class.class_eval do
|
||||
ACTS_AS_VERSIONED_CALLBACKS.each do |attr_name|
|
||||
alias_method attr_name, :empty_callback
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Finds a specific version of a specific row of this model
|
||||
def find_version(id, version)
|
||||
find_versions(id,
|
||||
:conditions => ["#{versioned_foreign_key} = ? AND version = ?", id, version],
|
||||
:limit => 1).first
|
||||
end
|
||||
|
||||
# Finds versions of a specific model. Takes an options hash like <tt>find</tt>
|
||||
def find_versions(id, options = {})
|
||||
versioned_class.find :all, {
|
||||
:conditions => ["#{versioned_foreign_key} = ?", id],
|
||||
:order => 'version' }.merge(options)
|
||||
end
|
||||
|
||||
# Returns an array of columns that are versioned. See non_versioned_fields
|
||||
def versioned_columns
|
||||
self.columns.select { |c| !non_versioned_fields.include?(c.name) }
|
||||
end
|
||||
|
||||
# Returns an instance of the dynamic versioned model
|
||||
def versioned_class
|
||||
"ActiveRecord::Acts::Versioned::#{versioned_class_name}".constantize
|
||||
end
|
||||
|
||||
# An array of fields that are not saved in the versioned table
|
||||
def non_versioned_fields
|
||||
[self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
|
||||
end
|
||||
|
||||
# Rake migration task to create the versioned table using options passed to acts_as_versioned
|
||||
def create_versioned_table(create_table_options = {})
|
||||
# create version column in main table if it does not exist
|
||||
if !self.content_columns.find { |c| %w(version lock_version).include? c.name }
|
||||
self.connection.add_column table_name, :version, :integer
|
||||
end
|
||||
|
||||
self.connection.create_table(versioned_table_name, create_table_options) do |t|
|
||||
t.column versioned_foreign_key, :integer
|
||||
t.column :version, :integer
|
||||
end
|
||||
|
||||
updated_col = nil
|
||||
self.versioned_columns.each do |col|
|
||||
updated_col = col if !updated_col and %(updated_at updated_on).include?(col.name)
|
||||
self.connection.add_column versioned_table_name, col.name, col.type,
|
||||
:limit => col.limit,
|
||||
:default => col.default
|
||||
end
|
||||
|
||||
if type_col = self.columns_hash[inheritance_column]
|
||||
self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type,
|
||||
:limit => type_col.limit,
|
||||
:default => type_col.default
|
||||
end
|
||||
|
||||
if updated_col.nil?
|
||||
self.connection.add_column versioned_table_name, :updated_at, :timestamp
|
||||
end
|
||||
end
|
||||
|
||||
# Rake migration task to drop the versioned table
|
||||
def drop_versioned_table
|
||||
self.connection.drop_table versioned_table_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ActiveRecord::Base.class_eval { include ActiveRecord::Acts::Versioned }
|
@ -1,24 +0,0 @@
|
||||
class Page < ActiveRecord::Base
|
||||
cattr_accessor :feeling_good
|
||||
@@feeling_good = true
|
||||
|
||||
acts_as_versioned :if => :feeling_good?
|
||||
|
||||
def feeling_good?
|
||||
@@feeling_good == true
|
||||
end
|
||||
end
|
||||
|
||||
class LockedPage < ActiveRecord::Base
|
||||
acts_as_versioned \
|
||||
:inheritance_column => :version_type,
|
||||
:foreign_key => :page_id,
|
||||
:table_name => :locked_pages_revisions,
|
||||
:class_name => 'LockedPageRevision',
|
||||
:version_column => :lock_version,
|
||||
:limit => 2,
|
||||
:if_changed => :title
|
||||
end
|
||||
|
||||
class SpecialLockedPage < LockedPage
|
||||
end
|
@ -1,3 +0,0 @@
|
||||
class Widget < ActiveRecord::Base
|
||||
acts_as_versioned :sequence_name => 'widgets_seq'
|
||||
end
|
@ -1,238 +0,0 @@
|
||||
require File.join(File.dirname(__FILE__), 'abstract_unit')
|
||||
require File.join(File.dirname(__FILE__), 'fixtures/page')
|
||||
require File.join(File.dirname(__FILE__), 'fixtures/widget')
|
||||
|
||||
class VersionedTest < Test::Unit::TestCase
|
||||
fixtures :pages, :page_versions, :locked_pages, :locked_pages_revisions
|
||||
|
||||
def test_saves_versioned_copy
|
||||
p = Page.create :title => 'first title', :body => 'first body'
|
||||
assert !p.new_record?
|
||||
assert_equal 1, p.versions.size
|
||||
assert_equal 1, p.version
|
||||
assert_instance_of Page.versioned_class, p.versions.first
|
||||
end
|
||||
|
||||
def test_rollback_with_version_number
|
||||
p = pages(:welcome)
|
||||
assert_equal 24, p.version
|
||||
assert_equal 'Welcome to the weblog', p.title
|
||||
|
||||
assert p.revert_to!(p.versions.first.version), "Couldn't revert to 23"
|
||||
assert_equal 23, p.version
|
||||
assert_equal 'Welcome to the weblg', p.title
|
||||
end
|
||||
|
||||
def test_versioned_class_name
|
||||
assert_equal 'PageVersion', Page.versioned_class_name
|
||||
assert_equal 'LockedPageRevision', LockedPage.versioned_class_name
|
||||
end
|
||||
|
||||
def test_rollback_with_version_class
|
||||
p = pages(:welcome)
|
||||
assert_equal 24, p.version
|
||||
assert_equal 'Welcome to the weblog', p.title
|
||||
|
||||
assert p.revert_to!(p.versions.first), "Couldn't revert to 23"
|
||||
assert_equal 23, p.version
|
||||
assert_equal 'Welcome to the weblg', p.title
|
||||
end
|
||||
|
||||
def test_rollback_fails_with_invalid_revision
|
||||
p = locked_pages(:welcome)
|
||||
assert !p.revert_to!(locked_pages(:thinking))
|
||||
end
|
||||
|
||||
def test_saves_versioned_copy_with_options
|
||||
p = LockedPage.create :title => 'first title'
|
||||
assert !p.new_record?
|
||||
assert_equal 1, p.versions.size
|
||||
assert_instance_of LockedPage.versioned_class, p.versions.first
|
||||
end
|
||||
|
||||
def test_rollback_with_version_number_with_options
|
||||
p = locked_pages(:welcome)
|
||||
assert_equal 'Welcome to the weblog', p.title
|
||||
assert_equal 'LockedPage', p.versions.first.version_type
|
||||
|
||||
assert p.revert_to!(p.versions.first.version), "Couldn't revert to 23"
|
||||
assert_equal 'Welcome to the weblg', p.title
|
||||
assert_equal 'LockedPage', p.versions.first.version_type
|
||||
end
|
||||
|
||||
def test_rollback_with_version_class_with_options
|
||||
p = locked_pages(:welcome)
|
||||
assert_equal 'Welcome to the weblog', p.title
|
||||
assert_equal 'LockedPage', p.versions.first.version_type
|
||||
|
||||
assert p.revert_to!(p.versions.first), "Couldn't revert to 1"
|
||||
assert_equal 'Welcome to the weblg', p.title
|
||||
assert_equal 'LockedPage', p.versions.first.version_type
|
||||
end
|
||||
|
||||
def test_saves_versioned_copy_with_sti
|
||||
p = SpecialLockedPage.create :title => 'first title'
|
||||
assert !p.new_record?
|
||||
assert_equal 1, p.versions.size
|
||||
assert_instance_of LockedPage.versioned_class, p.versions.first
|
||||
assert_equal 'SpecialLockedPage', p.versions.first.version_type
|
||||
end
|
||||
|
||||
def test_rollback_with_version_number_with_sti
|
||||
p = locked_pages(:thinking)
|
||||
assert_equal 'So I was thinking', p.title
|
||||
|
||||
assert p.revert_to!(p.versions.first.version), "Couldn't revert to 1"
|
||||
assert_equal 'So I was thinking!!!', p.title
|
||||
assert_equal 'SpecialLockedPage', p.versions.first.version_type
|
||||
end
|
||||
|
||||
def test_lock_version_works_with_versioning
|
||||
p = locked_pages(:thinking)
|
||||
p2 = LockedPage.find(p.id)
|
||||
|
||||
p.title = 'fresh title'
|
||||
p.save
|
||||
assert_equal 2, p.versions.size # limit!
|
||||
|
||||
assert_raises(ActiveRecord::StaleObjectError) do
|
||||
p2.title = 'stale title'
|
||||
p2.save
|
||||
end
|
||||
end
|
||||
|
||||
def test_version_if_condition
|
||||
p = Page.create :title => "title"
|
||||
assert_equal 1, p.version
|
||||
|
||||
Page.feeling_good = false
|
||||
p.save
|
||||
assert_equal 1, p.version
|
||||
Page.feeling_good = true
|
||||
end
|
||||
|
||||
def test_version_if_condition2
|
||||
# set new if condition
|
||||
Page.class_eval do
|
||||
def new_feeling_good() title[0..0] == 'a'; end
|
||||
alias_method :old_feeling_good, :feeling_good?
|
||||
alias_method :feeling_good?, :new_feeling_good
|
||||
end
|
||||
|
||||
p = Page.create :title => "title"
|
||||
assert_equal 1, p.version # version does not increment
|
||||
assert_equal 1, p.versions(true).size
|
||||
|
||||
p.update_attributes(:title => 'new title')
|
||||
assert_equal 1, p.version # version does not increment
|
||||
assert_equal 1, p.versions(true).size
|
||||
|
||||
p.update_attributes(:title => 'a title')
|
||||
assert_equal 2, p.version
|
||||
assert_equal 2, p.versions(true).size
|
||||
|
||||
# reset original if condition
|
||||
Page.class_eval { alias_method :feeling_good?, :old_feeling_good }
|
||||
end
|
||||
|
||||
def test_version_if_condition_with_block
|
||||
# set new if condition
|
||||
old_condition = Page.version_condition
|
||||
Page.version_condition = Proc.new { |page| page.title[0..0] == 'b' }
|
||||
|
||||
p = Page.create :title => "title"
|
||||
assert_equal 1, p.version # version does not increment
|
||||
assert_equal 1, p.versions(true).size
|
||||
|
||||
p.update_attributes(:title => 'a title')
|
||||
assert_equal 1, p.version # version does not increment
|
||||
assert_equal 1, p.versions(true).size
|
||||
|
||||
p.update_attributes(:title => 'b title')
|
||||
assert_equal 2, p.version
|
||||
assert_equal 2, p.versions(true).size
|
||||
|
||||
# reset original if condition
|
||||
Page.version_condition = old_condition
|
||||
end
|
||||
|
||||
def test_version_no_limit
|
||||
p = Page.create :title => "title", :body => 'first body'
|
||||
p.save
|
||||
p.save
|
||||
5.times do |i|
|
||||
assert_page_title p, i
|
||||
end
|
||||
end
|
||||
|
||||
def test_version_max_limit
|
||||
p = LockedPage.create :title => "title"
|
||||
p.update_attributes(:title => "title1")
|
||||
p.update_attributes(:title => "title2")
|
||||
5.times do |i|
|
||||
assert_page_title p, i, :lock_version
|
||||
assert p.versions(true).size <= 2, "locked version can only store 2 versions"
|
||||
end
|
||||
end
|
||||
|
||||
def test_track_changed_attributes_default_value
|
||||
assert !Page.track_changed_attributes
|
||||
assert LockedPage.track_changed_attributes
|
||||
assert SpecialLockedPage.track_changed_attributes
|
||||
end
|
||||
|
||||
def test_version_order
|
||||
assert_equal 23, pages(:welcome).versions.first.version
|
||||
assert_equal 24, pages(:welcome).versions.last.version
|
||||
assert_equal 23, pages(:welcome).find_versions.first.version
|
||||
assert_equal 24, pages(:welcome).find_versions.last.version
|
||||
end
|
||||
|
||||
def test_track_changed_attributes
|
||||
p = LockedPage.create :title => "title"
|
||||
assert_equal 1, p.lock_version
|
||||
assert_equal 1, p.versions(true).size
|
||||
|
||||
p.title = 'title'
|
||||
assert !p.save_version?
|
||||
p.save
|
||||
assert_equal 2, p.lock_version # still increments version because of optimistic locking
|
||||
assert_equal 1, p.versions(true).size
|
||||
|
||||
p.title = 'updated title'
|
||||
assert p.save_version?
|
||||
p.save
|
||||
assert_equal 3, p.lock_version
|
||||
assert_equal 1, p.versions(true).size # version 1 deleted
|
||||
|
||||
p.title = 'updated title!'
|
||||
assert p.save_version?
|
||||
p.save
|
||||
assert_equal 4, p.lock_version
|
||||
assert_equal 2, p.versions(true).size # version 1 deleted
|
||||
end
|
||||
|
||||
def assert_page_title(p, i, version_field = :version)
|
||||
p.title = "title#{i}"
|
||||
p.save
|
||||
assert_equal "title#{i}", p.title
|
||||
assert_equal (i+4), p.send(version_field)
|
||||
end
|
||||
|
||||
def test_find_versions
|
||||
assert_equal 2, locked_pages(:welcome).versions.size
|
||||
assert_equal 1, locked_pages(:welcome).find_versions(:conditions => ['title LIKE ?', '%weblog%']).length
|
||||
assert_equal 2, locked_pages(:welcome).find_versions(:conditions => ['title LIKE ?', '%web%']).length
|
||||
assert_equal 0, locked_pages(:thinking).find_versions(:conditions => ['title LIKE ?', '%web%']).length
|
||||
assert_equal 2, locked_pages(:welcome).find_versions.length
|
||||
end
|
||||
|
||||
def test_with_sequence
|
||||
assert_equal 'widgets_seq', Widget.versioned_class.sequence_name
|
||||
Widget.create :name => 'new widget'
|
||||
Widget.create :name => 'new widget'
|
||||
Widget.create :name => 'new widget'
|
||||
assert_equal 3, Widget.count
|
||||
assert_equal 3, Widget.versioned_class.count
|
||||
end
|
||||
end
|
@ -0,0 +1,57 @@
|
||||
--- !ruby/object:Gem::Specification
|
||||
name: bluecloth
|
||||
version: !ruby/object:Gem::Version
|
||||
version: 1.0.0
|
||||
platform: ruby
|
||||
authors: []
|
||||
|
||||
autorequire:
|
||||
bindir: bin
|
||||
cert_chain: []
|
||||
|
||||
date: 2009-07-16 00:00:00 -03:00
|
||||
default_executable:
|
||||
dependencies: []
|
||||
|
||||
description:
|
||||
email:
|
||||
executables: []
|
||||
|
||||
extensions: []
|
||||
|
||||
extra_rdoc_files: []
|
||||
|
||||
files:
|
||||
- lib
|
||||
- lib/bluecloth.rb
|
||||
has_rdoc: false
|
||||
homepage:
|
||||
post_install_message:
|
||||
rdoc_options: []
|
||||
|
||||
require_paths:
|
||||
- bin
|
||||
- bin
|
||||
- bin
|
||||
- lib
|
||||
required_ruby_version: !ruby/object:Gem::Requirement
|
||||
requirements:
|
||||
- - ">="
|
||||
- !ruby/object:Gem::Version
|
||||
version: "0"
|
||||
version:
|
||||
required_rubygems_version: !ruby/object:Gem::Requirement
|
||||
requirements:
|
||||
- - ">="
|
||||
- !ruby/object:Gem::Version
|
||||
version: "0"
|
||||
version:
|
||||
requirements: []
|
||||
|
||||
rubyforge_project:
|
||||
rubygems_version: 1.3.1
|
||||
signing_key:
|
||||
specification_version: 2
|
||||
summary:
|
||||
test_files: []
|
||||
|
@ -0,0 +1,107 @@
|
||||
--- !ruby/object:Gem::Specification
|
||||
name: dr_nic_magic_models
|
||||
version: !ruby/object:Gem::Version
|
||||
version: 0.9.2
|
||||
platform: ruby
|
||||
authors:
|
||||
- nicwilliams
|
||||
autorequire:
|
||||
bindir: bin
|
||||
cert_chain:
|
||||
date: 2007-04-29 00:00:00 -03:00
|
||||
default_executable:
|
||||
dependencies: []
|
||||
|
||||
description: Dr Nic's Magic Models - Invisible validations, assocations and Active Record models themselves!
|
||||
email: drnicwilliams@gmail.com
|
||||
executables: []
|
||||
|
||||
extensions: []
|
||||
|
||||
extra_rdoc_files: []
|
||||
|
||||
files:
|
||||
- CHANGELOG
|
||||
- History.txt
|
||||
- Manifest.txt
|
||||
- README
|
||||
- Rakefile
|
||||
- install.rb
|
||||
- lib/base.rb
|
||||
- lib/connection_adapters/abstract/schema_statements.rb
|
||||
- lib/connection_adapters/abstract_adapter.rb
|
||||
- lib/connection_adapters/mysql_adapter.rb
|
||||
- lib/connection_adapters/postgresql_adapter.rb
|
||||
- lib/dr_nic_magic_models.rb
|
||||
- lib/dr_nic_magic_models/inflector.rb
|
||||
- lib/dr_nic_magic_models/magic_model.rb
|
||||
- lib/dr_nic_magic_models/schema.rb
|
||||
- lib/dr_nic_magic_models/validations.rb
|
||||
- lib/dr_nic_magic_models/version.rb
|
||||
- lib/module.rb
|
||||
- lib/rails.rb
|
||||
- scripts/txt2html
|
||||
- scripts/txt2js
|
||||
- test.db
|
||||
- test/abstract_unit.rb
|
||||
- test/connections/native_mysql/connection.rb
|
||||
- test/connections/native_postgresql/connection.rb
|
||||
- test/connections/native_sqlite/connection.rb
|
||||
- test/dummy_test.rb
|
||||
- test/env_test.rb
|
||||
- test/fixtures/.DS_Store
|
||||
- test/fixtures/adjectives.yml
|
||||
- test/fixtures/adjectives_fun_users.yml
|
||||
- test/fixtures/db_definitions/mysql.drop.sql
|
||||
- test/fixtures/db_definitions/mysql.sql
|
||||
- test/fixtures/db_definitions/postgresql.sql
|
||||
- test/fixtures/db_definitions/sqlite.sql
|
||||
- test/fixtures/fun_users.yml
|
||||
- test/fixtures/group_memberships.yml
|
||||
- test/fixtures/group_tag.yml
|
||||
- test/fixtures/groups.yml
|
||||
- test/foreign_keys_test.rb
|
||||
- test/fun_user_plus.rb
|
||||
- test/invisible_model_access_test.rb
|
||||
- test/invisible_model_assoc_test.rb
|
||||
- test/invisible_model_classes_test.rb
|
||||
- test/magic_module_test.rb
|
||||
- test/test_existing_model.rb
|
||||
- website/index.html
|
||||
- website/index.txt
|
||||
- website/javascripts/rounded_corners_lite.inc.js
|
||||
- website/stylesheets/screen.css
|
||||
- website/template.js
|
||||
- website/template.rhtml
|
||||
- website/version-raw.js
|
||||
- website/version-raw.txt
|
||||
- website/version.js
|
||||
- website/version.txt
|
||||
has_rdoc: true
|
||||
homepage: http://magicmodels.rubyforge.org
|
||||
post_install_message:
|
||||
rdoc_options: []
|
||||
|
||||
require_paths:
|
||||
- lib
|
||||
required_ruby_version: !ruby/object:Gem::Requirement
|
||||
requirements:
|
||||
- - ">"
|
||||
- !ruby/object:Gem::Version
|
||||
version: 0.0.0
|
||||
version:
|
||||
required_rubygems_version: !ruby/object:Gem::Requirement
|
||||
requirements:
|
||||
- - ">="
|
||||
- !ruby/object:Gem::Version
|
||||
version: "0"
|
||||
version:
|
||||
requirements: []
|
||||
|
||||
rubyforge_project: magicmodels
|
||||
rubygems_version: 1.3.1
|
||||
signing_key:
|
||||
specification_version: 1
|
||||
summary: Dr Nic's Magic Models - Invisible validations, assocations and Active Record models themselves!
|
||||
test_files:
|
||||
- test/test_existing_model.rb
|
Binary file not shown.
@ -1,20 +0,0 @@
|
||||
Copyright (c) 2006-2007 Hampton Catlin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@ -1,248 +0,0 @@
|
||||
= Haml and Sass
|
||||
|
||||
Haml and Sass are templating engines
|
||||
for the two most common types of documents on the web:
|
||||
HTML and CSS, respectively.
|
||||
They are designed to make it both easier and more pleasant
|
||||
to code HTML and CSS documents,
|
||||
by eliminating redundancy,
|
||||
reflecting the underlying structure that the document represents,
|
||||
and providing elegant, easily understandable, and powerful syntax.
|
||||
|
||||
== Using
|
||||
|
||||
There are several ways to use Haml and Sass.
|
||||
They can be used as a plugins for Rails or Merb,
|
||||
or embedded on their own in other applications.
|
||||
The first step of all of these is to install the Haml gem:
|
||||
|
||||
gem install haml
|
||||
|
||||
To install Haml and Sass as a Rails plugin,
|
||||
just run <tt>haml --rails path/to/rails/app</tt>
|
||||
and both Haml and Sass will be installed.
|
||||
Views with the <tt>.haml</tt> (or <tt>.html.haml</tt> for edge)
|
||||
extension will automatically use Haml.
|
||||
Sass is a little more complicated;
|
||||
<tt>.sass</tt> files should be placed in public/stylesheets/sass,
|
||||
where they'll be automatically compiled
|
||||
to corresponding CSS files in public/stylesheets when needed
|
||||
(the Sass template directory is customizable...
|
||||
see the Sass module docs for details).
|
||||
|
||||
For Merb, <tt>.html.haml</tt> views will work without any further modification.
|
||||
To enable Sass, you also need to add it add a dependency.
|
||||
To do so, just add
|
||||
|
||||
dependency "haml"
|
||||
|
||||
to config/dependencies.rb.
|
||||
Then it'll work just like it does in Rails.
|
||||
|
||||
To use Haml and Sass programatically,
|
||||
check out the RDocs for the Haml and Sass modules.
|
||||
|
||||
== Formatting
|
||||
|
||||
=== Haml
|
||||
|
||||
The most basic element of Haml
|
||||
is a shorthand for creating HTML tags:
|
||||
|
||||
%tagname{ :attr1 => 'value1', :attr2 => 'value2' } Contents
|
||||
|
||||
No end-tag is needed; Haml handles that automatically.
|
||||
Adding <tt>class</tt> and <tt>id</tt> attributes is even easier.
|
||||
Haml uses the same syntax as the CSS that styles the document:
|
||||
|
||||
%tagname#id.class
|
||||
|
||||
In fact, when you're using the <tt><div></tt> tag,
|
||||
it becomes <em>even easier</em>.
|
||||
Because <tt><div></tt> is such a common element,
|
||||
a tag without a name defaults to a div. So
|
||||
|
||||
#foo Hello!
|
||||
|
||||
becomes
|
||||
|
||||
<div id='foo'>Hello!</div>
|
||||
|
||||
Haml uses indentation
|
||||
to bring the individual elements to represent the HTML structure.
|
||||
A tag's children are indented two spaces more than the parent tag.
|
||||
Again, a closing tag is automatically added.
|
||||
For example:
|
||||
|
||||
%ul
|
||||
%li Salt
|
||||
%li Pepper
|
||||
|
||||
becomes:
|
||||
|
||||
<ul>
|
||||
<li>Salt</li>
|
||||
<li>Pepper</li>
|
||||
</ul>
|
||||
|
||||
You can also put plain text as a child of an element:
|
||||
|
||||
%p
|
||||
Hello,
|
||||
World!
|
||||
|
||||
It's even possible to embed Ruby code into Haml documents.
|
||||
An equals sign, <tt>=</tt>, will output the result of the code.
|
||||
A hyphen, <tt>-</tt>, will run the code but not output the result.
|
||||
You can even use control statements
|
||||
like <tt>if</tt> and <tt>while</tt>:
|
||||
|
||||
%p
|
||||
Date/Time:
|
||||
- now = DateTime.now
|
||||
%strong= now
|
||||
- if now > DateTime.parse("December 31, 2006")
|
||||
= "Happy new " + "year!"
|
||||
|
||||
Haml provides far more tools than those presented here.
|
||||
Check out the reference documentation in the Haml module.
|
||||
|
||||
=== Sass
|
||||
|
||||
At its most basic,
|
||||
Sass is just another way of writing CSS.
|
||||
Although it's very much like normal CSS,
|
||||
the basic syntax offers a few helpful features:
|
||||
tabulation (using *two spaces*)
|
||||
indicates the attributes in a rule,
|
||||
rather than non-DRY brackets;
|
||||
and newlines indicate the end of an attribute,
|
||||
rather than a semicolon.
|
||||
For example:
|
||||
|
||||
#main
|
||||
:background-color #f00
|
||||
:width 98%
|
||||
|
||||
becomes:
|
||||
|
||||
#main {
|
||||
background-color: #f00;
|
||||
width: 98% }
|
||||
|
||||
However, Sass provides much more than a way to make CSS look nice.
|
||||
In CSS, it's important to have accurate selectors,
|
||||
so your styles don't just apply to everything.
|
||||
However, in order to do this,
|
||||
you need to use nested element selectors.
|
||||
These get very ugly very quickly.
|
||||
I'm sure everyone's had to write something like
|
||||
"#main .sidebar .top p h1 a",
|
||||
followed by
|
||||
"#main .sidebar .top p h1 a:visited" and
|
||||
"#main .sidebar .top p h1 a:hover".
|
||||
Well, Sass gets rid of that.
|
||||
Like Haml, it uses indentation to indicate the structure of the document.
|
||||
So, what was:
|
||||
|
||||
#main {
|
||||
width: 90%;
|
||||
}
|
||||
#main p {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: #00f;
|
||||
}
|
||||
#main p a {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
#main p a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
becomes:
|
||||
|
||||
#main
|
||||
:width 90%
|
||||
p
|
||||
:border-style solid
|
||||
:border-width 1px
|
||||
:border-color #00f
|
||||
a
|
||||
:text-decoration none
|
||||
:font-weight bold
|
||||
a:hover
|
||||
:text-decoration underline
|
||||
|
||||
Pretty nice, no? Well, it gets better.
|
||||
One of the main complaints against CSS is that it doesn't allow constants.
|
||||
What if have a color or a width you re-use all the time?
|
||||
In CSS, you just have to re-type it each time,
|
||||
which is a nightmare when you decide to change it later.
|
||||
Not so for Sass!
|
||||
You can use the "!" character to set constants.
|
||||
Then, if you put "=" after your attribute name,
|
||||
you can set it to a constant.
|
||||
For example:
|
||||
|
||||
!note_bg= #55aaff
|
||||
|
||||
#main
|
||||
:width 70%
|
||||
.note
|
||||
:background-color= !note_bg
|
||||
p
|
||||
:width 5em
|
||||
:background-color= !note_bg
|
||||
|
||||
becomes:
|
||||
|
||||
#main {
|
||||
width: 70%; }
|
||||
#main .note {
|
||||
background-color: #55aaff; }
|
||||
#main p {
|
||||
width: 5em;
|
||||
background-color: #55aaff; }
|
||||
|
||||
You can even do simple arithmetic operations with constants,
|
||||
adding numbers and even colors together:
|
||||
|
||||
!main_bg= #46ar12
|
||||
!main_width= 40em
|
||||
|
||||
#main
|
||||
:background-color= !main_bg
|
||||
:width= !main_width
|
||||
.sidebar
|
||||
:background-color= !main_bg + #333333
|
||||
:width= !main_width - 25em
|
||||
|
||||
becomes:
|
||||
|
||||
#main {
|
||||
background-color: #46a312;
|
||||
width: 40em; }
|
||||
#main .sidebar {
|
||||
background-color: #79d645;
|
||||
width: 15em; }
|
||||
|
||||
A comprehensive list of features is in
|
||||
the documentation for the Sass module.
|
||||
|
||||
== Authors
|
||||
|
||||
Haml and Sass are designed by Hampton Catlin (hcatlin).
|
||||
Help with the Ruby On Rails implementation and much of the documentation
|
||||
by Jeff Hardy (packagethief).
|
||||
|
||||
Nathan Weizenbaum (Nex3) contributed the buffered-engine code to Haml,
|
||||
along with many other enhancements
|
||||
(including the silent-line syntax: "-").
|
||||
He continues to actively work on both Haml and Sass.
|
||||
|
||||
If you use this software, you must pay Hampton a compliment.
|
||||
Say something nice about it.
|
||||
Beyond that, the implementation is licensed under the MIT License.
|
||||
Ok, fine, I guess that means compliments aren't *required*.
|
@ -1,174 +0,0 @@
|
||||
require 'rubygems'
|
||||
require 'rake'
|
||||
|
||||
volatile_requires = ['rcov/rcovtask']
|
||||
not_loaded = []
|
||||
volatile_requires.each do |file|
|
||||
begin
|
||||
require file
|
||||
rescue LoadError
|
||||
not_loaded.push file
|
||||
end
|
||||
end
|
||||
|
||||
# ----- Benchmarking -----
|
||||
|
||||
temp_desc = <<END
|
||||
Benchmark haml against ERb.
|
||||
TIMES=n sets the number of runs. Defaults to 100.
|
||||
END
|
||||
|
||||
desc temp_desc.chomp
|
||||
task :benchmark do
|
||||
require 'test/benchmark'
|
||||
|
||||
puts '-'*51, "Benchmark: Haml vs. ERb", '-'*51
|
||||
puts "Running benchmark #{ENV['TIMES']} times..." if ENV['TIMES']
|
||||
times = ENV['TIMES'].to_i if ENV['TIMES']
|
||||
benchmarker = Haml::Benchmarker.new
|
||||
puts benchmarker.benchmark(times || 100)
|
||||
puts '-'*51
|
||||
end
|
||||
|
||||
# Benchmarking gets screwed up if some other tasks have been
|
||||
# initialized.
|
||||
unless ARGV[0] == 'benchmark'
|
||||
|
||||
# ----- Default: Testing ------
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
require 'rake/testtask'
|
||||
|
||||
desc 'Test the Haml plugin'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
# ----- Packaging -----
|
||||
|
||||
require 'rake/gempackagetask'
|
||||
|
||||
spec = Gem::Specification.new do |spec|
|
||||
spec.name = 'haml'
|
||||
spec.summary = "An elegant, structured XHTML/XML templating engine.\nComes with Sass, a similar CSS templating engine."
|
||||
spec.version = File.read('VERSION').strip
|
||||
spec.author = 'Hampton Catlin'
|
||||
spec.email = 'haml@googlegroups.com'
|
||||
spec.description = <<-END
|
||||
Haml (HTML Abstraction Markup Language) is a layer on top of XHTML or XML
|
||||
that's designed to express the structure of XHTML or XML documents
|
||||
in a non-repetitive, elegant, easy way,
|
||||
using indentation rather than closing tags
|
||||
and allowing Ruby to be embedded with ease.
|
||||
It was originally envisioned as a plugin for Ruby on Rails,
|
||||
but it can function as a stand-alone templating engine.
|
||||
END
|
||||
#'
|
||||
|
||||
readmes = FileList.new('*') do |list|
|
||||
list.exclude(/[a-z]/)
|
||||
list.exclude('TODO')
|
||||
end.to_a
|
||||
spec.executables = ['haml', 'html2haml', 'sass']
|
||||
spec.files = FileList['lib/**/*', 'bin/*', 'test/**/*', 'Rakefile', 'init.rb'].to_a + readmes
|
||||
spec.autorequire = ['haml', 'sass']
|
||||
spec.homepage = 'http://haml.hamptoncatlin.com/'
|
||||
spec.has_rdoc = true
|
||||
spec.extra_rdoc_files = readmes
|
||||
spec.rdoc_options += [
|
||||
'--title', 'Haml',
|
||||
'--main', 'README',
|
||||
'--exclude', 'lib/haml/buffer.rb',
|
||||
'--line-numbers',
|
||||
'--inline-source'
|
||||
]
|
||||
spec.test_files = FileList['test/**/*_test.rb'].to_a
|
||||
end
|
||||
|
||||
Rake::GemPackageTask.new(spec) do |pkg|
|
||||
pkg.need_zip = true
|
||||
pkg.need_tar_gz = true
|
||||
pkg.need_tar_bz2 = true
|
||||
end
|
||||
|
||||
task :install => [:package] do
|
||||
sh %{gem install --no-ri pkg/haml-#{File.read('VERSION').strip}}
|
||||
end
|
||||
|
||||
# ----- Documentation -----
|
||||
|
||||
require 'rake/rdoctask'
|
||||
|
||||
rdoc_task = Proc.new do |rdoc|
|
||||
rdoc.title = 'Haml/Sass'
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
rdoc.rdoc_files.exclude('lib/haml/buffer.rb')
|
||||
rdoc.rdoc_files.exclude('lib/haml/util.rb')
|
||||
rdoc.rdoc_files.exclude('lib/sass/tree/*')
|
||||
end
|
||||
|
||||
Rake::RDocTask.new do |rdoc|
|
||||
rdoc_task.call(rdoc)
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
end
|
||||
|
||||
Rake::RDocTask.new(:rdoc_devel) do |rdoc|
|
||||
rdoc_task.call(rdoc)
|
||||
rdoc.rdoc_dir = 'rdoc_devel'
|
||||
rdoc.options << '--all'
|
||||
rdoc.rdoc_files.include('test/*.rb')
|
||||
|
||||
# Get rid of exclusion rules
|
||||
rdoc.rdoc_files = Rake::FileList.new(*rdoc.rdoc_files.to_a)
|
||||
rdoc.rdoc_files.include('lib/haml/buffer.rb')
|
||||
rdoc.rdoc_files.include('lib/sass/tree/*')
|
||||
end
|
||||
|
||||
# ----- Coverage -----
|
||||
|
||||
unless not_loaded.include? 'rcov/rcovtask'
|
||||
Rcov::RcovTask.new do |t|
|
||||
t.libs << "test"
|
||||
t.test_files = FileList['test/**/*_test.rb']
|
||||
t.rcov_opts << '-x' << '"^\/"'
|
||||
if ENV['NON_NATIVE']
|
||||
t.rcov_opts << "--no-rcovrt"
|
||||
end
|
||||
t.verbose = true
|
||||
end
|
||||
end
|
||||
|
||||
# ----- Profiling -----
|
||||
|
||||
temp_desc = <<-END
|
||||
Run a profile of haml.
|
||||
ENGINE=str sets the engine to be profiled (Haml or Sass).
|
||||
TIMES=n sets the number of runs. Defaults to 100.
|
||||
FILE=n sets the file to profile. Defaults to 'standard'.
|
||||
END
|
||||
desc temp_desc.chomp
|
||||
task :profile do
|
||||
require 'test/profile'
|
||||
|
||||
engine = ENV['ENGINE'] && ENV['ENGINE'].downcase == 'sass' ? Sass : Haml
|
||||
|
||||
puts '-'*51, "Profiling #{engine}", '-'*51
|
||||
|
||||
args = []
|
||||
args.push ENV['TIMES'].to_i if ENV['TIMES']
|
||||
args.push ENV['FILE'] if ENV['FILE']
|
||||
|
||||
profiler = engine::Profiler.new
|
||||
res = profiler.profile(*args)
|
||||
puts res
|
||||
|
||||
puts '-'*51
|
||||
end
|
||||
|
||||
end
|
@ -1 +0,0 @@
|
||||
1.7.2
|
@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require File.dirname(__FILE__) + '/../lib/haml'
|
||||
require 'haml/exec'
|
||||
|
||||
opts = Haml::Exec::CSS2Sass.new(ARGV)
|
||||
opts.parse!
|
@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# The command line Haml parser.
|
||||
|
||||
require File.dirname(__FILE__) + '/../lib/haml'
|
||||
require 'haml/exec'
|
||||
|
||||
opts = Haml::Exec::Haml.new(ARGV)
|
||||
opts.parse!
|
@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require File.dirname(__FILE__) + '/../lib/haml'
|
||||
require 'haml/exec'
|
||||
|
||||
opts = Haml::Exec::HTML2Haml.new(ARGV)
|
||||
opts.parse!
|
@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# The command line Sass parser.
|
||||
|
||||
require File.dirname(__FILE__) + '/../lib/haml'
|
||||
require 'haml/exec'
|
||||
|
||||
opts = Haml::Exec::Sass.new(ARGV)
|
||||
opts.parse!
|
@ -1,7 +0,0 @@
|
||||
require 'haml'
|
||||
require 'haml/template'
|
||||
require 'sass'
|
||||
require 'sass/plugin'
|
||||
|
||||
ActionView::Base.register_template_handler('haml', Haml::Template)
|
||||
Sass::Plugin.update_stylesheets
|
@ -1,708 +0,0 @@
|
||||
dir = File.dirname(__FILE__)
|
||||
$LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
||||
|
||||
# = Haml (XHTML Abstraction Markup Language)
|
||||
#
|
||||
# Haml is a markup language
|
||||
# that's used to cleanly and simply describe the XHTML of any web document,
|
||||
# without the use of inline code.
|
||||
# Haml functions as a replacement
|
||||
# for inline page templating systems such as PHP, ERB, and ASP.
|
||||
# However, Haml avoids the need for explicitly coding XHTML into the template,
|
||||
# because it is actually an abstract description of the XHTML,
|
||||
# with some code to generate dynamic content.
|
||||
#
|
||||
# == Features
|
||||
#
|
||||
# * Whitespace active
|
||||
# * Well-formatted markup
|
||||
# * DRY
|
||||
# * Follows CSS conventions
|
||||
# * Integrates Ruby code
|
||||
# * Implements Rails templates with the .haml extension
|
||||
#
|
||||
# == Using Haml
|
||||
#
|
||||
# Haml can be used in two ways:
|
||||
# as a plugin for Ruby on Rails,
|
||||
# and as a standalone Ruby module.
|
||||
#
|
||||
# Sass can be used in several ways:
|
||||
# As a template engine for Ruby on Rails or Merb,
|
||||
# or as a standalone engine.
|
||||
# The first step for all of these is to install the Haml gem:
|
||||
#
|
||||
# gem install haml
|
||||
#
|
||||
# To enable it as a Rails plugin,
|
||||
# then run
|
||||
#
|
||||
# haml --rails path/to/rails/app
|
||||
#
|
||||
# Haml is enabled in Merb by default,
|
||||
# so Merb users don't have to do anything more.
|
||||
#
|
||||
# Once it's installed, all view files with the ".haml" extension
|
||||
# (or ".html.haml" for Merb or edge Rails)
|
||||
# will be compiled using Haml.
|
||||
#
|
||||
# You can access instance variables in Haml templates
|
||||
# the same way you do in ERb templates.
|
||||
# Helper methods are also available in Haml templates.
|
||||
# For example (this example uses Rails, but the principle for Merb is the same):
|
||||
#
|
||||
# # file: app/controllers/movies_controller.rb
|
||||
#
|
||||
# class MoviesController < ApplicationController
|
||||
# def index
|
||||
# @title = "Teen Wolf"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# -# file: app/views/movies/index.haml
|
||||
#
|
||||
# #content
|
||||
# .title
|
||||
# %h1= @title
|
||||
# = link_to 'Home', home_url
|
||||
#
|
||||
# may be compiled to:
|
||||
#
|
||||
# <div id='content'>
|
||||
# <div class='title'>
|
||||
# <h1>Teen Wolf</h1>
|
||||
# <a href='/'>Home</a>
|
||||
# </div>
|
||||
# </div>
|
||||
#
|
||||
# === Ruby Module
|
||||
#
|
||||
# Haml can also be used completely separately from Rails and ActionView.
|
||||
# To do this, install the gem with RubyGems:
|
||||
#
|
||||
# gem install haml
|
||||
#
|
||||
# You can then use it by including the "haml" gem in Ruby code,
|
||||
# and using Haml::Engine like so:
|
||||
#
|
||||
# engine = Haml::Engine.new("%p Haml code!")
|
||||
# engine.render #=> "<p>Haml code!</p>\n"
|
||||
#
|
||||
# == Characters with meaning to Haml
|
||||
#
|
||||
# Various characters, when placed at a certain point in a line,
|
||||
# instruct Haml to render different types of things.
|
||||
#
|
||||
# === XHTML Tags
|
||||
#
|
||||
# These characters render XHTML tags.
|
||||
#
|
||||
# ==== %
|
||||
#
|
||||
#
|
||||
# The percent character is placed at the beginning of a line.
|
||||
# It's followed immediately by the name of an element,
|
||||
# then optionally by modifiers (see below), a space,
|
||||
# and text to be rendered inside the element.
|
||||
# It creates an element in the form of <tt><element></element></tt>.
|
||||
# For example:
|
||||
#
|
||||
# %one
|
||||
# %two
|
||||
# %three Hey there
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <one>
|
||||
# <two>
|
||||
# <three>Hey there</three>
|
||||
# </two>
|
||||
# </one>
|
||||
#
|
||||
# Any string is a valid element name;
|
||||
# Haml will automatically generate opening and closing tags for any element.
|
||||
#
|
||||
# ==== {}
|
||||
#
|
||||
# Brackets represent a Ruby hash
|
||||
# that is used for specifying the attributes of an element.
|
||||
# It is literally evaluated as a Ruby hash,
|
||||
# so logic will work in it and local variables may be used.
|
||||
# Quote characters within the attribute
|
||||
# will be replaced by appropriate escape sequences.
|
||||
# The hash is placed after the tag is defined.
|
||||
# For example:
|
||||
#
|
||||
# %head{ :name => "doc_head" }
|
||||
# %script{ 'type' => "text/" + "javascript",
|
||||
# :src => "javascripts/script_#{2 + 7}" }
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <head name="doc_head">
|
||||
# <script src='javascripts/script_9' type='text/javascript'>
|
||||
# </script>
|
||||
# </head>
|
||||
#
|
||||
# ==== []
|
||||
#
|
||||
# Square brackets follow a tag definition and contain a Ruby object
|
||||
# that is used to set the class and id of that tag.
|
||||
# The class is set to the object's class
|
||||
# (transformed to use underlines rather than camel case)
|
||||
# and the id is set to the object's class, followed by its id.
|
||||
# Because the id of an object is normally an obscure implementation detail,
|
||||
# this is most useful for elements that represent instances of Models.
|
||||
# For example:
|
||||
#
|
||||
# # file: app/controllers/users_controller.rb
|
||||
#
|
||||
# def show
|
||||
# @user = CrazyUser.find(15)
|
||||
# end
|
||||
#
|
||||
# -# file: app/views/users/show.haml
|
||||
#
|
||||
# %div[@user]
|
||||
# %bar[290]/
|
||||
# Hello!
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <div class="crazy_user" id="crazy_user_15">
|
||||
# <bar class="fixnum" id="fixnum_581" />
|
||||
# Hello!
|
||||
# </div>
|
||||
#
|
||||
# This is based off of DHH's SimplyHelpful syntax,
|
||||
# as presented at RailsConf Europe 2006.
|
||||
#
|
||||
# ==== /
|
||||
#
|
||||
# The forward slash character, when placed at the end of a tag definition,
|
||||
# causes the tag to be self-closed.
|
||||
# For example:
|
||||
#
|
||||
# %br/
|
||||
# %meta{'http-equiv' => 'Content-Type', :content => 'text/html'}/
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <br />
|
||||
# <meta http-equiv='Content-Type' content='text/html' />
|
||||
#
|
||||
# Some tags are automatically closed, as long as they have no content.
|
||||
# +meta+, +img+, +link+, +script+, +br+, and +hr+ tags are closed by default.
|
||||
# This list can be customized by setting the <tt>:autoclose</tt> option (see below).
|
||||
# For example:
|
||||
#
|
||||
# %br
|
||||
# %meta{'http-equiv' => 'Content-Type', :content => 'text/html'}
|
||||
#
|
||||
# is also compiled to:
|
||||
#
|
||||
# <br />
|
||||
# <meta http-equiv='Content-Type' content='text/html' />
|
||||
#
|
||||
# ==== . and #
|
||||
#
|
||||
# The period and pound sign are borrowed from CSS.
|
||||
# They are used as shortcuts to specify the <tt>class</tt>
|
||||
# and <tt>id</tt> attributes of an element, respectively.
|
||||
# Multiple class names can be specified in a similar way to CSS,
|
||||
# by chaining the class names together with periods.
|
||||
# They are placed immediately after the tag and before an attributes hash.
|
||||
# For example:
|
||||
#
|
||||
# %div#things
|
||||
# %span#rice Chicken Fried
|
||||
# %p.beans{ :food => 'true' } The magical fruit
|
||||
# %h1.class.otherclass#id La La La
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <div id='things'>
|
||||
# <span id='rice'>Chicken Fried</span>
|
||||
# <p class='beans' food='true'>The magical fruit</p>
|
||||
# <h1 class='class otherclass' id='id'>La La La</h1>
|
||||
# </div>
|
||||
#
|
||||
# And,
|
||||
#
|
||||
# #content
|
||||
# .articles
|
||||
# .article.title
|
||||
# Doogie Howser Comes Out
|
||||
# .article.date
|
||||
# 2006-11-05
|
||||
# .article.entry
|
||||
# Neil Patrick Harris would like to dispel any rumors that he is straight
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <div id="content">
|
||||
# <div class="articles">
|
||||
# <div class="article title">Doogie Howser Comes Out</div>
|
||||
# <div class="article date">2006-11-05</div>
|
||||
# <div class="article entry">
|
||||
# Neil Patrick Harris would like to dispel any rumors that he is straight
|
||||
# </div>
|
||||
# </div>
|
||||
# </div>
|
||||
#
|
||||
# ==== Implicit Div Elements
|
||||
#
|
||||
# Because the div element is used so often, it is the default element.
|
||||
# If you only define a class and/or id using the <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'>
|
||||
# <div class='description'>What a cool item!</div>
|
||||
# </div>
|
||||
# </div>
|
||||
#
|
||||
# ==== =
|
||||
#
|
||||
# <tt>=</tt> is placed at the end of a tag definition,
|
||||
# after class, id, and attribute declarations.
|
||||
# It's just a shortcut for inserting Ruby code into an element.
|
||||
# It works the same as <tt>=</tt> without a tag:
|
||||
# it inserts the result of the Ruby code into the template.
|
||||
# However, if the result is short enough,
|
||||
# it is displayed entirely on one line.
|
||||
# For example:
|
||||
#
|
||||
# %p= "hello"
|
||||
#
|
||||
# is not quite the same as:
|
||||
#
|
||||
# %p
|
||||
# = "hello"
|
||||
#
|
||||
# It's compiled to:
|
||||
#
|
||||
# <p>hello</p>
|
||||
#
|
||||
# === XHTML Helpers
|
||||
#
|
||||
# ==== No Special Character
|
||||
#
|
||||
# If no special character appears at the beginning of a line,
|
||||
# the line is rendered as plain text.
|
||||
# For example:
|
||||
#
|
||||
# %gee
|
||||
# %whiz
|
||||
# Wow this is cool!
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <gee>
|
||||
# <whiz>
|
||||
# Wow this is cool!
|
||||
# </whiz>
|
||||
# </gee>
|
||||
#
|
||||
# ==== !!!
|
||||
#
|
||||
# When describing XHTML documents with Haml,
|
||||
# you can have a document type or XML prolog generated automatically
|
||||
# by including the characters <tt>!!!</tt>.
|
||||
# For example:
|
||||
#
|
||||
# !!! XML
|
||||
# !!!
|
||||
# %html
|
||||
# %head
|
||||
# %title Myspace
|
||||
# %body
|
||||
# %h1 I am the international space station
|
||||
# %p Sign my guestbook
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <?xml version="1.0" encoding="utf-8" ?>
|
||||
# <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
# <html>
|
||||
# <head>
|
||||
# <title>Myspace</title>
|
||||
# </head>
|
||||
# <body>
|
||||
# <h1>I am the international space station</h1>
|
||||
# <p>Sign my guestbook</p>
|
||||
# </body>
|
||||
# </html>
|
||||
#
|
||||
# You can also specify the version and type of XHTML after the <tt>!!!</tt>.
|
||||
# XHTML 1.0 Strict, Transitional, and Frameset and XHTML 1.1 are supported.
|
||||
# The default version is 1.0 and the default type is Transitional.
|
||||
# For example:
|
||||
#
|
||||
# !!! 1.1
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
#
|
||||
# and
|
||||
#
|
||||
# !!! Strict
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
#
|
||||
# If you're not using the UTF-8 character set for your document,
|
||||
# you can specify which encoding should appear
|
||||
# in the XML prolog in a similar way.
|
||||
# For example:
|
||||
#
|
||||
# !!! XML iso-8859-1
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <?xml version="1.0" encoding="iso-8859-1" ?>
|
||||
#
|
||||
# ==== /
|
||||
#
|
||||
# The forward slash character, when placed at the beginning of a line,
|
||||
# wraps all text after it in an HTML comment.
|
||||
# For example:
|
||||
#
|
||||
# %peanutbutterjelly
|
||||
# / This is the peanutbutterjelly element
|
||||
# I like sandwiches!
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <peanutbutterjelly>
|
||||
# <!-- This is the peanutbutterjelly element -->
|
||||
# I like sandwiches!
|
||||
# </peanutbutterjelly>
|
||||
#
|
||||
# The forward slash can also wrap indented sections of code. For example:
|
||||
#
|
||||
# /
|
||||
# %p This doesn't render...
|
||||
# %div
|
||||
# %h1 Because it's commented out!
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <!--
|
||||
# <p>This doesn't render...</p>
|
||||
# <div>
|
||||
# <h1>Because it's commented out!</h1>
|
||||
# </div>
|
||||
# -->
|
||||
#
|
||||
# You can also use Internet Explorer conditional comments
|
||||
# (about)[http://www.quirksmode.org/css/condcom.html]
|
||||
# by enclosing the condition in square brackets after the <tt>/</tt>.
|
||||
# For example:
|
||||
#
|
||||
# /[if IE]
|
||||
# %a{ :href => 'http://www.mozilla.com/en-US/firefox/' }
|
||||
# %h1 Get Firefox
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <!--[if IE]>
|
||||
# <a href='http://www.mozilla.com/en-US/firefox/'>
|
||||
# <h1>Get Firefox</h1>
|
||||
# </a>
|
||||
# <![endif]-->
|
||||
#
|
||||
# ==== \
|
||||
#
|
||||
# The backslash character escapes the first character of a line,
|
||||
# allowing use of otherwise interpreted characters as plain text.
|
||||
# For example:
|
||||
#
|
||||
# %title
|
||||
# = @title
|
||||
# \- MySite
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <title>
|
||||
# MyPage
|
||||
# - MySite
|
||||
# </title>
|
||||
#
|
||||
# ==== |
|
||||
#
|
||||
# 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:
|
||||
#
|
||||
# <whoo>
|
||||
# <hoo>
|
||||
# I think this might get pretty long so I should probably make it multiline so it doesn't look awful.
|
||||
# </hoo>
|
||||
# </whoo>
|
||||
#
|
||||
# ==== :
|
||||
#
|
||||
# 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.
|
||||
# This is useful for large blocks of text without HTML tags,
|
||||
# when you don't want lines starting with <tt>.</tt> or <tt>-</tt>
|
||||
# to be parsed.
|
||||
#
|
||||
# [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.
|
||||
#
|
||||
# [preserve] Inserts the filtered text into the template with whitespace preserved.
|
||||
# <tt>preserve</tt>d blocks of text aren't indented,
|
||||
# and newlines are replaced with the HTML escape code for newlines,
|
||||
# to preserve nice-looking output.
|
||||
#
|
||||
# [erb] Parses the filtered text with ERB, like an RHTML template.
|
||||
# Not available if the <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
|
||||
#
|
||||
# ==== =
|
||||
#
|
||||
# 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>
|
||||
#
|
||||
# You can also use two equal signs, <tt>==</tt>,
|
||||
# along with conventional Ruby string-embedding syntax
|
||||
# to easily embed Ruby code in otherwise static text.
|
||||
# For example:
|
||||
#
|
||||
# %p
|
||||
# == 1 + 1 = #{1 + 1}
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <p>
|
||||
# 1 + 1 = 2
|
||||
# </p>
|
||||
#
|
||||
# ==== -
|
||||
#
|
||||
# The hyphen character makes the text following it into "silent script":
|
||||
# Ruby script that is evaluated, but not output.
|
||||
#
|
||||
# <b>It is not recommended that you use this widely;
|
||||
# almost all processing code and logic should be restricted
|
||||
# to the Controller, the Helper, or partials.</b>
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# - foo = "hello"
|
||||
# - foo << " there"
|
||||
# - foo << " you!"
|
||||
# %p= foo
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <p>
|
||||
# hello there you!
|
||||
# </p>
|
||||
#
|
||||
# ===== Blocks
|
||||
#
|
||||
# Ruby blocks, like XHTML tags, don't need to be explicitly closed in Haml.
|
||||
# Rather, they're automatically closed, based on indentation.
|
||||
# A block begins whenever the indentation is increased
|
||||
# after a silent script command.
|
||||
# It ends when the indentation decreases
|
||||
# (as long as it's not an +else+ clause or something similar).
|
||||
# For example:
|
||||
#
|
||||
# - (42...47).each do |i|
|
||||
# %p= i
|
||||
# %p See, I can count!
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <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>
|
||||
#
|
||||
# ==== -#
|
||||
#
|
||||
# The hyphen followed immediately by the pound sign
|
||||
# signifies a silent comment.
|
||||
# Any text following this isn't rendered in the resulting document
|
||||
# at all.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# %p foo
|
||||
# -# This is a comment
|
||||
# %p bar
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# <p>foo</p>
|
||||
# <p>bar</p>
|
||||
#
|
||||
# == Other Useful Things
|
||||
#
|
||||
# === Helpers
|
||||
#
|
||||
# Haml offers a bunch of helpers that are useful
|
||||
# for doing stuff like preserving whitespace,
|
||||
# creating nicely indented output for user-defined helpers,
|
||||
# and other useful things.
|
||||
# The helpers are all documented in the Haml::Helpers and Haml::Helpers::ActionViewExtensions modules.
|
||||
#
|
||||
# === Haml Options
|
||||
#
|
||||
# Options can be set by setting the hash <tt>Haml::Template.options</tt>
|
||||
# from <tt>environment.rb</tt> in Rails,
|
||||
# or by passing an options hash to Haml::Engine.
|
||||
# Available options are:
|
||||
#
|
||||
# [<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.
|
||||
#
|
||||
# [<tt>:attr_wrapper</tt>] The character that should wrap element attributes.
|
||||
# This defaults to <tt>'</tt> (an apostrophe). Characters
|
||||
# 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>:filename</tt>] The name of the Haml file being parsed.
|
||||
# This is only used as information when exceptions are raised.
|
||||
# This is automatically assigned when working through ActionView,
|
||||
# so it's really only useful for the user to assign
|
||||
# when dealing with Haml programatically.
|
||||
#
|
||||
# [<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.
|
||||
#
|
||||
# [<tt>:locals</tt>] The local variables that will be available within the
|
||||
# template. For instance, if <tt>:locals</tt> is
|
||||
# <tt>{ :foo => "bar" }</tt>, then within the template,
|
||||
# <tt>= foo</tt> will produce <tt>bar</tt>.
|
||||
#
|
||||
# [<tt>:autoclose</tt>] A list of tag names that should be automatically self-closed
|
||||
# if they have no content.
|
||||
# Defaults to <tt>['meta', 'img', 'link', 'script', 'br', 'hr']</tt>.
|
||||
#
|
||||
module Haml; end
|
||||
|
||||
require 'haml/engine'
|
@ -1,213 +0,0 @@
|
||||
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
|
||||
|
||||
# Gets the current tabulation of the document.
|
||||
def tabulation
|
||||
@real_tabs + @tabulation
|
||||
end
|
||||
|
||||
# Sets the current tabulation of the document.
|
||||
def tabulation=(val)
|
||||
val = val - @real_tabs
|
||||
@tabulation = val > -1 ? val : 0
|
||||
end
|
||||
|
||||
# Creates a new buffer.
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
@quote_escape = options[:attr_wrapper] == '"' ? """ : "'"
|
||||
@other_quote_char = options[:attr_wrapper] == '"' ? "'" : '"'
|
||||
@buffer = ""
|
||||
@one_liner_pending = false
|
||||
@tabulation = 0
|
||||
|
||||
# The number of tabs that Engine thinks we should have
|
||||
# @real_tabs + @tabulation is the number of tabs actually output
|
||||
@real_tabs = 0
|
||||
end
|
||||
|
||||
# Renders +text+ with the proper tabulation. This also deals with
|
||||
# making a possible one-line tag one line or not.
|
||||
def push_text(text, tabulation)
|
||||
if @one_liner_pending && Buffer.one_liner?(text)
|
||||
@buffer << text
|
||||
else
|
||||
if @one_liner_pending
|
||||
@buffer << "\n"
|
||||
@one_liner_pending = false
|
||||
end
|
||||
@buffer << "#{tabs(tabulation)}#{text}\n"
|
||||
end
|
||||
end
|
||||
|
||||
# Properly formats the output of a script that was run in the
|
||||
# instance_eval.
|
||||
def push_script(result, tabulation, flattened)
|
||||
if flattened
|
||||
result = Haml::Helpers.find_and_preserve(result)
|
||||
end
|
||||
|
||||
result = result.to_s
|
||||
while result[-1] == 10 # \n
|
||||
# String#chomp is slow
|
||||
result = result[0...-1]
|
||||
end
|
||||
|
||||
result = result.gsub("\n", "\n#{tabs(tabulation)}")
|
||||
push_text result, tabulation
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
def open_prerendered_tag(tag, tabulation)
|
||||
@buffer << "#{tabs(tabulation)}#{tag}"
|
||||
@real_tabs += 1
|
||||
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, obj_ref, attributes_hash)
|
||||
attributes = class_id
|
||||
if attributes_hash
|
||||
attributes_hash.keys.each { |key| attributes_hash[key.to_s] = attributes_hash.delete(key) }
|
||||
self.class.merge_attrs(attributes, attributes_hash)
|
||||
end
|
||||
self.class.merge_attrs(attributes, parse_object_ref(obj_ref)) if obj_ref
|
||||
|
||||
@one_liner_pending = false
|
||||
if atomic
|
||||
str = " />\n"
|
||||
elsif try_one_line
|
||||
@one_liner_pending = true
|
||||
str = ">"
|
||||
else
|
||||
str = ">\n"
|
||||
end
|
||||
@buffer << "#{tabs(tabulation)}<#{name}#{build_attributes(attributes)}#{str}"
|
||||
@real_tabs += 1
|
||||
end
|
||||
|
||||
def self.merge_attrs(to, from)
|
||||
if to['id'] && from['id']
|
||||
to['id'] << '_' << from.delete('id')
|
||||
end
|
||||
|
||||
if to['class'] && from['class']
|
||||
# Make sure we don't duplicate class names
|
||||
from['class'] = (from['class'].split(' ') | to['class'].split(' ')).join(' ')
|
||||
end
|
||||
|
||||
to.merge!(from)
|
||||
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
|
||||
|
||||
# Opens an XHTML comment.
|
||||
def open_comment(try_one_line, conditional, tabulation)
|
||||
conditional << ">" if conditional
|
||||
@buffer << "#{tabs(tabulation)}<!--#{conditional.to_s} "
|
||||
if try_one_line
|
||||
@one_liner_pending = true
|
||||
else
|
||||
@buffer << "\n"
|
||||
@real_tabs += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Closes an XHTML comment.
|
||||
def close_comment(has_conditional, tabulation)
|
||||
close_tag = has_conditional ? "<![endif]-->" : "-->"
|
||||
if @one_liner_pending
|
||||
@buffer << " #{close_tag}\n"
|
||||
@one_liner_pending = false
|
||||
else
|
||||
push_text(close_tag, tabulation)
|
||||
end
|
||||
end
|
||||
|
||||
# Some of these methods are exposed as public class methods
|
||||
# so they can be re-used in helpers.
|
||||
|
||||
# Takes a hash and builds a list of XHTML attributes from it, returning
|
||||
# the result.
|
||||
def build_attributes(attributes = {})
|
||||
result = attributes.collect do |a,v|
|
||||
v = v.to_s
|
||||
unless v.nil? || v.empty?
|
||||
attr_wrapper = @options[:attr_wrapper]
|
||||
if v.include? attr_wrapper
|
||||
if v.include? @other_quote_char
|
||||
v = v.gsub(attr_wrapper, @quote_escape)
|
||||
else
|
||||
attr_wrapper = @other_quote_char
|
||||
end
|
||||
end
|
||||
" #{a}=#{attr_wrapper}#{v}#{attr_wrapper}"
|
||||
end
|
||||
end
|
||||
result.compact.sort.join
|
||||
end
|
||||
|
||||
# Returns whether or not the given value is short enough to be rendered
|
||||
# on one line.
|
||||
def self.one_liner?(value)
|
||||
value.length <= ONE_LINER_LENGTH && value.scan(/\n/).empty?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@tab_cache = {}
|
||||
# Gets <tt>count</tt> tabs. Mostly for internal use.
|
||||
def tabs(count)
|
||||
@real_tabs = count
|
||||
tabs = count + @tabulation
|
||||
' ' * tabs
|
||||
@@tab_cache[tabs] ||= ' ' * tabs
|
||||
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]
|
||||
# Let's make sure the value isn't nil. If it is, return the default Hash.
|
||||
return {} if ref.nil?
|
||||
class_name = underscore(ref.class)
|
||||
id = "#{class_name}_#{ref.id || 'new'}"
|
||||
|
||||
{'id' => id, 'class' => class_name}
|
||||
end
|
||||
|
||||
# Changes a word from camel case to underscores.
|
||||
# Based on the method of the same name in Rails' Inflector,
|
||||
# but copied here so it'll run properly without Rails.
|
||||
def underscore(camel_cased_word)
|
||||
camel_cased_word.to_s.gsub(/::/, '_').
|
||||
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
||||
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
||||
tr("-", "_").
|
||||
downcase
|
||||
end
|
||||
end
|
||||
end
|
@ -1,876 +0,0 @@
|
||||
require 'haml/helpers'
|
||||
require 'haml/buffer'
|
||||
require 'haml/filters'
|
||||
require 'haml/error'
|
||||
require 'haml/util'
|
||||
|
||||
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 <tt>to_html</tt> to render the template. For example:
|
||||
#
|
||||
# template = File.read('templates/really_cool_template.haml')
|
||||
# haml_engine = Haml::Engine.new(template)
|
||||
# output = haml_engine.to_html
|
||||
# puts output
|
||||
class Engine
|
||||
# Allow reading and writing of the options hash
|
||||
attr :options, true
|
||||
|
||||
# Designates an XHTML/XML element.
|
||||
ELEMENT = ?%
|
||||
|
||||
# Designates a <tt><div></tt> element with the given class.
|
||||
DIV_CLASS = ?.
|
||||
|
||||
# Designates a <tt><div></tt> element with the given id.
|
||||
DIV_ID = ?#
|
||||
|
||||
# Designates an XHTML/XML comment.
|
||||
COMMENT = ?/
|
||||
|
||||
# Designates an XHTML doctype.
|
||||
DOCTYPE = ?!
|
||||
|
||||
# Designates script, the result of which is output.
|
||||
SCRIPT = ?=
|
||||
|
||||
# Designates script, the result of which is flattened and output.
|
||||
FLAT_SCRIPT = ?~
|
||||
|
||||
# Designates script which is run but not output.
|
||||
SILENT_SCRIPT = ?-
|
||||
|
||||
# When following SILENT_SCRIPT, designates a comment that is not output.
|
||||
SILENT_COMMENT = ?#
|
||||
|
||||
# Designates a non-parsed line.
|
||||
ESCAPE = ?\\
|
||||
|
||||
# Designates a block of filtered text.
|
||||
FILTER = ?:
|
||||
|
||||
# Designates a non-parsed line. Not actually a character.
|
||||
PLAIN_TEXT = -1
|
||||
|
||||
# Keeps track of the ASCII values of the characters that begin a
|
||||
# specially-interpreted line.
|
||||
SPECIAL_CHARACTERS = [
|
||||
ELEMENT,
|
||||
DIV_CLASS,
|
||||
DIV_ID,
|
||||
COMMENT,
|
||||
DOCTYPE,
|
||||
SCRIPT,
|
||||
FLAT_SCRIPT,
|
||||
SILENT_SCRIPT,
|
||||
ESCAPE,
|
||||
FILTER
|
||||
]
|
||||
|
||||
# The value of the character that designates that a line is part
|
||||
# of a multiline string.
|
||||
MULTILINE_CHAR_VALUE = ?|
|
||||
|
||||
# Characters that designate that a multiline string may be about
|
||||
# to begin.
|
||||
MULTILINE_STARTERS = SPECIAL_CHARACTERS - [?/]
|
||||
|
||||
# Keywords that appear in the middle of a Ruby block with lowered
|
||||
# indentation. If a block has been started using indentation,
|
||||
# lowering the indentation with one of these won't end the block.
|
||||
# For example:
|
||||
#
|
||||
# - if foo
|
||||
# %p yes!
|
||||
# - else
|
||||
# %p no!
|
||||
#
|
||||
# The block is ended after <tt>%p no!</tt>, because <tt>else</tt>
|
||||
# is a member of this array.
|
||||
MID_BLOCK_KEYWORDS = ['else', 'elsif', 'rescue', 'ensure', 'when']
|
||||
|
||||
# The Regex that matches an HTML comment command.
|
||||
COMMENT_REGEX = /\/(\[[\w\s\.]*\])?(.*)/
|
||||
|
||||
# The Regex that matches a Doctype command.
|
||||
DOCTYPE_REGEX = /(\d\.\d)?[\s]*([a-z]*)/i
|
||||
|
||||
# The Regex that matches an HTML tag command.
|
||||
TAG_REGEX = /[%]([-:\w]+)([-\w\.\#]*)(\{.*\})?(\[.*\])?([=\/\~]?)?(.*)?/
|
||||
|
||||
# The Regex that matches a literal string or symbol value
|
||||
LITERAL_VALUE_REGEX = /^\s*(:(\w*)|(('|")([^\\\#'"]*?)\4))\s*$/
|
||||
|
||||
FLAT_WARNING = <<END
|
||||
Haml deprecation warning:
|
||||
The ~ command is deprecated and will be removed in future Haml versions.
|
||||
Use the :preserve filter, the preserve helper, or the find_and_preserve
|
||||
helper instead.
|
||||
END
|
||||
|
||||
# Creates a new instace of Haml::Engine that will compile the given
|
||||
# template string when <tt>render</tt> is called.
|
||||
# See README for available options.
|
||||
#
|
||||
#--
|
||||
# When adding options, remember to add information about them
|
||||
# to README!
|
||||
#++
|
||||
#
|
||||
def initialize(template, l_options = {})
|
||||
@options = {
|
||||
:suppress_eval => false,
|
||||
:attr_wrapper => "'",
|
||||
:locals => {},
|
||||
:autoclose => ['meta', 'img', 'link', 'br', 'hr', 'input', 'area'],
|
||||
:filters => {
|
||||
'sass' => Sass::Engine,
|
||||
'plain' => Haml::Filters::Plain,
|
||||
'preserve' => Haml::Filters::Preserve }
|
||||
}
|
||||
|
||||
if !NOT_LOADED.include? 'redcloth'
|
||||
@options[:filters].merge!({
|
||||
'redcloth' => RedCloth,
|
||||
'textile' => Haml::Filters::Textile,
|
||||
'markdown' => Haml::Filters::Markdown
|
||||
})
|
||||
end
|
||||
if !NOT_LOADED.include? 'bluecloth'
|
||||
@options[:filters]['markdown'] = Haml::Filters::Markdown
|
||||
end
|
||||
|
||||
@options.rec_merge! l_options
|
||||
|
||||
unless @options[:suppress_eval]
|
||||
@options[:filters].merge!({
|
||||
'erb' => ERB,
|
||||
'ruby' => Haml::Filters::Ruby
|
||||
})
|
||||
end
|
||||
@options[:filters].rec_merge! l_options[:filters] if l_options[:filters]
|
||||
|
||||
@template = template.strip #String
|
||||
@to_close_stack = []
|
||||
@output_tabs = 0
|
||||
@template_tabs = 0
|
||||
@index = 0
|
||||
|
||||
# This is the base tabulation of the currently active
|
||||
# flattened block. -1 signifies that there is no such block.
|
||||
@flat_spaces = -1
|
||||
|
||||
begin
|
||||
# Only do the first round of pre-compiling if we really need to.
|
||||
# They might be passing in the precompiled string.
|
||||
requires_precompile = true
|
||||
if @@method_names[@template]
|
||||
# Check that the compiled method supports a superset of the local assigns we want to do
|
||||
supported_assigns = @@supported_local_assigns[@template]
|
||||
requires_precompile = !@options[:locals].keys.all? {|var| supported_assigns.include? var}
|
||||
end
|
||||
do_precompile if requires_precompile
|
||||
rescue Haml::Error => e
|
||||
e.add_backtrace_entry(@index, @options[:filename])
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
# Processes the template and returns the result as a string.
|
||||
def render(scope = Object.new, &block)
|
||||
@scope_object = scope
|
||||
@buffer = Haml::Buffer.new(@options)
|
||||
|
||||
# Run the compiled evaluator function
|
||||
compile &block
|
||||
|
||||
# Return the result string
|
||||
@buffer.buffer
|
||||
end
|
||||
|
||||
alias_method :to_html, :render
|
||||
|
||||
# This method is deprecated and shouldn't be used.
|
||||
def precompiled
|
||||
$stderr.puts <<END
|
||||
The Haml precompiled method and :precompiled option
|
||||
are deprecated and will be removed in version 2.0.
|
||||
Haml::Engine now automatically handles caching.
|
||||
END
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
#Precompile each line
|
||||
def do_precompile
|
||||
@precompiled = ''
|
||||
method_name = assign_method_name(@template, options[:filename])
|
||||
push_silent <<-END
|
||||
def #{method_name}(_haml_local_assigns)
|
||||
@haml_is_haml = true
|
||||
_hamlout = @haml_stack[-1]
|
||||
_erbout = _hamlout.buffer
|
||||
END
|
||||
|
||||
supported_local_assigns = {}
|
||||
@@supported_local_assigns[@template] = supported_local_assigns
|
||||
@options[:locals].each do |k,v|
|
||||
supported_local_assigns[k] = true
|
||||
push_silent "#{k} = _haml_local_assigns[:#{k}]"
|
||||
end
|
||||
|
||||
old_line = nil
|
||||
old_index = nil
|
||||
old_spaces = nil
|
||||
old_tabs = nil
|
||||
old_uline = nil
|
||||
(@template + "\n-#\n-#").each_with_index do |line, index|
|
||||
spaces, tabs = count_soft_tabs(line)
|
||||
uline = line.lstrip.chomp
|
||||
line = uline.rstrip
|
||||
|
||||
if !line.empty?
|
||||
if old_line
|
||||
block_opened = tabs > old_tabs && !line.empty?
|
||||
|
||||
suppress_render = handle_multiline(old_tabs, old_line, old_index) unless @flat_spaces != -1
|
||||
|
||||
if !suppress_render
|
||||
line_empty = old_line.empty?
|
||||
|
||||
process_indent(old_tabs, old_line) unless line_empty
|
||||
flat = @flat_spaces != -1
|
||||
|
||||
|
||||
if !flat && old_spaces != old_tabs * 2
|
||||
raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.")
|
||||
end
|
||||
|
||||
if flat
|
||||
push_flat(old_uline, old_spaces)
|
||||
elsif !line_empty && !@haml_comment
|
||||
process_line(old_line, old_index, block_opened)
|
||||
end
|
||||
|
||||
if @flat_spaces == -1 && tabs - old_tabs > 1
|
||||
raise SyntaxError.new("Illegal Indentation: Indenting more than once per line is illegal.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
old_line = line
|
||||
old_index = index
|
||||
old_spaces = spaces
|
||||
old_tabs = tabs
|
||||
old_uline = uline
|
||||
elsif @flat_spaces != -1
|
||||
process_indent(old_tabs, old_line) unless old_line.empty?
|
||||
|
||||
if @flat_spaces != -1
|
||||
push_flat(old_line, old_spaces)
|
||||
old_line = ''
|
||||
old_uline = ''
|
||||
old_spaces = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Close all the open tags
|
||||
@template_tabs.times { close }
|
||||
|
||||
push_silent "@haml_is_haml = false\nend\n"
|
||||
end
|
||||
|
||||
# Processes and deals with lowering indentation.
|
||||
def process_indent(count, line)
|
||||
if count <= @template_tabs && @template_tabs > 0
|
||||
to_close = @template_tabs - count
|
||||
|
||||
to_close.times do |i|
|
||||
offset = to_close - 1 - i
|
||||
unless offset == 0 && mid_block_keyword?(line)
|
||||
close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Processes a single line of Haml.
|
||||
#
|
||||
# This method doesn't return anything; it simply processes the line and
|
||||
# adds the appropriate code to <tt>@precompiled</tt>.
|
||||
def process_line(line, index, block_opened)
|
||||
@index = index + 1
|
||||
@block_opened = block_opened
|
||||
|
||||
case line[0]
|
||||
when DIV_CLASS, DIV_ID
|
||||
render_div(line)
|
||||
when ELEMENT
|
||||
render_tag(line)
|
||||
when COMMENT
|
||||
render_comment(line)
|
||||
when SCRIPT
|
||||
sub_line = line[1..-1]
|
||||
if sub_line[0] == SCRIPT
|
||||
push_script(unescape_interpolation(sub_line[1..-1].strip), false)
|
||||
else
|
||||
push_script(sub_line, false)
|
||||
end
|
||||
when FLAT_SCRIPT
|
||||
push_flat_script(line[1..-1])
|
||||
when SILENT_SCRIPT
|
||||
sub_line = line[1..-1]
|
||||
unless sub_line[0] == SILENT_COMMENT
|
||||
mbk = mid_block_keyword?(line)
|
||||
push_silent(sub_line, !mbk, true)
|
||||
if (@block_opened && !mbk) || line[1..-1].split(' ', 2)[0] == "case"
|
||||
push_and_tabulate([:script])
|
||||
end
|
||||
else
|
||||
start_haml_comment
|
||||
end
|
||||
when FILTER
|
||||
name = line[1..-1].downcase
|
||||
start_filtered(options[:filters][name.to_s] || name)
|
||||
when DOCTYPE
|
||||
if line[0...3] == '!!!'
|
||||
render_doctype(line)
|
||||
else
|
||||
push_plain line
|
||||
end
|
||||
when ESCAPE
|
||||
push_plain line[1..-1]
|
||||
else
|
||||
push_plain line
|
||||
end
|
||||
end
|
||||
|
||||
# Returns whether or not the line is a silent script line with one
|
||||
# of Ruby's mid-block keywords.
|
||||
def mid_block_keyword?(line)
|
||||
line.length > 2 && line[0] == SILENT_SCRIPT && MID_BLOCK_KEYWORDS.include?(line[1..-1].split[0])
|
||||
end
|
||||
|
||||
# Deals with all the logic of figuring out whether a given line is
|
||||
# the beginning, continuation, or end of a multiline sequence.
|
||||
#
|
||||
# This returns whether or not the line should be
|
||||
# rendered normally.
|
||||
def handle_multiline(count, line, index)
|
||||
suppress_render = false
|
||||
# Multilines are denoting by ending with a `|` (124)
|
||||
if is_multiline?(line) && @multiline_buffer
|
||||
# A multiline string is active, and is being continued
|
||||
@multiline_buffer += line[0...-1]
|
||||
suppress_render = true
|
||||
elsif is_multiline?(line) && (MULTILINE_STARTERS.include? line[0])
|
||||
# A multiline string has just been activated, start adding the lines
|
||||
@multiline_buffer = line[0...-1]
|
||||
@multiline_count = count
|
||||
@multiline_index = index
|
||||
process_indent(count, line)
|
||||
suppress_render = true
|
||||
elsif @multiline_buffer
|
||||
# A multiline string has just ended, make line into the result
|
||||
unless line.empty?
|
||||
process_line(@multiline_buffer, @multiline_index, count > @multiline_count)
|
||||
@multiline_buffer = nil
|
||||
end
|
||||
end
|
||||
|
||||
return suppress_render
|
||||
end
|
||||
|
||||
# Checks whether or not +line+ is in a multiline sequence.
|
||||
def is_multiline?(line) # ' '[0] == 32
|
||||
line && line.length > 1 && line[-1] == MULTILINE_CHAR_VALUE && line[-2] == 32
|
||||
end
|
||||
|
||||
# Method for generating compiled method names basically ripped out of ActiveView::Base
|
||||
# If Haml is to be used as a standalone module without rails and still use the precompiled
|
||||
# methods technique, it will end up duplicating this stuff. I can't decide whether
|
||||
# checking compile times to decide whether to recompile a template belongs in here or
|
||||
# out in template.rb
|
||||
@@method_names = {}
|
||||
@@supported_local_assigns = {}
|
||||
@@render_method_count = 0
|
||||
def assign_method_name(template, file_name)
|
||||
@@render_method_count += 1
|
||||
@@method_names[template] = "_render_haml_#{@@render_method_count}".intern
|
||||
end
|
||||
|
||||
module CompiledTemplates
|
||||
# holds compiled template code
|
||||
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(&block)
|
||||
# Set the local variables pointing to the buffer
|
||||
buffer = @buffer
|
||||
@scope_object.extend Haml::Helpers
|
||||
@scope_object.instance_eval do
|
||||
@haml_stack ||= Array.new
|
||||
@haml_stack.push(buffer)
|
||||
|
||||
class << self
|
||||
attr :haml_lineno # :nodoc:
|
||||
end
|
||||
end
|
||||
@scope_object.class.instance_eval do
|
||||
include CompiledTemplates
|
||||
end
|
||||
|
||||
begin
|
||||
method_name = @@method_names[@template]
|
||||
|
||||
unless @scope_object.respond_to?(method_name)
|
||||
CompiledTemplates.module_eval @precompiled
|
||||
end
|
||||
@scope_object.send(method_name, options[:locals], &block)
|
||||
rescue Exception => e
|
||||
class << e
|
||||
include Haml::Error
|
||||
end
|
||||
|
||||
lineno = @scope_object.haml_lineno
|
||||
|
||||
# Get information from the exception and format it so that
|
||||
# Rails can understand it.
|
||||
compile_error = e.message.scan(/\(eval\):([0-9]*):in `[-_a-zA-Z]*': compile error/)[0]
|
||||
|
||||
if compile_error
|
||||
if @precompiled
|
||||
eval_line = compile_error[0].to_i
|
||||
line_marker = @precompiled.split("\n")[0...eval_line].grep(/@haml_lineno = [0-9]*/)[-1]
|
||||
lineno = line_marker.scan(/[0-9]+/)[0].to_i if line_marker
|
||||
else
|
||||
lineno = -1
|
||||
end
|
||||
end
|
||||
|
||||
e.add_backtrace_entry(lineno, @options[:filename])
|
||||
raise e
|
||||
end
|
||||
|
||||
# Get rid of the current buffer
|
||||
@scope_object.instance_eval do
|
||||
@haml_stack.pop
|
||||
end
|
||||
end
|
||||
|
||||
# Evaluates <tt>text</tt> in the context of <tt>@scope_object</tt>, but
|
||||
# does not output the result.
|
||||
def push_silent(text, add_index = false, can_suppress = false)
|
||||
unless (can_suppress && options[:suppress_eval])
|
||||
if add_index
|
||||
@precompiled << "@haml_lineno = #{@index}\n#{text}\n"
|
||||
else
|
||||
# Not really DRY, but probably faster
|
||||
@precompiled << "#{text}\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Adds <tt>text</tt> to <tt>@buffer</tt> with appropriate tabulation
|
||||
# without parsing it.
|
||||
def push_text(text)
|
||||
@precompiled << "_hamlout.push_text(#{text.dump}, #{@output_tabs})\n"
|
||||
end
|
||||
|
||||
# Renders a block of text as plain text.
|
||||
# Also checks for an illegally opened block.
|
||||
def push_plain(text)
|
||||
if @block_opened
|
||||
raise SyntaxError.new("Illegal Nesting: Nesting within plain text is illegal.")
|
||||
end
|
||||
push_text text
|
||||
end
|
||||
|
||||
# Adds +text+ to <tt>@buffer</tt> while flattening text.
|
||||
def push_flat(text, spaces)
|
||||
tabulation = spaces - @flat_spaces
|
||||
tabulation = tabulation > -1 ? tabulation : 0
|
||||
@filter_buffer << "#{' ' * tabulation}#{text}\n"
|
||||
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)
|
||||
unless options[:suppress_eval]
|
||||
push_silent("haml_temp = #{text}", true)
|
||||
out = "haml_temp = _hamlout.push_script(haml_temp, #{@output_tabs}, #{flattened})\n"
|
||||
if @block_opened
|
||||
push_and_tabulate([:loud, out])
|
||||
else
|
||||
@precompiled << out
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Causes <tt>text</tt> to be evaluated, and Haml::Helpers#find_and_flatten
|
||||
# to be run on it afterwards.
|
||||
def push_flat_script(text)
|
||||
if text.empty?
|
||||
raise SyntaxError.new("Tag has no content.")
|
||||
else
|
||||
push_script(text, true)
|
||||
end
|
||||
end
|
||||
|
||||
def start_haml_comment
|
||||
if @block_opened
|
||||
@haml_comment = true
|
||||
push_and_tabulate([:haml_comment])
|
||||
end
|
||||
end
|
||||
|
||||
# Closes the most recent item in <tt>@to_close_stack</tt>.
|
||||
def close
|
||||
tag, value = @to_close_stack.pop
|
||||
case tag
|
||||
when :script
|
||||
close_block
|
||||
when :comment
|
||||
close_comment value
|
||||
when :element
|
||||
close_tag value
|
||||
when :loud
|
||||
close_loud value
|
||||
when :filtered
|
||||
close_filtered value
|
||||
when :haml_comment
|
||||
close_haml_comment
|
||||
end
|
||||
end
|
||||
|
||||
# Puts a line in <tt>@precompiled</tt> that will add the closing tag of
|
||||
# the most recently opened tag.
|
||||
def close_tag(tag)
|
||||
@output_tabs -= 1
|
||||
@template_tabs -= 1
|
||||
@precompiled << "_hamlout.close_tag(#{tag.dump}, #{@output_tabs})\n"
|
||||
end
|
||||
|
||||
# Closes a Ruby block.
|
||||
def close_block
|
||||
push_silent "end", false, true
|
||||
@template_tabs -= 1
|
||||
end
|
||||
|
||||
# Closes a comment.
|
||||
def close_comment(has_conditional)
|
||||
@output_tabs -= 1
|
||||
@template_tabs -= 1
|
||||
push_silent "_hamlout.close_comment(#{has_conditional}, #{@output_tabs})"
|
||||
end
|
||||
|
||||
# Closes a loud Ruby block.
|
||||
def close_loud(command)
|
||||
push_silent 'end', false, true
|
||||
@precompiled << command
|
||||
@template_tabs -= 1
|
||||
end
|
||||
|
||||
# Closes a filtered block.
|
||||
def close_filtered(filter)
|
||||
@flat_spaces = -1
|
||||
if filter.is_a? String
|
||||
if filter == 'redcloth' || filter == 'markdown' || filter == 'textile'
|
||||
raise HamlError.new("You must have the RedCloth gem installed to use #{filter}")
|
||||
else
|
||||
raise HamlError.new("Filter \"#{filter}\" is not defined!")
|
||||
end
|
||||
else
|
||||
filtered = filter.new(@filter_buffer).render
|
||||
|
||||
unless filter == Haml::Filters::Preserve
|
||||
push_text(filtered.rstrip.gsub("\n", "\n#{' ' * @output_tabs}"))
|
||||
else
|
||||
push_silent("_hamlout.buffer << #{filtered.dump} << \"\\n\"\n")
|
||||
end
|
||||
end
|
||||
|
||||
@filter_buffer = nil
|
||||
@template_tabs -= 1
|
||||
end
|
||||
|
||||
def close_haml_comment
|
||||
@haml_comment = false
|
||||
@template_tabs -= 1
|
||||
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-Z0-9]+)/) do |type, property|
|
||||
case type
|
||||
when '.'
|
||||
if attributes['class']
|
||||
attributes['class'] += " "
|
||||
else
|
||||
attributes['class'] = ""
|
||||
end
|
||||
attributes['class'] += property
|
||||
when '#'
|
||||
attributes['id'] = property
|
||||
end
|
||||
end
|
||||
attributes
|
||||
end
|
||||
|
||||
def parse_literal_value(text)
|
||||
text.match(LITERAL_VALUE_REGEX)
|
||||
|
||||
# $2 holds the value matched by a symbol, but is nil for a string match
|
||||
# $5 holds the value matched by a string
|
||||
$2 || $5
|
||||
end
|
||||
|
||||
def parse_literal_hash(text)
|
||||
unless text
|
||||
return {}
|
||||
end
|
||||
|
||||
attributes = {}
|
||||
if inner = text.scan(/^\{(.*)\}$/)[0]
|
||||
inner[0].split(',').each do |attrib|
|
||||
key, value, more = attrib.split('=>')
|
||||
|
||||
# Make sure the key and value and only the key and value exist
|
||||
# Otherwise, it's too complicated and we'll defer it to the actual Ruby parser
|
||||
if more || (key = parse_literal_value(key)).nil? ||
|
||||
(value = parse_literal_value(value)).nil?
|
||||
return nil
|
||||
end
|
||||
|
||||
attributes[key] = value
|
||||
end
|
||||
end
|
||||
attributes
|
||||
end
|
||||
|
||||
def build_attributes(attributes = {})
|
||||
@quote_escape = @options[:attr_wrapper] == '"' ? """ : "'"
|
||||
@other_quote_char = @options[:attr_wrapper] == '"' ? "'" : '"'
|
||||
|
||||
result = attributes.collect do |a,v|
|
||||
v = v.to_s
|
||||
unless v.nil? || v.empty?
|
||||
attr_wrapper = @options[:attr_wrapper]
|
||||
if v.include? attr_wrapper
|
||||
if v.include? @other_quote_char
|
||||
# An imperfection in LITERAL_VALUE_REGEX prevents this
|
||||
# from ever actually being reached,
|
||||
# but in case it becomes possible,
|
||||
# I'm leaving it in.
|
||||
v = v.gsub(attr_wrapper, @quote_escape)
|
||||
else
|
||||
attr_wrapper = @other_quote_char
|
||||
end
|
||||
end
|
||||
" #{a}=#{attr_wrapper}#{v}#{attr_wrapper}"
|
||||
end
|
||||
end
|
||||
result.sort.join
|
||||
end
|
||||
|
||||
def prerender_tag(name, atomic, attributes)
|
||||
if atomic
|
||||
str = " />"
|
||||
else
|
||||
str = ">"
|
||||
end
|
||||
|
||||
"<#{name}#{build_attributes(attributes)}#{str}"
|
||||
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)
|
||||
matched = false
|
||||
line.scan(TAG_REGEX) do |tag_name, attributes, attributes_hash, object_ref, action, value|
|
||||
matched = true
|
||||
value = value.to_s.strip
|
||||
|
||||
case action
|
||||
when '/'
|
||||
atomic = true
|
||||
when '=', '~'
|
||||
parse = true
|
||||
|
||||
if value[0] == ?=
|
||||
value = value[1..-1].strip.dump.gsub('\\#', '#')
|
||||
end
|
||||
end
|
||||
|
||||
flattened = (action == '~')
|
||||
|
||||
value_exists = !value.empty?
|
||||
literal_attributes = parse_literal_hash(attributes_hash)
|
||||
attributes_hash = "{nil}" if attributes_hash.nil? || literal_attributes || @options[:suppress_eval]
|
||||
object_ref = "nil" if object_ref.nil? || @options[:suppress_eval]
|
||||
|
||||
if attributes =~ /[\.#](\.|#|\z)/
|
||||
raise SyntaxError.new("Illegal element: classes and ids must have values. Use %div instead.")
|
||||
end
|
||||
|
||||
# Preparse the attributes hash
|
||||
attributes = parse_class_and_id(attributes)
|
||||
Buffer.merge_attrs(attributes, literal_attributes) if literal_attributes
|
||||
|
||||
if @block_opened
|
||||
if atomic
|
||||
raise SyntaxError.new("Illegal Nesting: Nesting within an atomic tag is illegal.")
|
||||
elsif action == '=' || value_exists
|
||||
raise SyntaxError.new("Illegal Nesting: Nesting within a tag that already has content is illegal.")
|
||||
end
|
||||
elsif atomic && value_exists
|
||||
raise SyntaxError.new("Atomic tags can't have content.")
|
||||
elsif parse && !value_exists
|
||||
raise SyntaxError.new("Tag has no content.")
|
||||
end
|
||||
|
||||
if !@block_opened && !value_exists && @options[:autoclose].include?(tag_name)
|
||||
atomic = true
|
||||
end
|
||||
|
||||
do_one_liner = value_exists && !parse && Buffer.one_liner?(value)
|
||||
|
||||
if object_ref == "nil" && attributes_hash == "{nil}" && !flattened && (do_one_liner || !value_exists)
|
||||
# This means that we can render the tag directly to text and not process it in the buffer
|
||||
open_tag = prerender_tag(tag_name, atomic, attributes)
|
||||
|
||||
if do_one_liner
|
||||
open_tag += value
|
||||
open_tag += "</#{tag_name}>"
|
||||
end
|
||||
|
||||
open_tag += "\n"
|
||||
|
||||
push_silent "_hamlout.open_prerendered_tag(#{open_tag.dump}, #{@output_tabs})"
|
||||
return if do_one_liner
|
||||
else
|
||||
push_silent "_hamlout.open_tag(#{tag_name.inspect}, #{@output_tabs}, #{atomic.inspect}, #{value_exists.inspect}, #{attributes.inspect}, #{object_ref}, #{attributes_hash[1...-1]})", true
|
||||
end
|
||||
|
||||
unless atomic
|
||||
push_and_tabulate([:element, tag_name])
|
||||
@output_tabs += 1
|
||||
|
||||
if value_exists
|
||||
if parse
|
||||
push_script(value, flattened)
|
||||
else
|
||||
push_text(value)
|
||||
end
|
||||
close
|
||||
elsif flattened
|
||||
raise SyntaxError.new("Tag has no content.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless matched
|
||||
raise SyntaxError.new("Invalid tag: \"#{line}\"")
|
||||
end
|
||||
end
|
||||
|
||||
# Renders a line that creates an XHTML tag and has an implicit div because of
|
||||
# <tt>.</tt> or <tt>#</tt>.
|
||||
def render_div(line)
|
||||
render_tag('%div' + line)
|
||||
end
|
||||
|
||||
# Renders an XHTML comment.
|
||||
def render_comment(line)
|
||||
conditional, content = line.scan(COMMENT_REGEX)[0]
|
||||
content.strip!
|
||||
|
||||
if @block_opened && !content.empty?
|
||||
raise SyntaxError.new('Illegal Nesting: Nesting within a tag that already has content is illegal.')
|
||||
end
|
||||
|
||||
try_one_line = !content.empty?
|
||||
push_silent "_hamlout.open_comment(#{try_one_line}, #{conditional.inspect}, #{@output_tabs})"
|
||||
@output_tabs += 1
|
||||
push_and_tabulate([:comment, !conditional.nil?])
|
||||
if try_one_line
|
||||
push_text content
|
||||
close
|
||||
end
|
||||
end
|
||||
|
||||
# Renders an XHTML doctype or XML shebang.
|
||||
def render_doctype(line)
|
||||
if @block_opened
|
||||
raise SyntaxError.new("Illegal Nesting: Nesting within a header command is illegal.")
|
||||
end
|
||||
line = line[3..-1].lstrip.downcase
|
||||
if line[0...3] == "xml"
|
||||
encoding = line.split[1] || "utf-8"
|
||||
wrapper = @options[:attr_wrapper]
|
||||
doctype = "<?xml version=#{wrapper}1.0#{wrapper} encoding=#{wrapper}#{encoding}#{wrapper} ?>"
|
||||
else
|
||||
version, type = line.scan(DOCTYPE_REGEX)[0]
|
||||
if version == "1.1"
|
||||
doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
|
||||
else
|
||||
case type
|
||||
when "strict"
|
||||
doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
||||
when "frameset"
|
||||
doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
|
||||
else
|
||||
doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
||||
end
|
||||
end
|
||||
end
|
||||
push_text doctype
|
||||
end
|
||||
|
||||
# Starts a filtered block.
|
||||
def start_filtered(filter)
|
||||
unless @block_opened
|
||||
raise SyntaxError.new('Filters must have nested text.')
|
||||
end
|
||||
push_and_tabulate([:filtered, filter])
|
||||
@flat_spaces = @template_tabs * 2
|
||||
@filter_buffer = String.new
|
||||
end
|
||||
|
||||
def unescape_interpolation(str)
|
||||
str.dump.gsub('\\#', '#').gsub(/\#\{[^\}]+\}/) do |substr|
|
||||
substr.gsub('\\"', '"')
|
||||
end
|
||||
end
|
||||
|
||||
# Counts the tabulation of a line.
|
||||
def count_soft_tabs(line)
|
||||
spaces = line.index(/[^ ]/)
|
||||
if line[spaces] == ?\t
|
||||
return nil if line.strip.empty?
|
||||
|
||||
raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.")
|
||||
end
|
||||
[spaces, spaces/2]
|
||||
end
|
||||
|
||||
# Pushes value onto <tt>@to_close_stack</tt> and increases
|
||||
# <tt>@template_tabs</tt>.
|
||||
def push_and_tabulate(value)
|
||||
@to_close_stack.push(value)
|
||||
@template_tabs += 1
|
||||
end
|
||||
end
|
||||
end
|
@ -1,43 +0,0 @@
|
||||
module Haml
|
||||
# The abstract type of exception raised by Haml code.
|
||||
# Haml::SyntaxError includes this module,
|
||||
# as do all exceptions raised by Ruby code within Haml.
|
||||
#
|
||||
# Haml::Error encapsulates information about the exception,
|
||||
# such as the line of the Haml template it was raised on
|
||||
# and the Haml file that was being parsed (if applicable).
|
||||
# It also provides a handy way to rescue only exceptions raised
|
||||
# because of a faulty template.
|
||||
module Error
|
||||
# The line of the Haml template on which the exception was thrown.
|
||||
attr_reader :haml_line
|
||||
|
||||
# The name of the file that was being parsed when the exception was raised.
|
||||
# This will be nil unless Haml is being used as an ActionView plugin.
|
||||
attr_reader :haml_filename
|
||||
|
||||
# Adds a properly formatted entry to the exception's backtrace.
|
||||
# +lineno+ should be the line on which the error occurred.
|
||||
# +filename+ should be the file in which the error occurred,
|
||||
# if applicable (defaults to "(haml)").
|
||||
def add_backtrace_entry(lineno, filename = nil) # :nodoc:
|
||||
@haml_line = lineno
|
||||
@haml_filename = filename
|
||||
self.backtrace ||= []
|
||||
self.backtrace.unshift "#{filename || '(haml)'}:#{lineno}"
|
||||
end
|
||||
end
|
||||
|
||||
# SyntaxError is the type of exception raised when Haml encounters an
|
||||
# ill-formatted document.
|
||||
# It's not particularly interesting, except in that it includes Haml::Error.
|
||||
class SyntaxError < StandardError
|
||||
include Haml::Error
|
||||
end
|
||||
|
||||
# HamlError is the type of exception raised when Haml encounters an error
|
||||
# not of a syntactical nature, such as an undefined Filter.
|
||||
class HamlError < StandardError
|
||||
include Haml::Error
|
||||
end
|
||||
end
|
@ -1,296 +0,0 @@
|
||||
require File.dirname(__FILE__) + '/../haml'
|
||||
require 'optparse'
|
||||
|
||||
module Haml
|
||||
# This module contains code for working with the
|
||||
# haml, sass, and haml2html executables,
|
||||
# such as command-line parsing stuff.
|
||||
# It shouldn't need to be invoked by client code.
|
||||
module Exec # :nodoc:
|
||||
# A class that encapsulates the executable code
|
||||
# for all three executables.
|
||||
class Generic # :nodoc:
|
||||
def initialize(args)
|
||||
@args = args
|
||||
@options = {}
|
||||
end
|
||||
|
||||
def parse!
|
||||
begin
|
||||
@opts = OptionParser.new(&(method(:set_opts).to_proc))
|
||||
@opts.parse!(@args)
|
||||
|
||||
process_result
|
||||
|
||||
@options
|
||||
rescue Exception => e
|
||||
raise e if e.is_a? SystemExit
|
||||
|
||||
line = e.backtrace[0].scan(/:(.*)/)[0]
|
||||
puts "#{e.class} on line #{line}: #{e.message}"
|
||||
|
||||
if @options[:trace]
|
||||
e.backtrace[1..-1].each { |t| puts " #{t}" }
|
||||
else
|
||||
puts " Use --trace to see traceback"
|
||||
end
|
||||
|
||||
exit 1
|
||||
end
|
||||
exit 0
|
||||
end
|
||||
|
||||
def to_s
|
||||
@opts.to_s
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_opts(opts)
|
||||
opts.on('--stdin', :NONE, 'Read input from standard input instead of an input file') do
|
||||
@options[:input] = $stdin
|
||||
end
|
||||
|
||||
opts.on('--stdout', :NONE, 'Print output to standard output instead of an output file') do
|
||||
@options[:output] = $stdout
|
||||
end
|
||||
|
||||
opts.on('-s', '--stdio', 'Read input from standard input and print output to standard output') do
|
||||
@options[:input] = $stdin
|
||||
@options[:output] = $stdout
|
||||
end
|
||||
|
||||
opts.on('--trace', :NONE, 'Show a full traceback on error') do
|
||||
@options[:trace] = true
|
||||
end
|
||||
|
||||
opts.on_tail("-?", "-h", "--help", "Show this message") do
|
||||
puts opts
|
||||
exit
|
||||
end
|
||||
|
||||
opts.on_tail("-v", "--version", "Print version") do
|
||||
puts("Haml " + File.read(File.dirname(__FILE__) + '/../../VERSION'))
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
def process_result
|
||||
input = @options[:input]
|
||||
output = @options[:output]
|
||||
|
||||
if input
|
||||
output ||= ARGV[0]
|
||||
else
|
||||
input ||= ARGV[0]
|
||||
output ||= ARGV[1]
|
||||
end
|
||||
|
||||
unless input && output
|
||||
puts @opts
|
||||
exit 1
|
||||
end
|
||||
|
||||
if input.is_a?(String) && !File.exists?(input)
|
||||
puts "File #{input} doesn't exist!"
|
||||
exit 1
|
||||
end
|
||||
|
||||
unless input.is_a? IO
|
||||
input = File.open(input)
|
||||
input_file = true
|
||||
end
|
||||
|
||||
unless output.is_a? IO
|
||||
output = File.open(output, "w")
|
||||
output_file = true
|
||||
end
|
||||
|
||||
@options[:input] = input
|
||||
@options[:output] = output
|
||||
end
|
||||
end
|
||||
|
||||
# A class encapsulating the executable functionality
|
||||
# specific to Haml and Sass.
|
||||
class HamlSass < Generic # :nodoc:
|
||||
private
|
||||
|
||||
def set_opts(opts)
|
||||
opts.banner = <<END
|
||||
Usage: #{@name.downcase} [options] (#{@name.downcase} file) (output file)
|
||||
|
||||
Description:
|
||||
Uses the #{@name} engine to parse the specified template
|
||||
and outputs the result to the specified file.
|
||||
|
||||
Options:
|
||||
END
|
||||
|
||||
opts.on('--rails RAILS_DIR', "Install Haml from the Gem to a Rails project") do |dir|
|
||||
original_dir = dir
|
||||
|
||||
dir = File.join(dir, 'vendor', 'plugins')
|
||||
|
||||
unless File.exists?(dir)
|
||||
puts "Directory #{dir} doesn't exist"
|
||||
exit
|
||||
end
|
||||
|
||||
dir = File.join(dir, 'haml')
|
||||
|
||||
if File.exists?(dir)
|
||||
puts "Directory #{dir} already exists."
|
||||
exit
|
||||
end
|
||||
|
||||
begin
|
||||
Dir.mkdir(dir)
|
||||
rescue SystemCallError
|
||||
puts "Cannot create #{dir}"
|
||||
exit
|
||||
end
|
||||
|
||||
File.open(File.join(dir, 'init.rb'), 'w') do |file|
|
||||
file.puts <<END
|
||||
require 'rubygems'
|
||||
require 'haml'
|
||||
require 'haml/template'
|
||||
require 'sass'
|
||||
require 'sass/plugin'
|
||||
|
||||
ActionView::Base.register_template_handler('haml', Haml::Template)
|
||||
Sass::Plugin.update_stylesheets
|
||||
END
|
||||
end
|
||||
|
||||
puts "Haml plugin added to #{original_dir}"
|
||||
exit
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def process_result
|
||||
super
|
||||
require File.dirname(__FILE__) + "/../#{@name.downcase}"
|
||||
end
|
||||
end
|
||||
|
||||
# A class encapsulating executable functionality
|
||||
# specific to Sass.
|
||||
class Sass < HamlSass # :nodoc:
|
||||
def initialize(args)
|
||||
super
|
||||
@name = "Sass"
|
||||
end
|
||||
|
||||
def process_result
|
||||
super
|
||||
input = @options[:input]
|
||||
output = @options[:output]
|
||||
|
||||
template = input.read()
|
||||
input.close() if input.is_a? File
|
||||
result = ::Sass::Engine.new(template).render
|
||||
output.write(result)
|
||||
output.close() if output.is_a? File
|
||||
end
|
||||
end
|
||||
|
||||
# A class encapsulating executable functionality
|
||||
# specific to Haml.
|
||||
class Haml < HamlSass # :nodoc:
|
||||
def initialize(args)
|
||||
super
|
||||
@name = "Haml"
|
||||
end
|
||||
|
||||
def process_result
|
||||
super
|
||||
input = @options[:input]
|
||||
output = @options[:output]
|
||||
|
||||
template = input.read()
|
||||
input.close() if input.is_a? File
|
||||
result = ::Haml::Engine.new(template).to_html
|
||||
output.write(result)
|
||||
output.close() if output.is_a? File
|
||||
end
|
||||
end
|
||||
|
||||
# A class encapsulating executable functionality
|
||||
# specific to the html2haml executable.
|
||||
class HTML2Haml < Generic # :nodoc:
|
||||
def initialize(args)
|
||||
super
|
||||
|
||||
@module_opts = {}
|
||||
|
||||
begin
|
||||
require 'haml/html'
|
||||
rescue LoadError => err
|
||||
dep = err.message.scan(/^no such file to load -- (.*)/)[0]
|
||||
puts "Required dependency #{dep} not found!"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def set_opts(opts)
|
||||
opts.banner = <<END
|
||||
Usage: html2haml [options] (html file) (output file)
|
||||
|
||||
Description: Transforms an HTML file into corresponding Haml code.
|
||||
|
||||
Options:
|
||||
END
|
||||
|
||||
opts.on('-r', '--rhtml', 'Parse RHTML tags.') do
|
||||
@module_opts[:rhtml] = true
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def process_result
|
||||
super
|
||||
|
||||
input = @options[:input]
|
||||
output = @options[:output]
|
||||
|
||||
output.write(::Haml::HTML.new(input, @module_opts).render)
|
||||
end
|
||||
end
|
||||
|
||||
# A class encapsulating executable functionality
|
||||
# specific to the css2sass executable.
|
||||
class CSS2Sass < Generic # :nodoc:
|
||||
def initialize(args)
|
||||
super
|
||||
|
||||
require 'sass/css'
|
||||
end
|
||||
|
||||
def set_opts(opts)
|
||||
opts.banner = <<END
|
||||
Usage: css2sass [options] (css file) (output file)
|
||||
|
||||
Description: Transforms a CSS file into corresponding Sass code.
|
||||
|
||||
Options:
|
||||
END
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def process_result
|
||||
super
|
||||
|
||||
input = @options[:input]
|
||||
output = @options[:output]
|
||||
|
||||
output.write(::Sass::CSS.new(input).render)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,89 +0,0 @@
|
||||
# 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 = [] unless defined?(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
|
||||
|
||||
class Preserve
|
||||
def initialize(text)
|
||||
@text = text
|
||||
end
|
||||
|
||||
def render
|
||||
Haml::Helpers.preserve(@text)
|
||||
end
|
||||
end
|
||||
|
||||
unless NOT_LOADED.include? 'bluecloth'
|
||||
Markdown = BlueCloth unless defined?(Markdown)
|
||||
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?(Markdown)
|
||||
# Uses RedCloth to provide only Markdown (not Textile) parsing
|
||||
class Markdown < RedCloth
|
||||
def render
|
||||
self.to_html(:markdown)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# :startdoc:
|
@ -1,328 +0,0 @@
|
||||
require 'haml/helpers/action_view_mods'
|
||||
require 'haml/helpers/action_view_extensions'
|
||||
|
||||
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
|
||||
self.extend self
|
||||
|
||||
@@action_view_defined = defined?(ActionView)
|
||||
@@force_no_action_view = false
|
||||
|
||||
# Returns whether or not ActionView is installed on the system.
|
||||
def self.action_view?
|
||||
@@action_view_defined
|
||||
end
|
||||
|
||||
# Isolates the whitespace-sensitive tags in the string and uses preserve
|
||||
# to convert any endlines inside them into HTML entities for endlines.
|
||||
def find_and_preserve(input)
|
||||
input = input.to_s
|
||||
input.scan(/<(textarea|code|pre)[^>]*>(.*?)<\/\1>/im) do |tag, contents|
|
||||
input = input.gsub(contents, preserve(contents))
|
||||
end
|
||||
input
|
||||
end
|
||||
|
||||
# Takes any string, finds all the endlines and converts them to
|
||||
# HTML entities for endlines so they'll render correctly in
|
||||
# whitespace-sensitive tags without screwing up the indentation.
|
||||
def preserve(input)
|
||||
input.gsub(/\n/, '
').gsub(/\r/, '')
|
||||
end
|
||||
|
||||
alias_method :flatten, :preserve
|
||||
|
||||
# Takes an Enumerable object and a block
|
||||
# and iterates over the object,
|
||||
# yielding each element to a Haml block
|
||||
# and putting the result into <tt><li></tt> elements.
|
||||
# This creates a list of the results of the block.
|
||||
# For example:
|
||||
#
|
||||
# = list_of([['hello'], ['yall']]) do |i|
|
||||
# = i[0]
|
||||
#
|
||||
# Produces:
|
||||
#
|
||||
# <li>hello</li>
|
||||
# <li>yall</li>
|
||||
#
|
||||
# And
|
||||
#
|
||||
# = list_of({:title => 'All the stuff', :description => 'A book about all the stuff.'}) do |key, val|
|
||||
# %h3= key.humanize
|
||||
# %p= val
|
||||
#
|
||||
# Produces:
|
||||
#
|
||||
# <li>
|
||||
# <h3>Title</h3>
|
||||
# <p>All the stuff</p>
|
||||
# </li>
|
||||
# <li>
|
||||
# <h3>Description</h3>
|
||||
# <p>A book about all the stuff.</p>
|
||||
# </li>
|
||||
#
|
||||
def list_of(array, &block) # :yields: item
|
||||
to_return = array.collect do |i|
|
||||
result = capture_haml(i, &block)
|
||||
|
||||
if result.count("\n") > 1
|
||||
result.gsub!("\n", "\n ")
|
||||
result = "\n #{result.strip}\n"
|
||||
else
|
||||
result.strip!
|
||||
end
|
||||
|
||||
"<li>#{result}</li>"
|
||||
end
|
||||
to_return.join("\n")
|
||||
end
|
||||
|
||||
# Returns a hash containing default assignments for the xmlns and xml:lang
|
||||
# attributes of the <tt>html</tt> HTML element.
|
||||
# It also takes an optional argument for the value of xml:lang and lang,
|
||||
# which defaults to 'en-US'.
|
||||
# For example,
|
||||
#
|
||||
# %html{html_attrs}
|
||||
#
|
||||
# becomes
|
||||
#
|
||||
# <html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en-US' lang='en-US'>
|
||||
#
|
||||
def html_attrs(lang = 'en-US')
|
||||
{:xmlns => "http://www.w3.org/1999/xhtml", 'xml:lang' => lang, :lang => lang}
|
||||
end
|
||||
|
||||
# Increments the number of tabs the buffer automatically adds
|
||||
# to the lines of the template.
|
||||
# For example:
|
||||
#
|
||||
# %h1 foo
|
||||
# - tab_up
|
||||
# %p bar
|
||||
# - tab_down
|
||||
# %strong baz
|
||||
#
|
||||
# Produces:
|
||||
#
|
||||
# <h1>foo</h1>
|
||||
# <p>bar</p>
|
||||
# <strong>baz</strong>
|
||||
#
|
||||
def tab_up(i = 1)
|
||||
buffer.tabulation += i
|
||||
end
|
||||
|
||||
# Increments the number of tabs the buffer automatically adds
|
||||
# to the lines of the template.
|
||||
#
|
||||
# See tab_up.
|
||||
def tab_down(i = 1)
|
||||
buffer.tabulation -= i
|
||||
end
|
||||
|
||||
# Surrounds the given block of Haml code with the given characters,
|
||||
# with no whitespace in between.
|
||||
# For example:
|
||||
#
|
||||
# = surround '(', ')' do
|
||||
# %a{:href => "food"} chicken
|
||||
#
|
||||
# Produces:
|
||||
#
|
||||
# (<a href='food'>chicken</a>)
|
||||
#
|
||||
# and
|
||||
#
|
||||
# = surround '*' do
|
||||
# %strong angry
|
||||
#
|
||||
# Produces:
|
||||
#
|
||||
# *<strong>angry</strong>*
|
||||
#
|
||||
def surround(front, back = nil, &block)
|
||||
back ||= front
|
||||
output = capture_haml(&block)
|
||||
|
||||
"#{front}#{output.chomp}#{back}\n"
|
||||
end
|
||||
|
||||
# Prepends the given character to the beginning of the Haml block,
|
||||
# with no whitespace between.
|
||||
# For example:
|
||||
#
|
||||
# = precede '*' do
|
||||
# %span.small Not really
|
||||
#
|
||||
# Produces:
|
||||
#
|
||||
# *<span class='small'>Not really</span>
|
||||
#
|
||||
def precede(char, &block)
|
||||
"#{char}#{capture_haml(&block).chomp}\n"
|
||||
end
|
||||
|
||||
# Appends the given character to the end of the Haml block,
|
||||
# with no whitespace between.
|
||||
# For example:
|
||||
#
|
||||
# click
|
||||
# = succeed '.' do
|
||||
# %a{:href=>"thing"} here
|
||||
#
|
||||
# Produces:
|
||||
#
|
||||
# click
|
||||
# <a href='thing'>here</a>.
|
||||
#
|
||||
def succeed(char, &block)
|
||||
"#{capture_haml(&block).chomp}#{char}\n"
|
||||
end
|
||||
|
||||
# Captures the result of the given block of Haml code,
|
||||
# gets rid of the excess indentation,
|
||||
# and returns it as a string.
|
||||
# For example, after the following,
|
||||
#
|
||||
# .foo
|
||||
# - foo = capture_haml(13) do |a|
|
||||
# %p= a
|
||||
#
|
||||
# the local variable <tt>foo</tt> would be assigned to "<p>13</p>\n".
|
||||
#
|
||||
def capture_haml(*args, &block)
|
||||
capture_haml_with_buffer(buffer.buffer, *args, &block)
|
||||
end
|
||||
|
||||
# Outputs text directly to the Haml buffer, with the proper tabulation
|
||||
def puts(text = "")
|
||||
buffer.buffer << (' ' * buffer.tabulation) << text.to_s << "\n"
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
# call-seq:
|
||||
# open(name, attributes = {}) {...}
|
||||
# open(name, text, attributes = {}) {...}
|
||||
#
|
||||
# Creates an HTML tag with the given name and optionally text and attributes.
|
||||
# Can take a block that will be executed
|
||||
# between when the opening and closing tags are output.
|
||||
# If the block is a Haml block or outputs text using puts,
|
||||
# the text will be properly indented.
|
||||
#
|
||||
# For example,
|
||||
#
|
||||
# open :table do
|
||||
# open :tr do
|
||||
# open :td, {:class => 'cell'} do
|
||||
# open :strong, "strong!"
|
||||
# puts "data"
|
||||
# end
|
||||
# open :td do
|
||||
# puts "more_data"
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# outputs
|
||||
#
|
||||
# <table>
|
||||
# <tr>
|
||||
# <td class='cell'>
|
||||
# <strong>
|
||||
# strong!
|
||||
# </strong>
|
||||
# data
|
||||
# </td>
|
||||
# <td>
|
||||
# more_data
|
||||
# </td>
|
||||
# </tr>
|
||||
# </table>
|
||||
#
|
||||
def open(name, attributes = {}, alt_atts = {}, &block)
|
||||
text = nil
|
||||
if attributes.is_a? String
|
||||
text = attributes
|
||||
attributes = alt_atts
|
||||
end
|
||||
|
||||
puts "<#{name}#{buffer.build_attributes(attributes)}>"
|
||||
tab_up
|
||||
# Print out either the text (using push_text) or call the block and add an endline
|
||||
if text
|
||||
puts(text)
|
||||
elsif block
|
||||
block.call
|
||||
end
|
||||
tab_down
|
||||
puts "</#{name}>"
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Gets a reference to the current Haml::Buffer object.
|
||||
def buffer
|
||||
@haml_stack[-1]
|
||||
end
|
||||
|
||||
# Gives a proc the same local "_hamlout" and "_erbout" variables
|
||||
# that the current template has.
|
||||
def bind_proc(&proc)
|
||||
_hamlout = buffer
|
||||
_erbout = _hamlout.buffer
|
||||
proc { |*args| proc.call(*args) }
|
||||
end
|
||||
|
||||
# Performs the function of capture_haml, assuming <tt>local_buffer</tt>
|
||||
# is where the output of block goes.
|
||||
def capture_haml_with_buffer(local_buffer, *args, &block)
|
||||
position = local_buffer.length
|
||||
|
||||
block.call *args
|
||||
|
||||
captured = local_buffer.slice!(position..-1)
|
||||
|
||||
min_tabs = nil
|
||||
captured.each do |line|
|
||||
tabs = line.index(/[^ ]/)
|
||||
min_tabs ||= tabs
|
||||
min_tabs = min_tabs > tabs ? tabs : min_tabs
|
||||
end
|
||||
|
||||
result = captured.map do |line|
|
||||
line[min_tabs..-1]
|
||||
end
|
||||
result.to_s
|
||||
end
|
||||
|
||||
# Returns whether or not the current template is a Haml template.
|
||||
#
|
||||
# This function, unlike other Haml::Helpers functions,
|
||||
# also works in other ActionView templates,
|
||||
# where it will always return false.
|
||||
def is_haml?
|
||||
@haml_is_haml
|
||||
end
|
||||
|
||||
include ActionViewExtensions if self.const_defined? "ActionViewExtensions"
|
||||
end
|
||||
end
|
||||
|
||||
module ActionView
|
||||
class Base # :nodoc:
|
||||
def is_haml?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
@ -1,45 +0,0 @@
|
||||
require 'haml/helpers/action_view_mods'
|
||||
|
||||
if defined?(ActionView)
|
||||
module Haml
|
||||
module Helpers
|
||||
# This module contains various useful helper methods
|
||||
# that either tie into ActionView or the rest of the ActionPack stack,
|
||||
# or are only useful in that context.
|
||||
# Thus, the methods defined here are only available
|
||||
# if ActionView is installed.
|
||||
module ActionViewExtensions
|
||||
# Returns a value for the "class" attribute
|
||||
# unique to this controller/action pair.
|
||||
# This can be used to target styles specifically at this action or controller.
|
||||
# For example, if the current action were EntryController#show,
|
||||
#
|
||||
# %div{:class => page_class} My Div
|
||||
#
|
||||
# would become
|
||||
#
|
||||
# <div class="entry show">My Div</div>
|
||||
#
|
||||
# Then, in a stylesheet
|
||||
# (shown here as Sass),
|
||||
# you could refer to this specific action:
|
||||
#
|
||||
# .entry.show
|
||||
# :font-weight bold
|
||||
#
|
||||
# or to all actions in the entry controller:
|
||||
#
|
||||
# .entry
|
||||
# :color #00f
|
||||
#
|
||||
def page_class
|
||||
controller.controller_name + " " + controller.action_name
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
alias_method :generate_content_class_names, :page_class
|
||||
# :startdoc:
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,86 +0,0 @@
|
||||
if defined?(ActionView) and not defined?(Merb::Plugins)
|
||||
module ActionView
|
||||
class Base # :nodoc:
|
||||
def render_with_haml(*args, &block)
|
||||
was_haml = is_haml?
|
||||
@haml_is_haml = false
|
||||
res = render_without_haml(*args, &block)
|
||||
@haml_is_haml = was_haml
|
||||
res
|
||||
end
|
||||
alias_method :render_without_haml, :render
|
||||
alias_method :render, :render_with_haml
|
||||
end
|
||||
|
||||
# This overrides various helpers in ActionView
|
||||
# to make them work more effectively with Haml.
|
||||
module Helpers
|
||||
# :stopdoc:
|
||||
module CaptureHelper
|
||||
def capture_erb_with_buffer_with_haml(*args, &block)
|
||||
if is_haml?
|
||||
capture_haml_with_buffer(*args, &block)
|
||||
else
|
||||
capture_erb_with_buffer_without_haml(*args, &block)
|
||||
end
|
||||
end
|
||||
alias_method :capture_erb_with_buffer_without_haml, :capture_erb_with_buffer
|
||||
alias_method :capture_erb_with_buffer, :capture_erb_with_buffer_with_haml
|
||||
end
|
||||
|
||||
module TextHelper
|
||||
def concat_with_haml(string, binding = nil)
|
||||
if is_haml?
|
||||
buffer.buffer.concat(string)
|
||||
else
|
||||
concat_without_haml(string, binding)
|
||||
end
|
||||
end
|
||||
alias_method :concat_without_haml, :concat
|
||||
alias_method :concat, :concat_with_haml
|
||||
end
|
||||
|
||||
module FormTagHelper
|
||||
def form_tag_with_haml(url_for_options = {}, options = {}, *parameters_for_url, &proc)
|
||||
if is_haml?
|
||||
if block_given?
|
||||
oldproc = proc
|
||||
proc = bind_proc do |*args|
|
||||
concat "\n"
|
||||
tab_up
|
||||
oldproc.call(*args)
|
||||
tab_down
|
||||
end
|
||||
end
|
||||
res = form_tag_without_haml(url_for_options, options, *parameters_for_url, &proc) + "\n"
|
||||
concat "\n" if block_given? && is_haml?
|
||||
res
|
||||
else
|
||||
form_tag_without_haml(url_for_options, options, *parameters_for_url, &proc)
|
||||
end
|
||||
end
|
||||
alias_method :form_tag_without_haml, :form_tag
|
||||
alias_method :form_tag, :form_tag_with_haml
|
||||
end
|
||||
|
||||
module FormHelper
|
||||
def form_for_with_haml(object_name, *args, &proc)
|
||||
if block_given? && is_haml?
|
||||
oldproc = proc
|
||||
proc = bind_proc do |*args|
|
||||
tab_up
|
||||
oldproc.call(*args)
|
||||
tab_down
|
||||
end
|
||||
end
|
||||
form_for_without_haml(object_name, *args, &proc)
|
||||
concat "\n" if block_given? && is_haml?
|
||||
end
|
||||
alias_method :form_for_without_haml, :form_for
|
||||
alias_method :form_for, :form_for_with_haml
|
||||
end
|
||||
# :startdoc:
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,30 +0,0 @@
|
||||
***************
|
||||
*** 1,11 ****
|
||||
- begin
|
||||
- require 'rubygems'
|
||||
- require 'active_support'
|
||||
- require 'action_controller'
|
||||
- require 'action_view'
|
||||
- action_view_included = true
|
||||
- rescue LoadError
|
||||
- action_view_included = false
|
||||
end
|
||||
|
||||
|
||||
--- 1,16 ----
|
||||
+
|
||||
+ # This obviously requires that ActiveSupport be present prior to Haml
|
||||
+ # being loaded.
|
||||
+ action_view_included = false
|
||||
+ if defined?(ActiveSupport)
|
||||
+ begin
|
||||
+ require 'rubygems'
|
||||
+ require 'active_support'
|
||||
+ require 'action_controller'
|
||||
+ require 'action_view'
|
||||
+ action_view_included = true
|
||||
+ rescue LoadError
|
||||
+ end
|
||||
end
|
||||
|
||||
|
@ -1,173 +0,0 @@
|
||||
require File.dirname(__FILE__) + '/../haml'
|
||||
|
||||
require 'haml/engine'
|
||||
require 'rubygems'
|
||||
require 'hpricot'
|
||||
require 'cgi'
|
||||
|
||||
module Haml
|
||||
# This class contains the functionality used in the +html2haml+ utility,
|
||||
# namely converting HTML documents to Haml templates.
|
||||
# It depends on Hpricot for HTML parsing (http://code.whytheluckystiff.net/hpricot/).
|
||||
class HTML
|
||||
# Creates a new instance of Haml::HTML that will compile the given template,
|
||||
# which can either be a string containing HTML or an Hpricot node,
|
||||
# to a Haml string when +render+ is called.
|
||||
def initialize(template, options = {})
|
||||
@@options = options
|
||||
|
||||
if template.is_a? Hpricot::Node
|
||||
@template = template
|
||||
else
|
||||
if template.is_a? IO
|
||||
template = template.read
|
||||
end
|
||||
|
||||
if @@options[:rhtml]
|
||||
match_to_html(template, /<%=(.*?)-?%>/m, 'loud')
|
||||
match_to_html(template, /<%(.*?)-?%>/m, 'silent')
|
||||
end
|
||||
@template = Hpricot(template)
|
||||
end
|
||||
end
|
||||
|
||||
# Processes the document and returns the result as a string
|
||||
# containing the Haml template.
|
||||
def render
|
||||
@template.to_haml(0)
|
||||
end
|
||||
alias_method :to_haml, :render
|
||||
|
||||
module ::Hpricot::Node
|
||||
# Returns the Haml representation of the given node,
|
||||
# at the given tabulation.
|
||||
def to_haml(tabs = 0)
|
||||
parse_text(self.to_s, tabs)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tabulate(tabs)
|
||||
' ' * tabs
|
||||
end
|
||||
|
||||
def parse_text(text, tabs)
|
||||
text.strip!
|
||||
if text.empty?
|
||||
String.new
|
||||
else
|
||||
lines = text.split("\n")
|
||||
|
||||
lines.map do |line|
|
||||
line.strip!
|
||||
"#{tabulate(tabs)}#{'\\' if Haml::Engine::SPECIAL_CHARACTERS.include?(line[0])}#{line}\n"
|
||||
end.join
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
def self.options
|
||||
@@options
|
||||
end
|
||||
|
||||
TEXT_REGEXP = /^(\s*).*$/
|
||||
|
||||
class ::Hpricot::Doc
|
||||
def to_haml(tabs = 0)
|
||||
output = ''
|
||||
children.each { |child| output += child.to_haml(0) }
|
||||
output
|
||||
end
|
||||
end
|
||||
|
||||
class ::Hpricot::XMLDecl
|
||||
def to_haml(tabs = 0)
|
||||
"#{tabulate(tabs)}!!! XML\n"
|
||||
end
|
||||
end
|
||||
|
||||
class ::Hpricot::DocType
|
||||
def to_haml(tabs = 0)
|
||||
attrs = public_id.scan(/DTD\s+([^\s]+)\s*([^\s]*)\s*([^\s]*)\s*\/\//)[0]
|
||||
if attrs == nil
|
||||
raise Exception.new("Invalid doctype")
|
||||
end
|
||||
|
||||
type, version, strictness = attrs.map { |a| a.downcase }
|
||||
if type == "html"
|
||||
version = "1.0"
|
||||
strictness = "transitional"
|
||||
end
|
||||
|
||||
if version == "1.0" || version.empty?
|
||||
version = nil
|
||||
end
|
||||
|
||||
if strictness == 'transitional' || strictness.empty?
|
||||
strictness = nil
|
||||
end
|
||||
|
||||
version = " #{version}" if version
|
||||
if strictness
|
||||
strictness[0] = strictness[0] - 32
|
||||
strictness = " #{strictness}"
|
||||
end
|
||||
|
||||
"#{tabulate(tabs)}!!!#{version}#{strictness}\n"
|
||||
end
|
||||
end
|
||||
|
||||
class ::Hpricot::Comment
|
||||
def to_haml(tabs = 0)
|
||||
"#{tabulate(tabs)}/\n#{parse_text(self.content, tabs + 1)}"
|
||||
end
|
||||
end
|
||||
|
||||
class ::Hpricot::Elem
|
||||
def to_haml(tabs = 0)
|
||||
output = "#{tabulate(tabs)}"
|
||||
if HTML.options[:rhtml] && name[0...5] == 'haml:'
|
||||
return output + HTML.send("haml_tag_#{name[5..-1]}", self.innerHTML)
|
||||
end
|
||||
|
||||
output += "%#{name}" unless name == 'div' && (attributes.include?('id') || attributes.include?('class'))
|
||||
|
||||
if attributes
|
||||
output += "##{attributes['id']}" if attributes['id']
|
||||
attributes['class'].split(' ').each { |c| output += ".#{c}" } if attributes['class']
|
||||
attributes.delete("id")
|
||||
attributes.delete("class")
|
||||
output += attributes.inspect if attributes.length > 0
|
||||
end
|
||||
|
||||
output += "/" if children.length == 0
|
||||
output += "\n"
|
||||
|
||||
self.children.each do |child|
|
||||
output += child.to_haml(tabs + 1)
|
||||
end
|
||||
|
||||
output
|
||||
end
|
||||
end
|
||||
|
||||
def self.haml_tag_loud(text)
|
||||
"= #{text.gsub(/\n\s*/, '; ').strip}\n"
|
||||
end
|
||||
|
||||
def self.haml_tag_silent(text)
|
||||
text.split("\n").map { |line| "- #{line.strip}\n" }.join
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def match_to_html(string, regex, tag)
|
||||
string.gsub!(regex) do
|
||||
"<haml:#{tag}>#{CGI.escapeHTML($1)}</haml:#{tag}>"
|
||||
end
|
||||
end
|
||||
# :startdoc:
|
||||
end
|
||||
end
|
@ -1,99 +0,0 @@
|
||||
require 'haml/engine'
|
||||
require 'rubygems'
|
||||
require 'active_support'
|
||||
require 'action_view'
|
||||
|
||||
module Haml
|
||||
# This class interfaces with ActionView
|
||||
# to make Haml usable as a Ruby on Rails plugin.
|
||||
# It usually shouldn't need to be used by end users.
|
||||
# Just in case, though, here's what you might do to render
|
||||
# <tt>templates/index.haml</tt>:
|
||||
#
|
||||
# ActionView::Base.register_template_handler("haml", Haml::Template)
|
||||
# base = ActionView::Base.new("templates")
|
||||
# base.render("index")
|
||||
#
|
||||
# Or, if you want to really get into the nitty-gritty:
|
||||
#
|
||||
# base = ActionView::Base.new
|
||||
# template = Haml::Template.new(base)
|
||||
# template.render("templates/index.haml")
|
||||
#
|
||||
class Template
|
||||
|
||||
class << self
|
||||
@@options = {}
|
||||
|
||||
# Gets various options for Haml. See README for details.
|
||||
def options
|
||||
@@options
|
||||
end
|
||||
|
||||
# Sets various options for Haml. See README for details.
|
||||
def options=(value)
|
||||
@@options = value
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a new Haml::Template object that uses <tt>view</tt>
|
||||
# to render its templates.
|
||||
def initialize(view)
|
||||
@view = view
|
||||
end
|
||||
|
||||
# Renders the file at the location <tt>template</tt>,
|
||||
# with <tt>local_assigns</tt> available as local variables within the template.
|
||||
# Returns the result as a string.
|
||||
def render(template, local_assigns={})
|
||||
@view.instance_eval do
|
||||
evaluate_assigns
|
||||
end
|
||||
|
||||
options = @@options.dup
|
||||
locals = options[:locals] || {}
|
||||
locals.merge! local_assigns
|
||||
options[:locals] = locals
|
||||
|
||||
if @view.haml_inline
|
||||
engine = Haml::Engine.new(template, options)
|
||||
else
|
||||
options[:filename] ||= template
|
||||
engine = Haml::Engine.new(File.read(template), options)
|
||||
end
|
||||
|
||||
yield_proc = @view.instance_eval do
|
||||
proc { |*name| instance_variable_get("@content_for_#{name.first || 'layout'}") }
|
||||
end
|
||||
|
||||
engine.to_html(@view) { |*args| yield_proc.call(*args) }
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This module refers to the ActionView module that's part of Ruby on Rails.
|
||||
# Haml can be used as an alternate templating engine for it,
|
||||
# and includes several modifications to make it more Haml-friendly.
|
||||
# The documentation can be found
|
||||
# here[http://rubyonrails.org/api/classes/ActionView/Base.html].
|
||||
module ActionView
|
||||
class Base # :nodoc:
|
||||
attr :haml_inline
|
||||
|
||||
alias_method :read_template_file_old, :read_template_file
|
||||
def read_template_file(template_path, extension)
|
||||
if extension =~ /haml/i
|
||||
template_path
|
||||
else
|
||||
read_template_file_old(template_path, extension)
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :render_template_old, :render_template
|
||||
def render_template(template_extension, template, file_path = nil, local_assigns = {})
|
||||
@haml_inline = !template.nil?
|
||||
render_template_old(template_extension, template, file_path, local_assigns)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,18 +0,0 @@
|
||||
# This file contains various useful bits of code
|
||||
# that are shared between Haml and Sass.
|
||||
|
||||
class Hash # :nodoc:
|
||||
# Same as Hash#merge!,
|
||||
# but recursively merges sub-hashes
|
||||
def rec_merge!(other)
|
||||
other.each do |key, value|
|
||||
myval = self[key]
|
||||
if value.is_a?(Hash) && myval.is_a?(Hash)
|
||||
myval.rec_merge!(value)
|
||||
else
|
||||
self[key] = value
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
end
|
@ -1,613 +0,0 @@
|
||||
dir = File.dirname(__FILE__)
|
||||
$LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
|
||||
|
||||
# = Sass (Syntactically Awesome StyleSheets)
|
||||
#
|
||||
# Sass is a meta-language on top of CSS
|
||||
# that's used to describe the style of a document
|
||||
# cleanly and structurally,
|
||||
# with more power than flat CSS allows.
|
||||
# Sass both provides a simpler, more elegant syntax for CSS
|
||||
# and implements various features that are useful
|
||||
# for creating manageable stylesheets.
|
||||
#
|
||||
# == Features
|
||||
#
|
||||
# * Whitespace active
|
||||
# * Well-formatted output
|
||||
# * Elegant input
|
||||
# * Feature-rich
|
||||
#
|
||||
# == Using Sass
|
||||
#
|
||||
# Sass can be used in several ways:
|
||||
# As a plugin for Ruby on Rails or Merb,
|
||||
# or as a standalone parser.
|
||||
# Sass is bundled with Haml,
|
||||
# so if the Haml plugin or RubyGem is installed,
|
||||
# Sass will already be installed as a plugin or gem, respectively.
|
||||
# The first step for all of these is to install the Haml gem:
|
||||
#
|
||||
# gem install haml
|
||||
#
|
||||
# To enable it as a Rails plugin,
|
||||
# then run
|
||||
#
|
||||
# haml --rails path/to/rails/app
|
||||
#
|
||||
# To enable Sass in Merb,
|
||||
# add
|
||||
#
|
||||
# dependency "haml"
|
||||
#
|
||||
# to config/dependencies.rb.
|
||||
#
|
||||
# Sass templates in Rails and Merb don't quite function in the same way as views,
|
||||
# because they don't contain dynamic content,
|
||||
# and so only need to be compiled when the template file has been updated.
|
||||
# By default (see options, below),
|
||||
# ".sass" files are placed in public/stylesheets/sass.
|
||||
# Then, whenever necessary, they're compiled into corresponding CSS files in public/stylesheets.
|
||||
# For instance, public/stylesheets/sass/main.sass would be compiled to public/stylesheets/main.css.
|
||||
#
|
||||
# Using Sass in Ruby code is very simple.
|
||||
# After installing the Haml gem,
|
||||
# you can use it by running <tt>require "sass"</tt>
|
||||
# and using Sass::Engine like so:
|
||||
#
|
||||
# engine = Sass::Engine.new("#main\n :background-color #0000ff")
|
||||
# engine.render #=> "#main { background-color: #0000ff; }\n"
|
||||
#
|
||||
# == CSS Rules
|
||||
#
|
||||
# Rules in flat CSS have two elements:
|
||||
# the selector
|
||||
# (e.g. "#main", "div p", "li a:hover")
|
||||
# and the attributes
|
||||
# (e.g. "color: #00ff00;", "width: 5em;").
|
||||
#
|
||||
# Sass has both of these,
|
||||
# as well as one additional element: nested rules.
|
||||
#
|
||||
# === Rules and Selectors
|
||||
#
|
||||
# However, some of the syntax is a little different.
|
||||
# The syntax for selectors is the same,
|
||||
# but instead of using brackets to delineate the attributes that belong to a particular rule,
|
||||
# Sass uses two spaces of indentation.
|
||||
# For example:
|
||||
#
|
||||
# #main p
|
||||
# <attribute>
|
||||
# <attribute>
|
||||
# ...
|
||||
#
|
||||
# === Attributes
|
||||
#
|
||||
# There are two different ways to write CSS attrbibutes.
|
||||
# The first is very similar to the how you're used to writing them:
|
||||
# with a colon between the name and the value.
|
||||
# However, Sass attributes don't have semicolons at the end;
|
||||
# each attribute is on its own line, so they aren't necessary.
|
||||
# For example:
|
||||
#
|
||||
# #main p
|
||||
# color: #00ff00
|
||||
# width: 97%
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# #main p {
|
||||
# color: #00ff00;
|
||||
# width: 97% }
|
||||
#
|
||||
# The second syntax for attributes is slightly different.
|
||||
# The colon is at the beginning of the attribute,
|
||||
# rather than between the name and the value,
|
||||
# so it's easier to tell what elements are attributes just by glancing at them.
|
||||
# For example:
|
||||
#
|
||||
# #main p
|
||||
# :color #00ff00
|
||||
# :width 97%
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# #main p {
|
||||
# color: #00ff00;
|
||||
# width: 97% }
|
||||
#
|
||||
# === Nested Rules
|
||||
#
|
||||
# Rules can also be nested within each other.
|
||||
# This signifies that the inner rule's selector is a child of the outer selector.
|
||||
# For example:
|
||||
#
|
||||
# #main p
|
||||
# :color #00ff00
|
||||
# :width 97%
|
||||
#
|
||||
# .redbox
|
||||
# :background-color #ff0000
|
||||
# :color #000000
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# #main p {
|
||||
# color: #00ff00;
|
||||
# width: 97%; }
|
||||
# #main p .redbox {
|
||||
# background-color: #ff0000;
|
||||
# color: #000000; }
|
||||
#
|
||||
# This makes insanely complicated CSS layouts with lots of nested selectors very simple:
|
||||
#
|
||||
# #main
|
||||
# :width 97%
|
||||
#
|
||||
# p, div
|
||||
# :font-size 2em
|
||||
# a
|
||||
# :font-weight bold
|
||||
#
|
||||
# pre
|
||||
# :font-size 3em
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# #main {
|
||||
# width: 97%; }
|
||||
# #main p, #main div {
|
||||
# font-size: 2em; }
|
||||
# #main p a, #main div a {
|
||||
# font-weight: bold; }
|
||||
# #main pre {
|
||||
# font-size: 3em; }
|
||||
#
|
||||
# === Referencing Parent Rules
|
||||
#
|
||||
# In addition to the default behavior of inserting the parent selector
|
||||
# as a CSS parent of the current selector
|
||||
# (e.g. above, "#main" is the parent of "p"),
|
||||
# you can have more fine-grained control over what's done with the parent selector
|
||||
# by using the ampersand character "&" in your selectors.
|
||||
#
|
||||
# The ampersand is automatically replaced by the parent selector,
|
||||
# instead of having it prepended.
|
||||
# This allows you to cleanly create pseudo-attributes:
|
||||
#
|
||||
# a
|
||||
# :font-weight bold
|
||||
# :text-decoration none
|
||||
# &:hover
|
||||
# :text-decoration underline
|
||||
# &:visited
|
||||
# :font-weight normal
|
||||
#
|
||||
# Which would become:
|
||||
#
|
||||
# a {
|
||||
# font-weight: bold;
|
||||
# text-decoration: none; }
|
||||
# a:hover {
|
||||
# text-decoration: underline; }
|
||||
# a:visited {
|
||||
# font-weight: normal; }
|
||||
#
|
||||
# It also allows you to add selectors at the base of the hierarchy,
|
||||
# which can be useuful for targeting certain styles to certain browsers:
|
||||
#
|
||||
# #main
|
||||
# :width 90%
|
||||
# #sidebar
|
||||
# :float left
|
||||
# :margin-left 20%
|
||||
# .ie6 &
|
||||
# :margin-left 40%
|
||||
#
|
||||
# Which would become:
|
||||
#
|
||||
# #main {
|
||||
# width: 90%; }
|
||||
# #main #sidebar {
|
||||
# float: left;
|
||||
# margin-left: 20%; }
|
||||
# .ie6 #main #sidebar {
|
||||
# margin-left: 40%; }
|
||||
#
|
||||
# === Attribute Namespaces
|
||||
#
|
||||
# CSS has quite a few attributes that are in "namespaces;"
|
||||
# for instance, "font-family," "font-size," and "font-weight"
|
||||
# are all in the "font" namespace.
|
||||
# In CSS, if you want to set a bunch of attributes in the same namespace,
|
||||
# you have to type it out each time.
|
||||
# Sass offers a shortcut for this:
|
||||
# just write the namespace one,
|
||||
# then indent each of the sub-attributes within it.
|
||||
# For example:
|
||||
#
|
||||
# .funky
|
||||
# :font
|
||||
# :family fantasy
|
||||
# :size 30em
|
||||
# :weight bold
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# .funky {
|
||||
# font-family: fantasy;
|
||||
# font-size: 30em;
|
||||
# font-weight: bold; }
|
||||
#
|
||||
# == Constants
|
||||
#
|
||||
# Sass has support for setting document-wide constants.
|
||||
# They're set using an exclamation mark followed by the name,
|
||||
# an equals sign, and the value.
|
||||
# An attribute can then be set to the value of a constant
|
||||
# by following it with another equals sign.
|
||||
# For example:
|
||||
#
|
||||
# !main_color = #00ff00
|
||||
#
|
||||
# #main
|
||||
# :color = !main_color
|
||||
# :p
|
||||
# :background-color = !main_color
|
||||
# :color #000000
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# #main {
|
||||
# color: #00ff00; }
|
||||
# #main p {
|
||||
# background-color: #00ff00;
|
||||
# color: #000000; }
|
||||
#
|
||||
# === Arithmetic
|
||||
#
|
||||
# You can even do basic arithmetic with constants.
|
||||
# Sass recognizes numbers, colors,
|
||||
# lengths (numbers with units),
|
||||
# and strings (everything that's not one of the above),
|
||||
# and various operators that work on various values.
|
||||
# All the normal arithmetic operators
|
||||
# (+, -, *, /, %, and parentheses for grouping)
|
||||
# are defined as usual
|
||||
# for numbers, colors, and lengths.
|
||||
# The "+" operator is also defined for Strings
|
||||
# as the concatenation operator.
|
||||
# For example:
|
||||
#
|
||||
# !main_width = 10
|
||||
# !unit1 = em
|
||||
# !unit2 = px
|
||||
# !bg_color = #a5f39e
|
||||
#
|
||||
# #main
|
||||
# :background-color = !bg_color
|
||||
# p
|
||||
# :background-color = !bg_color + #202020
|
||||
# :width = !main_width + !unit1
|
||||
# img.thumb
|
||||
# :width = (!main_width + 15) + !unit2
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# #main {
|
||||
# background-color: #a5f39e; }
|
||||
# #main p {
|
||||
# background-color: #c5ffbe;
|
||||
# width: 10em; }
|
||||
# #main img.thumb {
|
||||
# width: 25em; }
|
||||
#
|
||||
# === Colors
|
||||
#
|
||||
# Colors may be written as three- or six-digit hex numbers prefixed
|
||||
# by a pound sign (#), or as HTML4 color names. For example,
|
||||
# "#ff0", "#ffff00" and "yellow" all refer to the same color.
|
||||
#
|
||||
# Not only can arithmetic be done between colors and other colors,
|
||||
# but it can be done between colors and normal numbers.
|
||||
# In this case, the operation is done piecewise one each of the
|
||||
# Red, Green, and Blue components of the color.
|
||||
# For example:
|
||||
#
|
||||
# !main_color = #a5f39e
|
||||
#
|
||||
# #main
|
||||
# :background-color = !main_color
|
||||
# p
|
||||
# :background-color = !main_color + 32
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# #main {
|
||||
# background-color: #a5f39e; }
|
||||
# #main p {
|
||||
# background-color: #c5ffbe; }
|
||||
#
|
||||
# === Strings
|
||||
#
|
||||
# Strings are the type that's used by default
|
||||
# when an element in a bit of constant arithmetic isn't recognized
|
||||
# as another type of constant.
|
||||
# However, they can also be created explicitly be wrapping a section of code with quotation marks.
|
||||
# Inside the quotation marks,
|
||||
# a backslash can be used to
|
||||
# escape quotation marks that you want to appear in the CSS.
|
||||
# For example:
|
||||
#
|
||||
# !content = "Hello, \"Hubert\" Bean."
|
||||
#
|
||||
# #main
|
||||
# :content = "string(" + !content + ")"
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# #main {
|
||||
# content: string(Hello, "Hubert" Bean.) }
|
||||
#
|
||||
# === Default Concatenation
|
||||
#
|
||||
# All those plusses and quotes for concatenating strings
|
||||
# can get pretty messy, though.
|
||||
# Most of the time, if you want to concatenate stuff,
|
||||
# you just want individual values with spaces in between them.
|
||||
# Thus, in Sass, when two values are next to each other without an operator,
|
||||
# they're simply joined with a space.
|
||||
# For example:
|
||||
#
|
||||
# !font_family = "sans-serif"
|
||||
# !main_font_size = 1em
|
||||
#
|
||||
# #main
|
||||
# :font
|
||||
# :family = !font_family
|
||||
# :size = !main_font_size
|
||||
# h6
|
||||
# :font = italic "small-caps" bold (!main_font_size + 0.1em) !font_family
|
||||
#
|
||||
# is compiled to:
|
||||
#
|
||||
# #main {
|
||||
# font-family: sans-serif;
|
||||
# font-size: 1em; }
|
||||
# #main h6 {
|
||||
# font: italic small-caps bold 1.1em sans-serif; }
|
||||
#
|
||||
# == Directives
|
||||
#
|
||||
# Directives allow the author to directly issue instructions to the Sass compiler.
|
||||
# They're prefixed with an at sign, "<tt>@</tt>",
|
||||
# followed by the name of the directive,
|
||||
# a space, and any arguments to it -
|
||||
# just like CSS directives.
|
||||
# For example:
|
||||
#
|
||||
# @import red.sass
|
||||
#
|
||||
# === Import
|
||||
#
|
||||
# Currently, the only directive is the "import" directive.
|
||||
# It works in a very similar way to the CSS import directive,
|
||||
# and sometimes compiles to a literal CSS "@import".
|
||||
#
|
||||
# Sass can import either other Sass files or plain CSS files.
|
||||
# If it imports a Sass file,
|
||||
# not only are the rules from that file included,
|
||||
# but all constants in that file are made available in the current file.
|
||||
#
|
||||
# Sass looks for other Sass files in the working directory,
|
||||
# and the Sass file directory under Rails or Merb.
|
||||
# Additional search directories may be specified
|
||||
# using the :load_paths option (see below).
|
||||
#
|
||||
# Sass can also import plain CSS files.
|
||||
# In this case, it doesn't literally include the content of the files;
|
||||
# rather, it uses the built-in CSS "@import" directive to tell the client program
|
||||
# to import the files.
|
||||
#
|
||||
# The import directive can take either a full filename
|
||||
# or a filename without an extension.
|
||||
# If an extension isn't provided,
|
||||
# Sass will try to find a Sass file with the given basename in the load paths,
|
||||
# and, failing that, will assume a relevant CSS file will be available.
|
||||
#
|
||||
# For example,
|
||||
#
|
||||
# @import foo.sass
|
||||
#
|
||||
# would compile to
|
||||
#
|
||||
# .foo
|
||||
# :color #f00
|
||||
#
|
||||
# whereas
|
||||
#
|
||||
# @import foo.css
|
||||
#
|
||||
# would compile to
|
||||
#
|
||||
# @import foo.css
|
||||
#
|
||||
# Finally,
|
||||
#
|
||||
# @import foo
|
||||
#
|
||||
# might compile to either,
|
||||
# depending on whether a file called "foo.sass" existed.
|
||||
#
|
||||
# == Comments
|
||||
#
|
||||
# === Silent Comments
|
||||
#
|
||||
# It's simple to add "silent" comments,
|
||||
# which don't output anything to the CSS document,
|
||||
# to a Sass document.
|
||||
# Simply use the familiar C-style notation for a one-line comment, "//",
|
||||
# at the normal indentation level and all text following it won't be output.
|
||||
# For example:
|
||||
#
|
||||
# // A very awesome rule.
|
||||
# #awesome.rule
|
||||
# // An equally awesome attribute.
|
||||
# :awesomeness very
|
||||
#
|
||||
# becomes
|
||||
#
|
||||
# #awesome.rule {
|
||||
# awesomeness: very; }
|
||||
#
|
||||
# === Loud Comments
|
||||
#
|
||||
# "Loud" comments are just as easy as silent ones.
|
||||
# These comments output to the document as CSS comments,
|
||||
# and thus use the same opening sequence: "/*".
|
||||
# For example:
|
||||
#
|
||||
# /* A very awesome rule.
|
||||
# #awesome.rule
|
||||
# /* An equally awesome attribute.
|
||||
# :awesomeness very
|
||||
#
|
||||
# becomes
|
||||
#
|
||||
# /* A very awesome rule. */
|
||||
# #awesome.rule {
|
||||
# /* An equally awesome attribute. */
|
||||
# awesomeness: very; }
|
||||
#
|
||||
# == Output Style
|
||||
#
|
||||
# Although the default CSS style that Sass outputs is very nice,
|
||||
# and reflects the structure of the document in a similar way that Sass does,
|
||||
# sometimes it's good to have other formats available.
|
||||
#
|
||||
# Sass allows you to choose between three different output styles
|
||||
# by setting the <tt>:style</tt> option.
|
||||
# In Rails, this is done by setting <tt>Sass::Plugin.options[:style]</tt>;
|
||||
# outside Rails, it's done by passing an options hash with </tt>:style</tt> set.
|
||||
#
|
||||
# === <tt>:nested</tt>
|
||||
#
|
||||
# Nested style is the default Sass style,
|
||||
# because it reflects the structure of the document
|
||||
# in much the same way Sass does.
|
||||
# Each attribute has its own line,
|
||||
# but the indentation isn't constant.
|
||||
# Each rule is indented based on how deeply it's nested.
|
||||
# For example:
|
||||
#
|
||||
# #main {
|
||||
# color: #fff;
|
||||
# background-color: #000; }
|
||||
# #main p {
|
||||
# width: 10em; }
|
||||
#
|
||||
# .huge {
|
||||
# font-size: 10em;
|
||||
# font-weight: bold;
|
||||
# text-decoration: underline; }
|
||||
#
|
||||
# Nested style is very useful when looking at large CSS files
|
||||
# for the same reason Sass is useful for making them:
|
||||
# it allows you to very easily grasp the structure of the file
|
||||
# without actually reading anything.
|
||||
#
|
||||
# === <tt>:expanded</tt>
|
||||
#
|
||||
# Expanded is the typical human-made CSS style,
|
||||
# with each attribute and rule taking up one line.
|
||||
# Attributes are indented within the rules,
|
||||
# but the rules aren't indented in any special way.
|
||||
# For example:
|
||||
#
|
||||
# #main {
|
||||
# color: #fff;
|
||||
# background-color: #000;
|
||||
# }
|
||||
# #main p {
|
||||
# width: 10em;
|
||||
# }
|
||||
#
|
||||
# .huge {
|
||||
# font-size: 10em;
|
||||
# font-weight: bold;
|
||||
# text-decoration: underline;
|
||||
# }
|
||||
#
|
||||
# === <tt>:compact</tt>
|
||||
#
|
||||
# Compact style, as the name would imply,
|
||||
# takes up less space than Nested or Expanded.
|
||||
# However, it's also harder to read.
|
||||
# Each CSS rule takes up only one line,
|
||||
# with every attribute defined on that line.
|
||||
# Nested rules are placed next to each other with no newline,
|
||||
# while groups of rules have newlines between them.
|
||||
# For example:
|
||||
#
|
||||
# #main { color: #fff; background-color: #000; }
|
||||
# #main p { width: 10em; }
|
||||
#
|
||||
# .huge { font-size: 10em; font-weight: bold; text-decoration: underline; }
|
||||
#
|
||||
# == Sass Options
|
||||
#
|
||||
# Options can be set by setting the hash <tt>Sass::Plugin.options</tt>
|
||||
# from <tt>environment.rb</tt> in Rails,
|
||||
# or by passing an options hash to Sass::Engine.
|
||||
# Available options are:
|
||||
#
|
||||
# [<tt>:style</tt>] Sets the style of the CSS output.
|
||||
# See the section on Output Style, above.
|
||||
#
|
||||
# [<tt>:always_update</tt>] Whether the CSS files should be updated every
|
||||
# time a controller is accessed,
|
||||
# as opposed to only when the template has been modified.
|
||||
# Defaults to false.
|
||||
# Only has meaning within Ruby on Rails or Merb.
|
||||
#
|
||||
# [<tt>:always_check</tt>] Whether a Sass template should be checked for updates every
|
||||
# time a controller is accessed,
|
||||
# as opposed to only when the Rails server starts.
|
||||
# If a Sass template has been updated,
|
||||
# it will be recompiled and will overwrite the corresponding CSS file.
|
||||
# Defaults to false if Rails is running in production mode,
|
||||
# true otherwise.
|
||||
# Only has meaning within Ruby on Rails.
|
||||
#
|
||||
# [<tt>:full_exception</tt>] Whether an error in the Sass code
|
||||
# should cause Sass to provide a detailed description.
|
||||
# If set to true, the specific error will be displayed
|
||||
# along with a line number and source snippet.
|
||||
# Otherwise, a simple uninformative error message will be displayed.
|
||||
# Defaults to false in production mode, true otherwise.
|
||||
# Only has meaning within Ruby on Rails or Merb.
|
||||
#
|
||||
# [<tt>:template_location</tt>] The directory where Sass templates should be read from.
|
||||
# Defaults to <tt>RAILS_ROOT + "/public/stylesheets/sass"</tt>
|
||||
# or <tt>MERB_ROOT + "/public/stylesheets/sass"</tt>.
|
||||
# Only has meaning within Ruby on Rails or Merb.
|
||||
#
|
||||
# [<tt>:css_location</tt>] The directory where CSS output should be written to.
|
||||
# Defaults to <tt>RAILS_ROOT + "/public/stylesheets"</tt>
|
||||
# or <tt>MERB_ROOT + "/public/stylesheets"</tt>.
|
||||
# Only has meaning within Ruby on Rails or Merb.
|
||||
#
|
||||
# [<tt>:filename</tt>] The filename of the file being rendered.
|
||||
# This is used solely for reporting errors,
|
||||
# and is automatically set when using Rails or Merb.
|
||||
#
|
||||
# [<tt>:load_paths</tt>] An array of filesystem paths which should be searched
|
||||
# for Sass templates imported with the "@import" directive.
|
||||
# This defaults to the working directory and, in Rails or Merb,
|
||||
# whatever <tt>:template_location</tt> is.
|
||||
#
|
||||
module Sass; end
|
||||
|
||||
require 'sass/engine'
|
||||
require 'sass/plugin' if defined?(Merb::Plugins)
|
@ -1,219 +0,0 @@
|
||||
require 'sass/constant/operation'
|
||||
require 'sass/constant/literal'
|
||||
|
||||
module Sass
|
||||
module Constant # :nodoc:
|
||||
# The character that begins a constant.
|
||||
CONSTANT_CHAR = ?!
|
||||
|
||||
# Whitespace characters
|
||||
WHITESPACE = [?\ , ?\t, ?\n, ?\r]
|
||||
|
||||
# The character used to escape values
|
||||
ESCAPE_CHAR = ?\\
|
||||
|
||||
# The character used to open and close strings
|
||||
STRING_CHAR = ?"
|
||||
|
||||
# A mapping of syntactically-significant characters
|
||||
# to parsed symbols
|
||||
SYMBOLS = {
|
||||
?( => :open,
|
||||
?) => :close,
|
||||
?+ => :plus,
|
||||
?- => :minus,
|
||||
?* => :times,
|
||||
?/ => :div,
|
||||
?% => :mod,
|
||||
STRING_CHAR => :str,
|
||||
ESCAPE_CHAR => :esc
|
||||
}
|
||||
|
||||
# The regular expression used to parse constants
|
||||
MATCH = /^#{Regexp.escape(CONSTANT_CHAR.chr)}([^\s#{(SYMBOLS.keys + [ ?= ]).map {|c| Regexp.escape("#{c.chr}") }}]+)\s*=\s*(.+)/
|
||||
|
||||
# First-order operations
|
||||
FIRST_ORDER = [:times, :div, :mod]
|
||||
|
||||
# Second-order operations
|
||||
SECOND_ORDER = [:plus, :minus]
|
||||
|
||||
class << self
|
||||
def parse(value, constants, line)
|
||||
begin
|
||||
operationalize(parenthesize(tokenize(value)), constants).to_s
|
||||
rescue Sass::SyntaxError => e
|
||||
if e.message == "Constant arithmetic error"
|
||||
e.instance_eval do
|
||||
@message += ": #{value.dump}"
|
||||
end
|
||||
end
|
||||
e.sass_line = line
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tokenize(value)
|
||||
escaped = false
|
||||
is_string = false
|
||||
negative_okay = true
|
||||
str = ''
|
||||
to_return = []
|
||||
|
||||
reset_str = Proc.new do
|
||||
to_return << str unless str.empty?
|
||||
''
|
||||
end
|
||||
|
||||
value.each_byte do |byte|
|
||||
unless escaped
|
||||
if byte == ESCAPE_CHAR
|
||||
escaped = true
|
||||
next
|
||||
end
|
||||
|
||||
last = to_return[-1]
|
||||
|
||||
# Do we need to open or close a string literal?
|
||||
if byte == STRING_CHAR
|
||||
is_string = !is_string
|
||||
|
||||
# Adjacent strings should be concatenated
|
||||
if is_string && last && (!last.is_a?(Symbol) || last == :close)
|
||||
to_return << :concat
|
||||
end
|
||||
|
||||
str = reset_str.call
|
||||
next
|
||||
end
|
||||
|
||||
unless is_string
|
||||
|
||||
# Are we looking at whitespace?
|
||||
if WHITESPACE.include?(byte)
|
||||
str = reset_str.call
|
||||
next
|
||||
end
|
||||
|
||||
symbol = SYMBOLS[byte]
|
||||
|
||||
# Adjacent values without an operator should be concatenated
|
||||
if (symbol.nil? || symbol == :open) &&
|
||||
last && (!last.is_a?(Symbol) || last == :close)
|
||||
to_return << :concat
|
||||
end
|
||||
|
||||
# Time for a unary minus!
|
||||
if negative_okay && symbol == :minus
|
||||
negative_okay = true
|
||||
to_return << :neg
|
||||
next
|
||||
end
|
||||
|
||||
# Are we looking at an operator?
|
||||
if symbol && (str.empty? || symbol != :mod)
|
||||
str = reset_str.call
|
||||
negative_okay = true
|
||||
to_return << symbol
|
||||
next
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
escaped = false
|
||||
negative_okay = false
|
||||
str << byte.chr
|
||||
end
|
||||
|
||||
if is_string
|
||||
raise Sass::SyntaxError.new("Unterminated string: #{value.dump}")
|
||||
end
|
||||
str = reset_str.call
|
||||
to_return
|
||||
end
|
||||
|
||||
def parenthesize(value)
|
||||
parenthesize_helper(0, value, value.length)[0]
|
||||
end
|
||||
|
||||
def parenthesize_helper(i, value, value_len)
|
||||
to_return = []
|
||||
beginning = i
|
||||
token = value[i]
|
||||
|
||||
while i < value_len && token != :close
|
||||
if token == :open
|
||||
to_return.push(*value[beginning...i])
|
||||
sub, i = parenthesize_helper(i + 1, value, value_len)
|
||||
beginning = i
|
||||
to_return << sub
|
||||
elsif token == :neg
|
||||
if value[i + 1].nil?
|
||||
raise Sass::SyntaxError("Unterminated unary minus.")
|
||||
elsif value[i + 1] == :open
|
||||
to_return.push(*value[beginning...i])
|
||||
sub, i = parenthesize_helper(i + 2, value, value_len)
|
||||
beginning = i
|
||||
to_return << [:neg, sub]
|
||||
else
|
||||
to_return.push(*value[beginning...i])
|
||||
to_return << [:neg, value[i + 1]]
|
||||
beginning = i = i + 2
|
||||
end
|
||||
else
|
||||
i += 1
|
||||
end
|
||||
|
||||
token = value[i]
|
||||
end
|
||||
to_return.push(*value[beginning...i])
|
||||
return to_return, i + 1
|
||||
end
|
||||
|
||||
#--
|
||||
# TODO: Don't pass around original value;
|
||||
# have Constant.parse automatically add it to exception.
|
||||
#++
|
||||
def operationalize(value, constants)
|
||||
value = [value] unless value.is_a?(Array)
|
||||
if value.length == 1
|
||||
value = value[0]
|
||||
if value.is_a? Array
|
||||
operationalize(value, constants)
|
||||
elsif value.is_a? Operation
|
||||
value
|
||||
else
|
||||
Literal.parse(insert_constant(value, constants))
|
||||
end
|
||||
elsif value.length == 2
|
||||
if value[0] == :neg
|
||||
Operation.new(Sass::Constant::Number.new('0'), operationalize(value[1], constants), :minus)
|
||||
else
|
||||
raise SyntaxError.new("Constant arithmetic error")
|
||||
end
|
||||
elsif value.length == 3
|
||||
Operation.new(operationalize(value[0], constants), operationalize(value[2], constants), value[1])
|
||||
else
|
||||
if SECOND_ORDER.include?(value[1]) && FIRST_ORDER.include?(value[3])
|
||||
operationalize([value[0], value[1], operationalize(value[2..4], constants), *value[5..-1]], constants)
|
||||
else
|
||||
operationalize([operationalize(value[0..2], constants), *value[3..-1]], constants)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def insert_constant(value, constants)
|
||||
to_return = value
|
||||
if value[0] == CONSTANT_CHAR
|
||||
to_return = constants[value[1..-1]]
|
||||
unless to_return
|
||||
raise SyntaxError.new("Undefined constant: \"#{value}\"")
|
||||
end
|
||||
end
|
||||
to_return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,101 +0,0 @@
|
||||
require 'sass/constant/literal'
|
||||
|
||||
module Sass::Constant # :nodoc:
|
||||
class Color < Literal # :nodoc:
|
||||
|
||||
HTML4_COLORS = {
|
||||
'black' => 0x000000,
|
||||
'silver' => 0xc0c0c0,
|
||||
'gray' => 0x808080,
|
||||
'white' => 0xffffff,
|
||||
'maroon' => 0x800000,
|
||||
'red' => 0xff0000,
|
||||
'purple' => 0x800080,
|
||||
'fuchsia' => 0xff00ff,
|
||||
'green' => 0x008000,
|
||||
'lime' => 0x00ff00,
|
||||
'olive' => 0x808000,
|
||||
'yellow' => 0xffff00,
|
||||
'navy' => 0x000080,
|
||||
'blue' => 0x0000ff,
|
||||
'teal' => 0x008080,
|
||||
'aqua' => 0x00ffff
|
||||
}
|
||||
|
||||
REGEXP = /\##{"([0-9a-fA-F]{1,2})" * 3}/
|
||||
|
||||
def parse(value)
|
||||
if (value =~ REGEXP)
|
||||
@value = value.scan(REGEXP)[0].map { |num| num.ljust(2, num).to_i(16) }
|
||||
else
|
||||
color = HTML4_COLORS[value.downcase]
|
||||
@value = (0..2).map{ |n| color >> (n << 3) & 0xff }.reverse
|
||||
end
|
||||
end
|
||||
|
||||
def plus(other)
|
||||
if other.is_a? Sass::Constant::String
|
||||
Sass::Constant::String.from_value(self.to_s + other.to_s)
|
||||
else
|
||||
piecewise(other, :+)
|
||||
end
|
||||
end
|
||||
|
||||
def minus(other)
|
||||
if other.is_a? Sass::Constant::String
|
||||
raise NoMethodError.new(nil, :minus)
|
||||
else
|
||||
piecewise(other, :-)
|
||||
end
|
||||
end
|
||||
|
||||
def times(other)
|
||||
if other.is_a? Sass::Constant::String
|
||||
raise NoMethodError.new(nil, :times)
|
||||
else
|
||||
piecewise(other, :*)
|
||||
end
|
||||
end
|
||||
|
||||
def div(other)
|
||||
if other.is_a? Sass::Constant::String
|
||||
raise NoMethodError.new(nil, :div)
|
||||
else
|
||||
piecewise(other, :/)
|
||||
end
|
||||
end
|
||||
|
||||
def mod(other)
|
||||
if other.is_a? Sass::Constant::String
|
||||
raise NoMethodError.new(nil, :mod)
|
||||
else
|
||||
piecewise(other, :%)
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
red, green, blue = @value.map { |num| num.to_s(16).rjust(2, '0') }
|
||||
"##{red}#{green}#{blue}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def self.filter_value(value)
|
||||
value.map { |num| num.to_i }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def piecewise(other, operation)
|
||||
other_num = other.is_a? Number
|
||||
other_val = other.value
|
||||
|
||||
rgb = []
|
||||
for i in (0...3)
|
||||
res = @value[i].send(operation, other_num ? other_val : other_val[i])
|
||||
rgb[i] = [ [res, 255].min, 0 ].max
|
||||
end
|
||||
Color.from_value(rgb)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,53 +0,0 @@
|
||||
# Let the subclasses see the superclass
|
||||
module Sass::Constant; class Literal; end; end; # :nodoc:
|
||||
|
||||
require 'sass/constant/string'
|
||||
require 'sass/constant/number'
|
||||
require 'sass/constant/color'
|
||||
|
||||
class Sass::Constant::Literal # :nodoc:
|
||||
# The regular expression matching numbers.
|
||||
NUMBER = /^(-?\d*?\.?)(\d+)([^\d\s]*)$/
|
||||
|
||||
html_color_matcher = Sass::Constant::Color::HTML4_COLORS.keys.map { |c| "^#{c}$" }.join '|'
|
||||
|
||||
# The regular expression matching colors.
|
||||
COLOR = /^\# (?: [\da-f]{3} | [\da-f]{6} ) | #{html_color_matcher}/ix
|
||||
|
||||
def self.parse(value)
|
||||
case value
|
||||
when NUMBER
|
||||
Sass::Constant::Number.new(value)
|
||||
when COLOR
|
||||
Sass::Constant::Color.new(value)
|
||||
else
|
||||
Sass::Constant::String.new(value)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(value = nil)
|
||||
self.parse(value) if value
|
||||
end
|
||||
|
||||
def perform
|
||||
self
|
||||
end
|
||||
|
||||
def concat(other)
|
||||
Sass::Constant::String.from_value("#{self.to_s} #{other.to_s}")
|
||||
end
|
||||
|
||||
attr_reader :value
|
||||
|
||||
protected
|
||||
|
||||
def self.filter_value(value)
|
||||
value
|
||||
end
|
||||
|
||||
def self.from_value(value)
|
||||
instance = self.new
|
||||
instance.instance_variable_set('@value', self.filter_value(value))
|
||||
instance
|
||||
end
|
||||
end
|
@ -1,87 +0,0 @@
|
||||
require 'sass/constant/literal'
|
||||
|
||||
module Sass::Constant # :nodoc:
|
||||
class Number < Literal # :nodoc:
|
||||
|
||||
attr_reader :unit
|
||||
|
||||
def parse(value)
|
||||
first, second, unit = value.scan(Literal::NUMBER)[0]
|
||||
@value = first.empty? ? second.to_i : "#{first}#{second}".to_f
|
||||
@unit = unit unless unit.empty?
|
||||
end
|
||||
|
||||
def plus(other)
|
||||
if other.is_a? Number
|
||||
operate(other, :+)
|
||||
elsif other.is_a? Color
|
||||
other.plus(self)
|
||||
else
|
||||
Sass::Constant::String.from_value(self.to_s + other.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def minus(other)
|
||||
if other.is_a? Number
|
||||
operate(other, :-)
|
||||
else
|
||||
raise NoMethodError.new(nil, :minus)
|
||||
end
|
||||
end
|
||||
|
||||
def times(other)
|
||||
if other.is_a? Number
|
||||
operate(other, :*)
|
||||
elsif other.is_a? Color
|
||||
other.times(self)
|
||||
else
|
||||
raise NoMethodError.new(nil, :times)
|
||||
end
|
||||
end
|
||||
|
||||
def div(other)
|
||||
if other.is_a? Number
|
||||
operate(other, :/)
|
||||
else
|
||||
raise NoMethodError.new(nil, :div)
|
||||
end
|
||||
end
|
||||
|
||||
def mod(other)
|
||||
if other.is_a? Number
|
||||
operate(other, :%)
|
||||
else
|
||||
raise NoMethodError.new(nil, :mod)
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
value = @value
|
||||
value = value.to_i if value % 1 == 0.0
|
||||
"#{value}#{@unit}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def self.from_value(value, unit=nil)
|
||||
instance = super(value)
|
||||
instance.instance_variable_set('@unit', unit)
|
||||
instance
|
||||
end
|
||||
|
||||
def operate(other, operation)
|
||||
unit = nil
|
||||
if other.unit.nil?
|
||||
unit = self.unit
|
||||
elsif self.unit.nil?
|
||||
unit = other.unit
|
||||
elsif other.unit == self.unit
|
||||
unit = self.unit
|
||||
else
|
||||
raise Sass::SyntaxError.new("Incompatible units: #{self.unit} and #{other.unit}")
|
||||
end
|
||||
|
||||
Number.from_value(self.value.send(operation, other.value), unit)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,30 +0,0 @@
|
||||
require 'sass/constant/string'
|
||||
require 'sass/constant/number'
|
||||
require 'sass/constant/color'
|
||||
|
||||
module Sass::Constant # :nodoc:
|
||||
class Operation # :nodoc:
|
||||
def initialize(operand1, operand2, operator)
|
||||
@operand1 = operand1
|
||||
@operand2 = operand2
|
||||
@operator = operator
|
||||
end
|
||||
|
||||
def to_s
|
||||
self.perform.to_s
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def perform
|
||||
literal1 = @operand1.perform
|
||||
literal2 = @operand2.perform
|
||||
begin
|
||||
literal1.send(@operator, literal2)
|
||||
rescue NoMethodError => e
|
||||
raise e unless e.name.to_s == @operator.to_s
|
||||
raise Sass::SyntaxError.new("Undefined operation: \"#{literal1} #{@operator} #{literal2}\"")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,18 +0,0 @@
|
||||
require 'sass/constant/literal'
|
||||
|
||||
module Sass::Constant # :nodoc:
|
||||
class String < Literal # :nodoc:
|
||||
|
||||
def parse(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def plus(other)
|
||||
Sass::Constant::String.from_value(self.to_s + other.to_s)
|
||||
end
|
||||
|
||||
def to_s
|
||||
@value
|
||||
end
|
||||
end
|
||||
end
|
@ -1,197 +0,0 @@
|
||||
require File.dirname(__FILE__) + '/../sass'
|
||||
require 'sass/tree/node'
|
||||
require 'strscan'
|
||||
|
||||
module Sass
|
||||
# :stopdoc:
|
||||
module Tree
|
||||
class Node
|
||||
def to_sass
|
||||
result = ''
|
||||
|
||||
children.each do |child|
|
||||
result << "#{child.to_sass(0)}\n"
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
class ValueNode
|
||||
def to_sass(tabs)
|
||||
"#{value}\n"
|
||||
end
|
||||
end
|
||||
|
||||
class RuleNode
|
||||
def to_sass(tabs)
|
||||
str = "#{' ' * tabs}#{rule}\n"
|
||||
|
||||
children.each do |child|
|
||||
str << "#{child.to_sass(tabs + 1)}"
|
||||
end
|
||||
|
||||
str
|
||||
end
|
||||
end
|
||||
|
||||
class AttrNode
|
||||
def to_sass(tabs)
|
||||
"#{' ' * tabs}:#{name} #{value}\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
# :startdoc:
|
||||
|
||||
# This class contains the functionality used in the +css2sass+ utility,
|
||||
# namely converting CSS documents to Sass templates.
|
||||
class CSS
|
||||
# :stopdoc:
|
||||
|
||||
# The Regexp matching a CSS rule
|
||||
RULE_RE = /\s*([^\{]+)\s*\{/
|
||||
|
||||
# The Regexp matching a CSS attribute
|
||||
ATTR_RE = /\s*[^::\{\}]+\s*:\s*[^:;\{\}]+\s*;/
|
||||
|
||||
# :startdoc:
|
||||
|
||||
# Creates a new instance of Sass::CSS that will compile the given document
|
||||
# to a Sass string when +render+ is called.
|
||||
def initialize(template)
|
||||
if template.is_a? IO
|
||||
template = template.read
|
||||
end
|
||||
|
||||
@template = StringScanner.new(template)
|
||||
end
|
||||
|
||||
# Processes the document and returns the result as a string
|
||||
# containing the CSS template.
|
||||
def render
|
||||
begin
|
||||
build_tree.to_sass
|
||||
rescue Exception => err
|
||||
line = @template.string[0...@template.pos].split("\n").size
|
||||
|
||||
err.backtrace.unshift "(css):#{line}"
|
||||
raise err
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_tree
|
||||
root = Tree::Node.new(nil)
|
||||
whitespace
|
||||
directives(root)
|
||||
rules(root)
|
||||
sort_rules(root)
|
||||
root
|
||||
end
|
||||
|
||||
def directives(root)
|
||||
while @template.scan(/@/)
|
||||
name = @template.scan /[^\s;]+/
|
||||
whitespace
|
||||
value = @template.scan /[^;]+/
|
||||
assert_match /;/
|
||||
whitespace
|
||||
|
||||
if name == "import" && value =~ /^(url\()?"?([^\s\(\)\"]+)\.css"?\)?$/
|
||||
value = $2
|
||||
end
|
||||
|
||||
root << Tree::ValueNode.new("@#{name} #{value};", nil)
|
||||
end
|
||||
end
|
||||
|
||||
def rules(root)
|
||||
rules = []
|
||||
while @template.scan(/[^\{\s]+/)
|
||||
rules << @template[0]
|
||||
whitespace
|
||||
|
||||
if @template.scan(/\{/)
|
||||
result = Tree::RuleNode.new(rules.join(' '), nil)
|
||||
root << result
|
||||
rules = []
|
||||
|
||||
whitespace
|
||||
attributes(result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def attributes(rule)
|
||||
while @template.scan(/[^:\}\s]+/)
|
||||
name = @template[0]
|
||||
whitespace
|
||||
|
||||
assert_match /:/
|
||||
|
||||
value = ''
|
||||
while @template.scan(/[^;\s]+/)
|
||||
value << @template[0] << whitespace
|
||||
end
|
||||
|
||||
assert_match /;/
|
||||
rule << Tree::AttrNode.new(name, value, nil)
|
||||
end
|
||||
|
||||
assert_match /\}/
|
||||
end
|
||||
|
||||
def whitespace
|
||||
space = @template.scan(/\s*/) || ''
|
||||
|
||||
# If we've hit a comment,
|
||||
# go past it and look for more whitespace
|
||||
if @template.scan(/\/\*/)
|
||||
@template.scan_until(/\*\//)
|
||||
return space + whitespace
|
||||
end
|
||||
return space
|
||||
end
|
||||
|
||||
def assert_match(re)
|
||||
if !@template.scan(re)
|
||||
raise Exception.new("Invalid CSS!")
|
||||
end
|
||||
whitespace
|
||||
end
|
||||
|
||||
def sort_rules(root)
|
||||
root.children.sort! do |c1, c2|
|
||||
if c1.is_a?(Tree::RuleNode) && c2.is_a?(Tree::RuleNode)
|
||||
c1.rule <=> c2.rule
|
||||
elsif !(c1.is_a?(Tree::RuleNode) || c2.is_a?(Tree::RuleNode)) || c2.is_a?(Tree::RuleNode)
|
||||
-1
|
||||
else
|
||||
1
|
||||
end
|
||||
end
|
||||
|
||||
prev_rules = []
|
||||
prev_rule_values = []
|
||||
root.children.each do |child|
|
||||
if child.is_a? Tree::RuleNode
|
||||
joined_prev_values = prev_rule_values.join(' ')
|
||||
until prev_rules.empty? || child.rule =~ /^#{Regexp.escape(joined_prev_values)}/
|
||||
prev_rules.pop
|
||||
prev_rule_values.pop
|
||||
end
|
||||
|
||||
unless prev_rules.empty?
|
||||
child.rule.slice!(0..(joined_prev_values.size))
|
||||
prev_rules[-1] << child
|
||||
root.children.delete child
|
||||
end
|
||||
|
||||
prev_rules << child
|
||||
prev_rule_values << child.rule
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,354 +0,0 @@
|
||||
require 'sass/tree/node'
|
||||
require 'sass/tree/value_node'
|
||||
require 'sass/tree/rule_node'
|
||||
require 'sass/tree/comment_node'
|
||||
require 'sass/tree/attr_node'
|
||||
require 'sass/constant'
|
||||
require 'sass/error'
|
||||
require 'haml/util'
|
||||
|
||||
module Sass
|
||||
# This is the class where all the parsing and processing of the Sass
|
||||
# template is done. It can be directly used by the user by creating a
|
||||
# new instance and calling <tt>render</tt> to render the template. For example:
|
||||
#
|
||||
# template = File.load('stylesheets/sassy.sass')
|
||||
# sass_engine = Sass::Engine.new(template)
|
||||
# output = sass_engine.render
|
||||
# puts output
|
||||
class Engine
|
||||
# The character that begins a CSS attribute.
|
||||
ATTRIBUTE_CHAR = ?:
|
||||
|
||||
# The character that designates that
|
||||
# an attribute should be assigned to the result of constant arithmetic.
|
||||
SCRIPT_CHAR = ?=
|
||||
|
||||
# The character that designates the beginning of a comment,
|
||||
# either Sass or CSS.
|
||||
COMMENT_CHAR = ?/
|
||||
|
||||
# The character that follows the general COMMENT_CHAR and designates a Sass comment,
|
||||
# which is not output as a CSS comment.
|
||||
SASS_COMMENT_CHAR = ?/
|
||||
|
||||
# The character that follows the general COMMENT_CHAR and designates a CSS comment,
|
||||
# which is embedded in the CSS document.
|
||||
CSS_COMMENT_CHAR = ?*
|
||||
|
||||
# The character used to denote a compiler directive.
|
||||
DIRECTIVE_CHAR = ?@
|
||||
|
||||
# The regex that matches and extracts data from
|
||||
# attributes of the form <tt>:name attr</tt>.
|
||||
ATTRIBUTE = /^:([^\s=:]+)\s*(=?)(?:\s+|$)(.*)/
|
||||
|
||||
# The regex that matches attributes of the form <tt>name: attr</tt>.
|
||||
ATTRIBUTE_ALTERNATE_MATCHER = /^[^\s:]+\s*[=:](\s|$)/
|
||||
|
||||
# The regex that matches and extracts data from
|
||||
# attributes of the form <tt>name: attr</tt>.
|
||||
ATTRIBUTE_ALTERNATE = /^([^\s=:]+)(\s*=|:)(?:\s+|$)(.*)/
|
||||
|
||||
# Creates a new instace of Sass::Engine that will compile the given
|
||||
# template string when <tt>render</tt> is called.
|
||||
# See README for available options.
|
||||
#
|
||||
#--
|
||||
#
|
||||
# TODO: Add current options to REFRENCE. Remember :filename!
|
||||
#
|
||||
# When adding options, remember to add information about them
|
||||
# to README!
|
||||
#++
|
||||
#
|
||||
def initialize(template, options={})
|
||||
@options = {
|
||||
:style => :nested,
|
||||
:load_paths => ['.']
|
||||
}.merge! options
|
||||
@template = template.split(/\n\r|\n/)
|
||||
@lines = []
|
||||
@constants = {}
|
||||
end
|
||||
|
||||
# Processes the template and returns the result as a string.
|
||||
def render
|
||||
begin
|
||||
render_to_tree.to_s
|
||||
rescue SyntaxError => err
|
||||
unless err.sass_filename
|
||||
err.add_backtrace_entry(@options[:filename])
|
||||
end
|
||||
raise err
|
||||
end
|
||||
end
|
||||
|
||||
alias_method :to_css, :render
|
||||
|
||||
protected
|
||||
|
||||
def constants
|
||||
@constants
|
||||
end
|
||||
|
||||
def render_to_tree
|
||||
split_lines
|
||||
|
||||
root = Tree::Node.new(@options[:style])
|
||||
index = 0
|
||||
while @lines[index]
|
||||
child, index = build_tree(index)
|
||||
|
||||
if child.is_a? Tree::Node
|
||||
child.line = index
|
||||
root << child
|
||||
elsif child.is_a? Array
|
||||
child.each do |c|
|
||||
root << c
|
||||
end
|
||||
end
|
||||
end
|
||||
@line = nil
|
||||
|
||||
root
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Readies each line in the template for parsing,
|
||||
# and computes the tabulation of the line.
|
||||
def split_lines
|
||||
@line = 0
|
||||
old_tabs = 0
|
||||
@template.each_with_index do |line, index|
|
||||
@line += 1
|
||||
|
||||
tabs = count_tabs(line)
|
||||
|
||||
if line[0] == COMMENT_CHAR && line[1] == SASS_COMMENT_CHAR && tabs == 0
|
||||
tabs = old_tabs
|
||||
end
|
||||
|
||||
if tabs # if line isn't blank
|
||||
if tabs - old_tabs > 1
|
||||
raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.", @line)
|
||||
end
|
||||
@lines << [line.strip, tabs]
|
||||
|
||||
old_tabs = tabs
|
||||
else
|
||||
@lines << ['//', old_tabs]
|
||||
end
|
||||
end
|
||||
|
||||
@line = nil
|
||||
end
|
||||
|
||||
# Counts the tabulation of a line.
|
||||
def count_tabs(line)
|
||||
spaces = line.index(/[^ ]/)
|
||||
if spaces
|
||||
if spaces % 2 == 1 || line[spaces] == ?\t
|
||||
# Make sure a line with just tabs isn't an error
|
||||
return nil if line.strip.empty?
|
||||
|
||||
raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.", @line)
|
||||
end
|
||||
spaces / 2
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def build_tree(index)
|
||||
line, tabs = @lines[index]
|
||||
index += 1
|
||||
@line = index
|
||||
node = parse_line(line)
|
||||
|
||||
has_children = has_children?(index, tabs)
|
||||
|
||||
# Node is a symbol if it's non-outputting, like a constant assignment
|
||||
unless node.is_a? Tree::Node
|
||||
if has_children
|
||||
if node == :constant
|
||||
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath constants.", @line)
|
||||
elsif node.is_a? Array
|
||||
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.", @line)
|
||||
end
|
||||
end
|
||||
|
||||
return node, index
|
||||
end
|
||||
|
||||
if node.is_a? Tree::CommentNode
|
||||
while has_children
|
||||
line, index = raw_next_line(index)
|
||||
node << line
|
||||
|
||||
has_children = has_children?(index, tabs)
|
||||
end
|
||||
else
|
||||
while has_children
|
||||
child, index = build_tree(index)
|
||||
|
||||
if child == :constant
|
||||
raise SyntaxError.new("Constants may only be declared at the root of a document.", @line)
|
||||
elsif child.is_a? Array
|
||||
raise SyntaxError.new("Import directives may only be used at the root of a document.", @line)
|
||||
elsif child.is_a? Tree::Node
|
||||
child.line = @line
|
||||
node << child
|
||||
end
|
||||
|
||||
has_children = has_children?(index, tabs)
|
||||
end
|
||||
end
|
||||
|
||||
return node, index
|
||||
end
|
||||
|
||||
def has_children?(index, tabs)
|
||||
next_line = ['//', 0]
|
||||
while !next_line.nil? && next_line[0] == '//' && next_line[1] = 0
|
||||
next_line = @lines[index]
|
||||
index += 1
|
||||
end
|
||||
next_line && next_line[1] > tabs
|
||||
end
|
||||
|
||||
def raw_next_line(index)
|
||||
[@lines[index][0], index + 1]
|
||||
end
|
||||
|
||||
def parse_line(line)
|
||||
case line[0]
|
||||
when ATTRIBUTE_CHAR
|
||||
parse_attribute(line, ATTRIBUTE)
|
||||
when Constant::CONSTANT_CHAR
|
||||
parse_constant(line)
|
||||
when COMMENT_CHAR
|
||||
parse_comment(line)
|
||||
when DIRECTIVE_CHAR
|
||||
parse_directive(line)
|
||||
else
|
||||
if line =~ ATTRIBUTE_ALTERNATE_MATCHER
|
||||
parse_attribute(line, ATTRIBUTE_ALTERNATE)
|
||||
else
|
||||
Tree::RuleNode.new(line, @options[:style])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def parse_attribute(line, attribute_regx)
|
||||
name, eq, value = line.scan(attribute_regx)[0]
|
||||
|
||||
if name.nil? || value.nil?
|
||||
raise SyntaxError.new("Invalid attribute: \"#{line}\"", @line)
|
||||
end
|
||||
|
||||
if eq.strip[0] == SCRIPT_CHAR
|
||||
value = Sass::Constant.parse(value, @constants, @line).to_s
|
||||
end
|
||||
|
||||
Tree::AttrNode.new(name, value, @options[:style])
|
||||
end
|
||||
|
||||
def parse_constant(line)
|
||||
name, value = line.scan(Sass::Constant::MATCH)[0]
|
||||
unless name && value
|
||||
raise SyntaxError.new("Invalid constant: \"#{line}\"", @line)
|
||||
end
|
||||
@constants[name] = Sass::Constant.parse(value, @constants, @line)
|
||||
:constant
|
||||
end
|
||||
|
||||
def parse_comment(line)
|
||||
if line[1] == SASS_COMMENT_CHAR
|
||||
:comment
|
||||
elsif line[1] == CSS_COMMENT_CHAR
|
||||
Tree::CommentNode.new(line, @options[:style])
|
||||
else
|
||||
Tree::RuleNode.new(line, @options[:style])
|
||||
end
|
||||
end
|
||||
|
||||
def parse_directive(line)
|
||||
directive, value = line[1..-1].split(/\s+/, 2)
|
||||
|
||||
case directive
|
||||
when "import"
|
||||
import(value)
|
||||
else
|
||||
raise SyntaxError.new("Unknown compiler directive: #{"@#{directive} #{value}".dump}", @line)
|
||||
end
|
||||
end
|
||||
|
||||
def import(files)
|
||||
nodes = []
|
||||
|
||||
files.split(/,\s*/).each do |filename|
|
||||
engine = nil
|
||||
filename = find_file_to_import(filename)
|
||||
if filename =~ /\.css$/
|
||||
nodes << Tree::ValueNode.new("@import url(#{filename});", @options[:style])
|
||||
else
|
||||
File.open(filename) do |file|
|
||||
new_options = @options.dup
|
||||
new_options[:filename] = filename
|
||||
engine = Sass::Engine.new(file.read, @options)
|
||||
end
|
||||
|
||||
engine.constants.merge! @constants
|
||||
|
||||
begin
|
||||
root = engine.render_to_tree
|
||||
rescue Sass::SyntaxError => err
|
||||
err.add_backtrace_entry(filename)
|
||||
raise err
|
||||
end
|
||||
root.children.each do |child|
|
||||
child.filename = filename
|
||||
nodes << child
|
||||
end
|
||||
@constants = engine.constants
|
||||
end
|
||||
end
|
||||
|
||||
nodes
|
||||
end
|
||||
|
||||
def find_file_to_import(filename)
|
||||
was_sass = false
|
||||
original_filename = filename
|
||||
new_filename = nil
|
||||
|
||||
if filename[-5..-1] == ".sass"
|
||||
filename = filename[0...-5]
|
||||
was_sass = true
|
||||
elsif filename[-4..-1] == ".css"
|
||||
return filename
|
||||
end
|
||||
|
||||
@options[:load_paths].each do |path|
|
||||
full_path = File.join(path, filename) + '.sass'
|
||||
|
||||
if File.readable?(full_path)
|
||||
new_filename = full_path
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if new_filename.nil?
|
||||
if was_sass
|
||||
raise SyntaxError.new("File to import not found or unreadable: #{original_filename}", @line)
|
||||
else
|
||||
return filename + '.css'
|
||||
end
|
||||
else
|
||||
new_filename
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,35 +0,0 @@
|
||||
module Sass
|
||||
# Sass::SyntaxError encapsulates information about the exception,
|
||||
# such as the line of the Sass template it was raised on
|
||||
# and the Sass file that was being parsed (if applicable).
|
||||
# It also provides a handy way to rescue only exceptions raised
|
||||
# because of a faulty template.
|
||||
class SyntaxError < StandardError
|
||||
# The line of the Sass template on which the exception was thrown.
|
||||
attr_accessor :sass_line
|
||||
|
||||
# The name of the file that was being parsed when the exception was raised.
|
||||
# This will be nil unless Sass is being used as an ActionView plugin.
|
||||
attr_reader :sass_filename
|
||||
|
||||
# Creates a new SyntaxError.
|
||||
# +lineno+ should be the line of the Sass template on which the error occurred.
|
||||
def initialize(msg, lineno = nil)
|
||||
@message = msg
|
||||
@sass_line = lineno
|
||||
end
|
||||
|
||||
# Adds a properly formatted entry to the exception's backtrace.
|
||||
# +filename+ should be the file in which the error occurred,
|
||||
# if applicable (defaults to "(sass)").
|
||||
def add_backtrace_entry(filename) # :nodoc:
|
||||
@sass_filename ||= filename
|
||||
self.backtrace ||= []
|
||||
self.backtrace.unshift "#{@sass_filename || '(sass)'}:#{@sass_line}"
|
||||
end
|
||||
|
||||
def to_s # :nodoc:
|
||||
@message
|
||||
end
|
||||
end
|
||||
end
|
@ -1,111 +0,0 @@
|
||||
require 'sass/engine'
|
||||
|
||||
module Sass
|
||||
# This module contains methods to aid in using Sass
|
||||
# as a stylesheet-rendering plugin for various systems.
|
||||
# Currently Rails/ActionController and Merb are supported out of the box.
|
||||
module Plugin
|
||||
class << self
|
||||
@@options = {
|
||||
:template_location => './public/stylesheets/sass',
|
||||
:css_location => './public/stylesheets',
|
||||
:always_update => false,
|
||||
:always_check => true,
|
||||
:full_exception => true
|
||||
}
|
||||
|
||||
# Gets various options for Sass. See README for details.
|
||||
#--
|
||||
# TODO: *DOCUMENT OPTIONS*
|
||||
#++
|
||||
def options
|
||||
@@options
|
||||
end
|
||||
|
||||
# Sets various options for Sass.
|
||||
def options=(value)
|
||||
@@options.merge!(value)
|
||||
end
|
||||
|
||||
# Checks each stylesheet in <tt>options[:css_location]</tt>
|
||||
# to see if it needs updating,
|
||||
# and updates it using the corresponding template
|
||||
# from <tt>options[:templates]</tt>
|
||||
# if it does.
|
||||
def update_stylesheets
|
||||
Dir.glob(File.join(options[:template_location], "**", "*.sass")).entries.each do |file|
|
||||
|
||||
# Get the relative path to the file with no extension
|
||||
name = file.sub(options[:template_location] + "/", "")[0...-5]
|
||||
|
||||
if options[:always_update] || stylesheet_needs_update?(name)
|
||||
css = css_filename(name)
|
||||
File.delete(css) if File.exists?(css)
|
||||
|
||||
filename = template_filename(name)
|
||||
l_options = @@options.dup
|
||||
l_options[:filename] = filename
|
||||
l_options[:load_paths] = (l_options[:load_paths] || []) + [l_options[:template_location]]
|
||||
engine = Engine.new(File.read(filename), l_options)
|
||||
begin
|
||||
result = engine.render
|
||||
rescue Exception => e
|
||||
if options[:full_exception]
|
||||
e_string = "#{e.class}: #{e.message}"
|
||||
|
||||
if e.is_a? Sass::SyntaxError
|
||||
e_string << "\non line #{e.sass_line}"
|
||||
|
||||
if e.sass_filename
|
||||
e_string << " of #{e.sass_filename}"
|
||||
|
||||
if File.exists?(e.sass_filename)
|
||||
e_string << "\n\n"
|
||||
|
||||
min = [e.sass_line - 5, 0].max
|
||||
File.read(e.sass_filename).rstrip.split("\n")[
|
||||
min .. e.sass_line + 5
|
||||
].each_with_index do |line, i|
|
||||
e_string << "#{min + i + 1}: #{line}\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
result = "/*\n#{e_string}\n\nBacktrace:\n#{e.backtrace.join("\n")}\n*/"
|
||||
else
|
||||
result = "/* Internal stylesheet error */"
|
||||
end
|
||||
end
|
||||
|
||||
# Create any directories that might be necessary
|
||||
dirs = [l_options[:css_location]]
|
||||
name.split("/")[0...-1].each { |dir| dirs << "#{dirs[-1]}/#{dir}" }
|
||||
dirs.each { |dir| Dir.mkdir(dir) unless File.exist?(dir) }
|
||||
|
||||
# Finally, write the file
|
||||
File.open(css, 'w') do |file|
|
||||
file.print(result)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def template_filename(name)
|
||||
"#{@@options[:template_location]}/#{name}.sass"
|
||||
end
|
||||
|
||||
def css_filename(name)
|
||||
"#{@@options[:css_location]}/#{name}.css"
|
||||
end
|
||||
|
||||
def stylesheet_needs_update?(name)
|
||||
!File.exists?(css_filename(name)) || (File.mtime(template_filename(name)) - 2) > File.mtime(css_filename(name))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'sass/plugin/rails' if defined?(ActionController)
|
||||
require 'sass/plugin/merb' if defined?(Merb::Plugins)
|
@ -1,20 +0,0 @@
|
||||
unless defined?(Sass::MERB_LOADED)
|
||||
Sass::MERB_LOADED = true
|
||||
|
||||
Sass::Plugin.options.merge!(:template_location => MERB_ROOT + '/public/stylesheets/sass',
|
||||
:css_location => MERB_ROOT + '/public/stylesheets',
|
||||
:always_check => MERB_ENV != "production",
|
||||
:full_exception => MERB_ENV != "production")
|
||||
config = Merb::Plugins.config[:sass] || Merb::Plugins.config["sass"] || {}
|
||||
config.symbolize_keys!
|
||||
Sass::Plugin.options.merge!(config)
|
||||
|
||||
class MerbHandler # :nodoc:
|
||||
def process_with_sass(request, response)
|
||||
Sass::Plugin.update_stylesheets if Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check]
|
||||
process_without_sass(request, response)
|
||||
end
|
||||
alias_method :process_without_sass, :process
|
||||
alias_method :process, :process_with_sass
|
||||
end
|
||||
end
|
@ -1,18 +0,0 @@
|
||||
unless defined?(Sass::RAILS_LOADED)
|
||||
Sass::RAILS_LOADED = true
|
||||
|
||||
Sass::Plugin.options.merge!(:template_location => RAILS_ROOT + '/public/stylesheets/sass',
|
||||
:css_location => RAILS_ROOT + '/public/stylesheets',
|
||||
:always_check => RAILS_ENV != "production",
|
||||
:full_exception => RAILS_ENV != "production")
|
||||
|
||||
module ActionController # :nodoc:
|
||||
class Base # :nodoc:
|
||||
alias_method :sass_old_process, :process
|
||||
def process(*args)
|
||||
Sass::Plugin.update_stylesheets if Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check]
|
||||
sass_old_process(*args)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,52 +0,0 @@
|
||||
require 'sass/tree/node'
|
||||
|
||||
module Sass::Tree
|
||||
class AttrNode < ValueNode
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(name, value, style)
|
||||
@name = name
|
||||
super(value, style)
|
||||
end
|
||||
|
||||
def to_s(parent_name = nil)
|
||||
if value[-1] == ?;
|
||||
raise Sass::SyntaxError.new("Invalid attribute: #{declaration.dump} (This isn't CSS!)", @line)
|
||||
end
|
||||
real_name = name
|
||||
real_name = "#{parent_name}-#{real_name}" if parent_name
|
||||
|
||||
if value.empty? && children.empty?
|
||||
raise Sass::SyntaxError.new("Invalid attribute: #{declaration.dump}", @line)
|
||||
end
|
||||
|
||||
join_string = @style == :compact ? ' ' : "\n"
|
||||
to_return = ''
|
||||
if !value.empty?
|
||||
to_return << "#{real_name}: #{value};#{join_string}"
|
||||
end
|
||||
|
||||
children.each do |kid|
|
||||
if @style == :compact
|
||||
to_return << "#{kid.to_s(real_name)} "
|
||||
else
|
||||
to_return << "#{kid.to_s(real_name)}\n"
|
||||
end
|
||||
end
|
||||
to_return << "\n" unless children.empty? || @style == :compact
|
||||
to_return[0...-1]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def declaration
|
||||
":#{name} #{value}"
|
||||
end
|
||||
|
||||
def invalid_child?(child)
|
||||
if !child.is_a?(AttrNode)
|
||||
"Illegal nesting: Only attributes may be nested beneath attributes."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,14 +0,0 @@
|
||||
require 'sass/tree/node'
|
||||
|
||||
module Sass::Tree
|
||||
class CommentNode < ValueNode
|
||||
def initialize(value, style)
|
||||
super(value[2..-1].strip, style)
|
||||
end
|
||||
|
||||
def to_s(parent_name = nil)
|
||||
join_string = @style == :compact ? ' ' : "\n * "
|
||||
"/* #{value}#{join_string unless children.empty?}#{children.join join_string} */"
|
||||
end
|
||||
end
|
||||
end
|
@ -1,46 +0,0 @@
|
||||
module Sass
|
||||
module Tree
|
||||
class Node
|
||||
attr_accessor :children
|
||||
attr_accessor :line
|
||||
attr_accessor :filename
|
||||
|
||||
def initialize(style)
|
||||
@style = style
|
||||
@children = []
|
||||
end
|
||||
|
||||
def <<(child)
|
||||
if msg = invalid_child?(child)
|
||||
raise Sass::SyntaxError.new(msg, child.line)
|
||||
end
|
||||
@children << child
|
||||
end
|
||||
|
||||
def to_s
|
||||
result = String.new
|
||||
children.each do |child|
|
||||
if child.is_a? AttrNode
|
||||
raise SyntaxError.new('Attributes aren\'t allowed at the root of a document.', child.line)
|
||||
end
|
||||
|
||||
begin
|
||||
result += "#{child.to_s(1)}\n"
|
||||
rescue SyntaxError => e
|
||||
raise e
|
||||
end
|
||||
end
|
||||
result[0...-1]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# This method should be overridden by subclasses to return an error message
|
||||
# if the given child node is invalid,
|
||||
# and false or nil otherwise.
|
||||
def invalid_child?(child)
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,59 +0,0 @@
|
||||
require 'sass/tree/node'
|
||||
require 'sass/tree/attr_node'
|
||||
|
||||
module Sass::Tree
|
||||
class RuleNode < ValueNode
|
||||
# The character used to include the parent selector
|
||||
PARENT = '&'
|
||||
|
||||
alias_method :rule, :value
|
||||
alias_method :rule=, :value=
|
||||
|
||||
def to_s(tabs, super_rules = nil)
|
||||
attributes = []
|
||||
sub_rules = []
|
||||
total_rule = if super_rules
|
||||
super_rules.split(/,\s*/).collect! do |s|
|
||||
self.rule.split(/,\s*/).collect do |r|
|
||||
if r.include?(PARENT)
|
||||
r.gsub(PARENT, s)
|
||||
else
|
||||
"#{s} #{r}"
|
||||
end
|
||||
end.join(", ")
|
||||
end.join(", ")
|
||||
elsif self.rule.include?(PARENT)
|
||||
raise Sass::SyntaxError.new("Base-level rules cannot contain the parent-selector-referencing character '#{PARENT}'", line)
|
||||
else
|
||||
self.rule
|
||||
end
|
||||
|
||||
children.each do |child|
|
||||
if child.is_a? RuleNode
|
||||
sub_rules << child
|
||||
else
|
||||
attributes << child
|
||||
end
|
||||
end
|
||||
|
||||
to_return = ''
|
||||
unless attributes.empty?
|
||||
if @style == :compact
|
||||
to_return << "#{total_rule} { #{attributes.join(' ')} }\n"
|
||||
else
|
||||
spaces = (@style == :expanded ? 2 : tabs * 2)
|
||||
old_spaces = ' ' * (spaces - 2)
|
||||
spaces = ' ' * spaces
|
||||
|
||||
attributes = attributes.join("\n").gsub("\n", "\n#{spaces}").rstrip
|
||||
end_attrs = (@style == :expanded ? "\n" : ' ')
|
||||
to_return << "#{old_spaces}#{total_rule} {\n#{spaces}#{attributes}#{end_attrs}}\n"
|
||||
end
|
||||
end
|
||||
|
||||
tabs += 1 unless attributes.empty?
|
||||
sub_rules.each { |sub| to_return << sub.to_s(tabs, total_rule) }
|
||||
to_return
|
||||
end
|
||||
end
|
||||
end
|
@ -1,16 +0,0 @@
|
||||
require 'sass/tree/node'
|
||||
|
||||
module Sass::Tree
|
||||
class ValueNode < Node
|
||||
attr_accessor :value
|
||||
|
||||
def initialize(value, style)
|
||||
@value = value
|
||||
super(style)
|
||||
end
|
||||
|
||||
def to_s(tabs = 0)
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
@ -1,62 +0,0 @@
|
||||
require 'rubygems'
|
||||
require 'active_support'
|
||||
require 'action_controller'
|
||||
require 'action_view'
|
||||
|
||||
require File.dirname(__FILE__) + '/../lib/haml'
|
||||
require 'haml/template'
|
||||
require 'sass/engine'
|
||||
|
||||
require 'benchmark'
|
||||
require 'stringio'
|
||||
|
||||
module Haml
|
||||
class Benchmarker
|
||||
|
||||
# Creates a new benchmarker that looks for templates in the base
|
||||
# directory.
|
||||
def initialize(base = File.dirname(__FILE__))
|
||||
ActionView::Base.register_template_handler("haml", Haml::Template)
|
||||
unless base.class == ActionView::Base
|
||||
@base = ActionView::Base.new(base)
|
||||
else
|
||||
@base = base
|
||||
end
|
||||
end
|
||||
|
||||
# Benchmarks haml against ERb, and Sass on its own.
|
||||
#
|
||||
# Returns the results of the benchmarking as a string.
|
||||
#
|
||||
def benchmark(runs = 100)
|
||||
template_name = 'standard'
|
||||
haml_template = "haml/templates/#{template_name}"
|
||||
rhtml_template = "haml/rhtml/#{template_name}"
|
||||
sass_template = File.dirname(__FILE__) + "/sass/templates/complex.sass"
|
||||
|
||||
old_stdout = $stdout
|
||||
$stdout = StringIO.new
|
||||
|
||||
times = Benchmark.bmbm do |b|
|
||||
b.report("haml:") { runs.times { @base.render haml_template } }
|
||||
b.report("erb:") { runs.times { @base.render rhtml_template } }
|
||||
end
|
||||
|
||||
#puts times[0].inspect, times[1].inspect
|
||||
ratio = sprintf("%g", times[0].to_a[5] / times[1].to_a[5])
|
||||
puts "Haml/ERB: " + ratio
|
||||
|
||||
puts '', '-' * 50, 'Sass on its own', '-' * 50
|
||||
|
||||
Benchmark.bmbm do |b|
|
||||
b.report("sass:") { runs.times { Sass::Engine.new(File.read(sass_template)).render } }
|
||||
end
|
||||
|
||||
$stdout.pos = 0
|
||||
to_return = $stdout.read
|
||||
$stdout = old_stdout
|
||||
|
||||
to_return
|
||||
end
|
||||
end
|
||||
end
|
@ -1,261 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'rubygems'
|
||||
require 'active_support'
|
||||
require 'action_controller'
|
||||
require 'action_view'
|
||||
|
||||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../../lib/haml'
|
||||
require 'haml/engine'
|
||||
|
||||
class EngineTest < Test::Unit::TestCase
|
||||
|
||||
def render(text, options = {})
|
||||
Haml::Engine.new(text, options).to_html
|
||||
end
|
||||
|
||||
def test_empty_render_should_remain_empty
|
||||
assert_equal('', render(''))
|
||||
end
|
||||
|
||||
# This is ugly because Hashes are unordered; we don't always know the order
|
||||
# in which attributes will be returned.
|
||||
# There is probably a better way to do this.
|
||||
def test_attributes_should_render_correctly
|
||||
assert_equal("<div class='atlantis' style='ugly'>\n</div>", render(".atlantis{:style => 'ugly'}").chomp)
|
||||
rescue
|
||||
assert_equal("<div style='ugly' class='atlantis'>\n</div>", render(".atlantis{:style => 'ugly'}").chomp)
|
||||
end
|
||||
|
||||
def test_ruby_code_should_work_inside_attributes
|
||||
author = 'hcatlin'
|
||||
assert_equal("<p class='3'>foo</p>", render("%p{:class => 1+2} foo").chomp)
|
||||
end
|
||||
|
||||
def test_nil_should_render_empty_tag
|
||||
assert_equal("<div class='no_attributes'>\n</div>",
|
||||
render(".no_attributes{:nil => nil}").chomp)
|
||||
end
|
||||
|
||||
def test_strings_should_get_stripped_inside_tags
|
||||
assert_equal("<div class='stripped'>This should have no spaces in front of it</div>",
|
||||
render(".stripped This should have no spaces in front of it").chomp)
|
||||
end
|
||||
|
||||
def test_one_liner_should_be_one_line
|
||||
assert_equal("<p>Hello</p>", render('%p Hello').chomp)
|
||||
end
|
||||
|
||||
def test_long_liner_should_not_print_on_one_line
|
||||
assert_equal("<div>\n #{'x' * 51}\n</div>", render("%div #{'x' * 51}").chomp)
|
||||
end
|
||||
|
||||
def test_multi_render
|
||||
engine = Haml::Engine.new("%strong Hi there!")
|
||||
assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
|
||||
assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
|
||||
assert_equal("<strong>Hi there!</strong>\n", engine.to_html)
|
||||
end
|
||||
|
||||
def test_double_equals
|
||||
assert_equal("<p>Hello World</p>\n", render('%p== Hello #{who}', :locals => {:who => 'World'}))
|
||||
assert_equal("<p>\n Hello World\n</p>\n", render("%p\n == Hello \#{who}", :locals => {:who => 'World'}))
|
||||
end
|
||||
|
||||
def test_nil_tag_value_should_render_as_empty
|
||||
assert_equal("<p></p>\n", render("%p= nil"))
|
||||
end
|
||||
|
||||
def test_tag_with_failed_if_should_render_as_empty
|
||||
assert_equal("<p></p>\n", render("%p= 'Hello' if false"))
|
||||
end
|
||||
|
||||
# Options tests
|
||||
|
||||
def test_stop_eval
|
||||
assert_equal("", render("= 'Hello'", :suppress_eval => true))
|
||||
assert_equal("", render("- puts 'foo'", :suppress_eval => true))
|
||||
assert_equal("<div id='foo' yes='no' />\n", render("#foo{:yes => 'no'}/", :suppress_eval => true))
|
||||
assert_equal("<div id='foo' />\n", render("#foo{:yes => 'no', :call => a_function() }/", :suppress_eval => true))
|
||||
assert_equal("<div />\n", render("%div[1]/", :suppress_eval => true))
|
||||
|
||||
begin
|
||||
assert_equal("", render(":ruby\n puts 'hello'", :suppress_eval => true))
|
||||
rescue Haml::HamlError => err
|
||||
caught = true
|
||||
assert_equal('Filter "ruby" is not defined!', err.message)
|
||||
end
|
||||
assert(caught, "Rendering a ruby filter without evaluating didn't throw an error!")
|
||||
end
|
||||
|
||||
def test_attr_wrapper
|
||||
assert_equal("<p strange=*attrs*>\n</p>\n", render("%p{ :strange => 'attrs'}", :attr_wrapper => '*'))
|
||||
assert_equal("<p escaped='quo\"te'>\n</p>\n", render("%p{ :escaped => 'quo\"te'}", :attr_wrapper => '"'))
|
||||
assert_equal("<p escaped=\"q'uo"te\">\n</p>\n", render("%p{ :escaped => 'q\\'uo\"te'}", :attr_wrapper => '"'))
|
||||
assert_equal("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n", render("!!! XML", :attr_wrapper => '"'))
|
||||
end
|
||||
|
||||
def test_attrs_parsed_correctly
|
||||
assert_equal("<p boom=>biddly='bar => baz'>\n</p>\n", render("%p{'boom=>biddly' => 'bar => baz'}"))
|
||||
assert_equal("<p foo,bar='baz, qux'>\n</p>\n", render("%p{'foo,bar' => 'baz, qux'}"))
|
||||
assert_equal("<p escaped='quo\nte'>\n</p>\n", render("%p{ :escaped => \"quo\\nte\"}"))
|
||||
assert_equal("<p escaped='quo4te'>\n</p>\n", render("%p{ :escaped => \"quo\#{2 + 2}te\"}"))
|
||||
end
|
||||
|
||||
def test_locals
|
||||
assert_equal("<p>Paragraph!</p>\n", render("%p= text", :locals => { :text => "Paragraph!" }))
|
||||
end
|
||||
|
||||
def test_recompile_with_new_locals
|
||||
template = "%p= (text == 'first time') ? text : new_text"
|
||||
assert_equal("<p>first time</p>\n", render(template, :locals => { :text => "first time" }))
|
||||
assert_equal("<p>second time</p>\n", render(template, :locals => { :text => "recompile", :new_text => "second time" }))
|
||||
|
||||
# Make sure the method called will return junk unless recompiled
|
||||
method_name = Haml::Engine.send(:class_variable_get, '@@method_names')[template]
|
||||
Haml::Engine::CompiledTemplates.module_eval "def #{method_name}(stuff); @haml_stack[-1].push_text 'NOT RECOMPILED', 0; end"
|
||||
|
||||
assert_equal("NOT RECOMPILED\n", render(template, :locals => { :text => "first time" }))
|
||||
assert_equal("<p>first time</p>\n", render(template, :locals => { :text => "first time", :foo => 'bar' }))
|
||||
end
|
||||
|
||||
def test_dynamc_attrs_shouldnt_register_as_literal_values
|
||||
assert_equal("<p a='b2c'>\n</p>\n", render('%p{:a => "b#{1 + 1}c"}'))
|
||||
assert_equal("<p a='b2c'>\n</p>\n", render("%p{:a => 'b' + (1 + 1).to_s + 'c'}"))
|
||||
end
|
||||
|
||||
def test_rec_merge
|
||||
hash1 = {1=>2, 3=>{5=>7, 8=>9}}
|
||||
hash2 = {4=>5, 3=>{5=>2, 16=>12}}
|
||||
hash3 = {1=>2, 4=>5, 3=>{5=>2, 8=>9, 16=>12}}
|
||||
|
||||
hash1.rec_merge!(hash2)
|
||||
assert_equal(hash3, hash1)
|
||||
end
|
||||
|
||||
def test_exception_type
|
||||
begin
|
||||
render("%p hi\n= undefined")
|
||||
rescue Exception => e
|
||||
assert(e.is_a?(Haml::Error))
|
||||
assert_equal(2, e.haml_line)
|
||||
assert_equal(nil, e.haml_filename)
|
||||
assert_equal('(haml):2', e.backtrace[0])
|
||||
else
|
||||
# Test failed... should have raised an exception
|
||||
assert(false)
|
||||
end
|
||||
end
|
||||
|
||||
def test_syntax_errors
|
||||
errs = [ "!!!\n a", "a\n b", "a\n:foo\nb", "/ a\n b",
|
||||
"% a", "%p a\n b", "a\n%p=\nb", "%p=\n a",
|
||||
"a\n%p~\nb", "a\n~\nb", "a\n~\n b", "%p~\n b", "%p/\n a",
|
||||
"%p\n \t%a b", "%a\n b\nc", "%a\n b\nc",
|
||||
":notafilter\n This isn't\n a filter!",
|
||||
".{} a", "\#{} a", ".= 'foo'", "%a/ b", "%p..class", "%p..#." ]
|
||||
errs.each do |err|
|
||||
begin
|
||||
render(err)
|
||||
rescue Exception => e
|
||||
assert(e.is_a?(Haml::Error),
|
||||
"#{err.dump} doesn't produce Haml::SyntaxError!")
|
||||
else
|
||||
assert(false,
|
||||
"#{err.dump} doesn't produce an exception!")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_compile_error
|
||||
begin
|
||||
render("a\nb\n- fee do\nc")
|
||||
rescue Exception => e
|
||||
assert_equal(3, e.haml_line)
|
||||
else
|
||||
assert(false,
|
||||
'"a\nb\n- fee do\nc" doesn\'t produce an exception!')
|
||||
end
|
||||
end
|
||||
|
||||
def test_no_bluecloth
|
||||
old_markdown = false
|
||||
if defined?(Haml::Filters::Markdown)
|
||||
old_markdown = Haml::Filters::Markdown
|
||||
end
|
||||
|
||||
Kernel.module_eval do
|
||||
alias_method :haml_old_require, :gem_original_require
|
||||
|
||||
def gem_original_require(file)
|
||||
raise LoadError if file == 'bluecloth'
|
||||
haml_old_require(file)
|
||||
end
|
||||
end
|
||||
|
||||
if old_markdown
|
||||
Haml::Filters.instance_eval do
|
||||
remove_const 'Markdown'
|
||||
end
|
||||
end
|
||||
|
||||
# This is purposefully redundant, so it doesn't stop
|
||||
# haml/filters from being required later on.
|
||||
require 'haml/../haml/filters'
|
||||
|
||||
assert_equal("<h1>Foo</h1>\t<p>- a\n- b</p>\n",
|
||||
Haml::Engine.new(":markdown\n Foo\n ===\n - a\n - b").to_html)
|
||||
|
||||
Haml::Filters.instance_eval do
|
||||
remove_const 'Markdown'
|
||||
end
|
||||
|
||||
Haml::Filters.const_set('Markdown', old_markdown) if old_markdown
|
||||
|
||||
Kernel.module_eval do
|
||||
alias_method :gem_original_require, :haml_old_require
|
||||
end
|
||||
|
||||
NOT_LOADED.delete 'bluecloth'
|
||||
end
|
||||
|
||||
def test_no_redcloth
|
||||
Kernel.module_eval do
|
||||
alias_method :haml_old_require2, :gem_original_require
|
||||
|
||||
def gem_original_require(file)
|
||||
raise LoadError if file == 'redcloth'
|
||||
haml_old_require2(file)
|
||||
end
|
||||
end
|
||||
|
||||
# This is purposefully redundant, so it doesn't stop
|
||||
# haml/filters from being required later on.
|
||||
require 'haml/../haml/../haml/filters'
|
||||
|
||||
begin
|
||||
Haml::Engine.new(":redcloth\n _foo_").to_html
|
||||
rescue Haml::HamlError
|
||||
else
|
||||
assert(false, "No exception raised!")
|
||||
end
|
||||
|
||||
Kernel.module_eval do
|
||||
alias_method :gem_original_require2, :haml_old_require
|
||||
end
|
||||
|
||||
NOT_LOADED.delete 'redcloth'
|
||||
end
|
||||
|
||||
def test_local_assigns_dont_modify_class
|
||||
assert_equal("bar\n", render("= foo", :locals => {:foo => 'bar'}))
|
||||
assert_equal(nil, defined?(foo))
|
||||
end
|
||||
|
||||
def test_object_ref_with_nil_id
|
||||
user = Struct.new('User', :id).new
|
||||
assert_equal("<p class='struct_user' id='struct_user_new'>New User</p>\n",
|
||||
render("%p[user] New User", :locals => {:user => user}))
|
||||
end
|
||||
end
|
@ -1,123 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'rubygems'
|
||||
require 'active_support'
|
||||
require 'action_controller'
|
||||
require 'action_view'
|
||||
|
||||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../../lib/haml'
|
||||
require 'haml/template'
|
||||
|
||||
class HelperTest < Test::Unit::TestCase
|
||||
include Haml::Helpers
|
||||
|
||||
def setup
|
||||
ActionView::Base.register_template_handler("haml", Haml::Template)
|
||||
@base = ActionView::Base.new
|
||||
@base.controller = ActionController::Base.new
|
||||
end
|
||||
|
||||
def render(text, options = {})
|
||||
if options == :action_view
|
||||
@base.render :inline => text, :type => :haml
|
||||
else
|
||||
scope = options.delete :scope_object
|
||||
Haml::Engine.new(text, options).to_html(scope ? scope : Object.new)
|
||||
end
|
||||
end
|
||||
|
||||
def test_flatten
|
||||
assert_equal(flatten("FooBar"), "FooBar")
|
||||
|
||||
assert_equal(flatten("Foo\rBar"), "FooBar")
|
||||
|
||||
assert_equal(flatten("Foo\nBar"), "Foo
Bar")
|
||||
|
||||
assert_equal(flatten("Hello\nWorld!\nYOU ARE \rFLAT?\n\rOMGZ!"),
|
||||
"Hello
World!
YOU ARE FLAT?
OMGZ!")
|
||||
end
|
||||
|
||||
def test_list_of_should_render_correctly
|
||||
assert_equal("<li>1</li>\n<li>2</li>\n", render("= list_of([1, 2]) do |i|\n = i"))
|
||||
assert_equal("<li>[1]</li>\n", render("= list_of([[1]]) do |i|\n = i.inspect"))
|
||||
assert_equal("<li>\n <h1>Fee</h1>\n <p>A word!</p>\n</li>\n<li>\n <h1>Fi</h1>\n <p>A word!</p>\n</li>\n<li>\n <h1>Fo</h1>\n <p>A word!</p>\n</li>\n<li>\n <h1>Fum</h1>\n <p>A word!</p>\n</li>\n",
|
||||
render("= list_of(['Fee', 'Fi', 'Fo', 'Fum']) do |title|\n %h1= title\n %p A word!"))
|
||||
end
|
||||
|
||||
def test_buffer_access
|
||||
assert(render("= buffer") =~ /#<Haml::Buffer:0x[a-z0-9]+>/)
|
||||
assert_equal(render("= (buffer == _hamlout)"), "true\n")
|
||||
end
|
||||
|
||||
def test_tabs
|
||||
assert_equal(render("foo\n- tab_up\nbar\n- tab_down\nbaz"), "foo\n bar\nbaz\n")
|
||||
end
|
||||
|
||||
def test_helpers_dont_leak
|
||||
# Haml helpers shouldn't be accessible from ERB
|
||||
render("foo")
|
||||
proper_behavior = false
|
||||
|
||||
begin
|
||||
ActionView::Base.new.render(:inline => "<%= flatten('Foo\\nBar') %>")
|
||||
rescue NoMethodError
|
||||
proper_behavior = true
|
||||
end
|
||||
assert(proper_behavior)
|
||||
|
||||
begin
|
||||
ActionView::Base.new.render(:inline => "<%= concat('foo') %>")
|
||||
rescue ArgumentError, NameError
|
||||
proper_behavior = true
|
||||
end
|
||||
assert(proper_behavior)
|
||||
end
|
||||
|
||||
def test_action_view_included
|
||||
assert(Haml::Helpers.action_view?)
|
||||
end
|
||||
|
||||
def test_form_tag
|
||||
result = render("- form_tag 'foo' do\n %p bar\n %strong baz", :action_view)
|
||||
should_be = "<form action=\"foo\" method=\"post\">\n <p>bar</p>\n <strong>baz</strong>\n</form>\n"
|
||||
assert_equal(should_be, result)
|
||||
end
|
||||
|
||||
def test_capture_haml
|
||||
assert_equal("\"<p>13</p>\\n\"\n", render("- foo = capture_haml(13) do |a|\n %p= a\n= foo.dump"))
|
||||
end
|
||||
|
||||
def test_is_haml
|
||||
assert(!ActionView::Base.new.is_haml?)
|
||||
assert_equal("true\n", render("= is_haml?"))
|
||||
assert_equal("true\n", render("= is_haml?", :action_view))
|
||||
assert_equal("false", @base.render(:inline => '<%= is_haml? %>'))
|
||||
assert_equal("false\n", render("= render :inline => '<%= is_haml? %>'", :action_view))
|
||||
end
|
||||
|
||||
def test_page_class
|
||||
controller = Struct.new(:controller_name, :action_name).new('troller', 'tion')
|
||||
scope = Struct.new(:controller).new(controller)
|
||||
result = render("%div{:class => page_class} MyDiv", :scope_object => scope)
|
||||
expected = "<div class='troller tion'>MyDiv</div>\n"
|
||||
assert_equal expected, result
|
||||
end
|
||||
|
||||
def test_indented_capture
|
||||
assert_equal(" \n Foo\n ", @base.render(:inline => " <% res = capture do %>\n Foo\n <% end %><%= res %>"))
|
||||
end
|
||||
|
||||
def test_capture_deals_properly_with_collections
|
||||
Haml::Helpers.module_eval do
|
||||
def trc(collection, &block)
|
||||
collection.each do |record|
|
||||
puts capture_haml(record, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal("1\n\n2\n\n3\n\n", render("- trc([1, 2, 3]) do |i|\n = i.inspect"))
|
||||
end
|
||||
end
|
||||
|
@ -1,6 +0,0 @@
|
||||
class Article
|
||||
attr_accessor :id, :title, :body
|
||||
def initialize
|
||||
@id, @title, @body = 1, 'Hello', 'World'
|
||||
end
|
||||
end
|
@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div id='content'>
|
||||
Lorem ipsum dolor sit amet
|
||||
</div>
|
||||
<div id='yieldy'>
|
||||
Lorem ipsum dolor sit amet
|
||||
</div>
|
||||
<div id='nosym'>
|
||||
Lorem ipsum dolor sit amet
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,8 +0,0 @@
|
||||
<p></p>
|
||||
<h1>Me!</h1>
|
||||
<div id='foo'>
|
||||
<p id='bar'>All</p>
|
||||
<br />
|
||||
<p class='baz'>This</p>
|
||||
Should render
|
||||
</div>
|
@ -1,57 +0,0 @@
|
||||
<style>
|
||||
p {
|
||||
border-style: dotted;
|
||||
border-width: 10px;
|
||||
border-color: #ff00ff; }
|
||||
|
||||
h1 {
|
||||
font-weight: normal; }
|
||||
</style>
|
||||
TESTING HAHAHAHA!
|
||||
<h1>Foo</h1>
|
||||
|
||||
|
||||
<pre><code>This is preformatted!
|
||||
Look at that!
|
||||
Wowie-zowie!</code></pre>
|
||||
|
||||
|
||||
<p><strong>boldilicious!</strong></p>
|
||||
<h1>Yeah</h1>
|
||||
|
||||
|
||||
<p><em>pretty much the same as above</em></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?
|
||||
|
@ -1,84 +0,0 @@
|
||||
&&&&&&&&&&&
|
||||
<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>
|
||||
<p>foo</p>
|
||||
<p>
|
||||
reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally loooooooooooooooooong
|
||||
</p>
|
||||
<div class='woah'>
|
||||
<div id='funky'>
|
||||
<div>
|
||||
<h1>Big!</h1>
|
||||
<p>Small</p>
|
||||
<!-- Invisible -->
|
||||
</div>
|
||||
<div class='dilly'>
|
||||
<p>foo</p>
|
||||
<h1>bar</h1>
|
||||
</div>
|
||||
</div>
|
||||
(<strong>parentheses!</strong>)
|
||||
</div>
|
||||
*<span class='small'>Not really</span>
|
||||
click
|
||||
<a href='thing'>here</a>.
|
||||
<p>baz</p>
|
||||
<p>boom</p>
|
||||
foo
|
||||
<p>
|
||||
<form action="hello/world" method="post">
|
||||
</p>
|
||||
<form action="heeheeaform" method="post">
|
||||
<div><input name="commit" type="submit" value="save" /></div>
|
||||
</form>
|
||||
<form action="article_url" method="post">
|
||||
Title:
|
||||
<input id="article_title" name="article[title]" size="30" type="text" value="Hello" />
|
||||
Body:
|
||||
<input id="article_body" name="article[body]" size="30" type="text" value="World" />
|
||||
</form>
|
||||
<li><a href='http://www.google.com'>google</a></li>
|
||||
<p>
|
||||
foo
|
||||
<div>
|
||||
bar
|
||||
</div>
|
||||
boom
|
||||
baz
|
||||
boom, again
|
||||
</p>
|
||||
<table>
|
||||
<tr>
|
||||
<td class='cell'>
|
||||
<strong>
|
||||
strong!
|
||||
</strong>
|
||||
data
|
||||
</td>
|
||||
<td>
|
||||
more_data
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
@ -1,10 +0,0 @@
|
||||
<div class='article' id='article_1'>
|
||||
<h1>Hello</h1>
|
||||
<div>World</div>
|
||||
</div>
|
||||
<div class='article' id='id_article_1'>id</div>
|
||||
<div class='article class' id='article_1'>class</div>
|
||||
<div class='article class' id='id_article_1'>id class</div>
|
||||
<div class='article full' id='article_1'>boo</div>
|
||||
<div class='article full' id='article_1'>moo</div>
|
||||
<div class='article articleFull' id='article_1'>foo</div>
|
@ -1,59 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8' ?>
|
||||
<?xml version='1.0' encoding='iso-8859-1' ?>
|
||||
<?xml version='1.0' encoding='utf-8' ?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
|
||||
<strong apos="Foo's bar!">Boo!</strong>
|
||||
Embedded? false!
|
||||
Embedded? true!
|
||||
Embedded? true!
|
||||
<div class='render'><em>wow!</em></div>
|
||||
stuff followed by whitespace
|
||||
<strong>block with whitespace</strong>
|
||||
<p>
|
||||
Escape
|
||||
- character
|
||||
%p foo
|
||||
yee\ha
|
||||
</p>
|
||||
<!-- Short comment -->
|
||||
<!--
|
||||
This is a really long comment look how long it is it should be on a line of its own don't you think?
|
||||
-->
|
||||
<!--
|
||||
This is a block comment
|
||||
cool, huh?
|
||||
<strong>there can even be sub-tags!</strong>
|
||||
Or script!
|
||||
-->
|
||||
<p>class attribute shouldn't appear!</p>
|
||||
<!--[if lte IE6]> conditional comment! <![endif]-->
|
||||
<!--[if gte IE7]>
|
||||
<p>Block conditional comment</p>
|
||||
<div>
|
||||
<h1>Cool, eh?</h1>
|
||||
</div>
|
||||
<![endif]-->
|
||||
<!--[if gte IE5.2]>
|
||||
Woah a period.
|
||||
<![endif]-->
|
||||
testtest
|
||||
<br />
|
||||
<meta foo='bar' />
|
||||
<img />
|
||||
<hr />
|
||||
<link />
|
||||
<script>Inline content</script>
|
||||
<br>
|
||||
Nested content
|
||||
</br>
|
||||
<p class='article bar foo' id='article_1'>Blah</p>
|
||||
<p class='article foo' id='article_1'>Blah</p>
|
||||
<p class='article quux qux' id='article_1'>Blump</p>
|
||||
Woah inner quotes
|
||||
<p class='dynamic_quote' dyn='3' quotes="single '">
|
||||
</p>
|
||||
<p class='dynamic_atomic' dyn='3' />
|
@ -1,12 +0,0 @@
|
||||
! Not a Doctype !
|
||||
<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>
|
||||
</ul>
|
@ -1,24 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Stop. haml time</title>
|
||||
<div id='content'>
|
||||
<h1>This is a title!</h1>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit
|
||||
</p>
|
||||
<p class='foo'>Cigarettes!</p>
|
||||
<h2>Man alive!</h2>
|
||||
<ul class='things'>
|
||||
<li>Slippers</li>
|
||||
<li>Shoes</li>
|
||||
<li>Bathrobe</li>
|
||||
<li>Coffee</li>
|
||||
</ul>
|
||||
<pre>
|
||||
This is some text that's in a pre block!
|
||||
Let's see what happens when it's rendered! What about now, since we're on a new line?
|
||||
</pre>
|
||||
</div>
|
||||
</head>
|
||||
</html>
|
@ -1,20 +0,0 @@
|
||||
<p>
|
||||
@foo =
|
||||
value one
|
||||
</p>
|
||||
<p>
|
||||
@foo =
|
||||
value two
|
||||
</p>
|
||||
<p>
|
||||
@foo =
|
||||
value two
|
||||
</p>
|
||||
<p>
|
||||
@foo =
|
||||
value three
|
||||
</p>
|
||||
<p>
|
||||
@foo =
|
||||
value three
|
||||
</p>
|
@ -1,74 +0,0 @@
|
||||
<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! "undefined method `silly' for String:Class" happened!
|
||||
<p>
|
||||
"false" is:
|
||||
false
|
||||
</p>
|
||||
Even!
|
||||
Odd!
|
||||
Even!
|
||||
Odd!
|
||||
Even!
|
||||
</div>
|
||||
<div class='foo'>
|
||||
<strong>foobar</strong>
|
||||
</div>
|
||||
<strong>0</strong>
|
||||
<strong>1</strong>
|
||||
<strong>2</strong>
|
||||
<strong>3</strong>
|
||||
<strong>4</strong>
|
||||
<div class='test'>
|
||||
<p>boom</p>
|
||||
</div>
|
@ -1,43 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html lang='en-US' xml:lang='en-US' xmlns='http://www.w3.org/1999/xhtml'>
|
||||
<head>
|
||||
<title>Hampton Catlin Is Totally Awesome</title>
|
||||
<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
|
||||
</head>
|
||||
<body>
|
||||
<!-- You're In my house now! -->
|
||||
foo
|
||||
<div class='header'>
|
||||
Yes, ladies and gentileman. He is just that egotistical.
|
||||
Fantastic! This should be multi-line output
|
||||
The question is if this would translate! Ahah!
|
||||
20
|
||||
</div>
|
||||
<div id='body'> Quotes should be loved! Just like people!</div>
|
||||
Wow.|
|
||||
<p>
|
||||
Holy cow multiline tags! A pipe (|) even!
|
||||
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>
|
||||
hello
|
||||
<div class='footer'>
|
||||
<strong class='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.
|
||||
So, I'm just making it *really* long. God, I hope this works
|
||||
</strong>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,28 +0,0 @@
|
||||
<div class='tags'>
|
||||
<foo>1</foo>
|
||||
<FOO>2</FOO>
|
||||
<fooBAR>3</fooBAR>
|
||||
<fooBar>4</fooBar>
|
||||
<foo_bar>5</foo_bar>
|
||||
<foo-bar>6</foo-bar>
|
||||
<foo:bar>7</foo:bar>
|
||||
<foo class='bar'>8</foo>
|
||||
<fooBAr_baz:boom_bar>9</fooBAr_baz:boom_bar>
|
||||
<foo13>10</foo13>
|
||||
<foo2u>11</foo2u>
|
||||
</div>
|
||||
<div class='classes'>
|
||||
<p class='foo bar' id='boom'>
|
||||
</p>
|
||||
<div class='fooBar'>a</div>
|
||||
<div class='foo-bar'>b</div>
|
||||
<div class='foo_bar'>c</div>
|
||||
<div class='FOOBAR'>d</div>
|
||||
<div class='foo16'>e</div>
|
||||
<div class='123'>f</div>
|
||||
<div class='foo2u'>g</div>
|
||||
</div>
|
||||
<div class='broken'>
|
||||
<foo><{ :a => :b }</foo>
|
||||
<div class='foo'>>{ :c => :d }</div>
|
||||
</div>
|
@ -1,7 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -1,94 +0,0 @@
|
||||
<div id='whitespace_test'>
|
||||
<div class='text_area_test_area'>
|
||||
<textarea>Oneline</textarea>
|
||||
</div>
|
||||
<textarea>BLAH
|
||||
</textarea>
|
||||
<div class='text_area_test_area'>
|
||||
<textarea>Two
lines</textarea>
|
||||
</div>
|
||||
<textarea>BLAH
|
||||
</textarea>
|
||||
<div class='text_area_test_area'>
|
||||
<textarea>Oneline</textarea>
|
||||
</div>
|
||||
<textarea>BLAH
</textarea>
|
||||
<div class='text_area_test_area'>
|
||||
<textarea>Two
lines</textarea>
|
||||
</div>
|
||||
<textarea>BLAH
</textarea>
|
||||
<div id='flattened'>
|
||||
<div class='text_area_test_area'>
|
||||
<textarea>Two
lines</textarea>
|
||||
</div>
|
||||
<textarea>BLAH
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class='hithere'>
|
||||
Foo bar
|
||||
<pre>foo bar</pre>
|
||||
<pre>foo
bar</pre>
|
||||
<p><pre>foo
bar</pre></p>
|
||||
<p>
|
||||
foo
|
||||
bar
|
||||
</p>
|
||||
</div>
|
||||
<div class='foo'>
|
||||
13
|
||||
<textarea>
a
</textarea><textarea>
b
</textarea><textarea>
c
</textarea>
|
||||
</div>
|
||||
<div id='whitespace_test'>
|
||||
<div class='text_area_test_area'>
|
||||
<textarea>Oneline</textarea>
|
||||
</div>
|
||||
<textarea>BLAH
|
||||
</textarea>
|
||||
<div class='text_area_test_area'>
|
||||
<textarea>Two
lines</textarea>
|
||||
</div>
|
||||
<textarea>BLAH
|
||||
</textarea>
|
||||
<div class='text_area_test_area'>
|
||||
<textarea>Oneline</textarea>
|
||||
</div>
|
||||
<textarea>BLAH
</textarea>
|
||||
<div class='text_area_test_area'>
|
||||
<textarea>Two
lines</textarea>
|
||||
</div>
|
||||
<textarea>BLAH
</textarea>
|
||||
<div id='flattened'>
|
||||
<div class='text_area_test_area'>
|
||||
<textarea>Two
lines</textarea>
|
||||
</div>
|
||||
<textarea>BLAH
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class='hithere'>
|
||||
Foo bar
|
||||
<pre>foo bar</pre>
|
||||
<pre>foo
bar</pre>
|
||||
<p><pre>foo
bar</pre></p>
|
||||
<p>
|
||||
foo
|
||||
bar
|
||||
</p>
|
||||
<pre>
|
||||
___
 ,o88888
 ,o8888888'
 ,:o:o:oooo. ,8O88Pd8888"
 ,.::.::o:ooooOoOoO. ,oO8O8Pd888'"
 ,.:.::o:ooOoOoOO8O8OOo.8OOPd8O8O"
 , ..:.::o:ooOoOOOO8OOOOo.FdO8O8"
 , ..:.::o:ooOoOO8O888O8O,COCOO"
 , . ..:.::o:ooOoOOOO8OOOOCOCO"
 . ..:.::o:ooOoOoOO8O8OCCCC"o
 . ..:.::o:ooooOoCoCCC"o:o
 . ..:.::o:o:,cooooCo"oo:o:
 ` . . ..:.:cocoooo"'o:o:::'
 .` . ..::ccccoc"'o:o:o:::'
 :.:. ,c:cccc"':.:.:.:.:.'
 ..:.:"'`::::c:"'..:.:.:.:.:.' http://www.chris.com/ASCII/
 ...:.'.:.::::"' . . . . .'
 .. . ....:."' ` . . . ''
 . . . ...."'
 .. . ."' -hrr-
 .


 It's a planet!
%strong This shouldn't be bold!

|
||||
</pre>
|
||||
<strong>This should!</strong>
|
||||
<textarea>
|
||||
___ ___ ___ ___ 
 /\__\ /\ \ /\__\ /\__\
 /:/ / /::\ \ /::| | /:/ /
 /:/__/ /:/\:\ \ /:|:| | /:/ / 
 /::\ \ ___ /::\~\:\ \ /:/|:|__|__ /:/ / 
 /:/\:\ /\__\ /:/\:\ \:\__\ /:/ |::::\__\ /:/__/ 
 \/__\:\/:/ / \/__\:\/:/ / \/__/~~/:/ / \:\ \ 
 \::/ / \::/ / /:/ / \:\ \ 
 /:/ / /:/ / /:/ / \:\ \ 
 /:/ / /:/ / /:/ / \:\__\
 \/__/ \/__/ \/__/ \/__/

 Many
 thanks
 to
 http://www.network-science.de/ascii/

|
||||
<strong>indeed!</strong>
|
||||
</textarea>
|
||||
</div>
|
||||
<div class='foo'>
|
||||
13
|
||||
</div>
|
||||
<pre>
|
||||
__ ______ __ ______
.----.| |--.|__ |.----.| |--..--------.| __ |
| __|| ||__ || __|| < | || __ |
|____||__|__||______||____||__|__||__|__|__||______|

|
||||
</pre>
|
||||
<pre>
|
||||
foo

|
||||
bar
|
||||
</pre>
|
@ -1,55 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en-US'>
|
||||
<head>
|
||||
<title>Hampton Catlin Is Totally Awesome</title>
|
||||
<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
|
||||
</head>
|
||||
<body>
|
||||
<!-- You're In my house now! -->
|
||||
<% concat("Foo", Proc.new {}) %>
|
||||
<div class='header'>
|
||||
Yes, ladies and gentileman. He is just that egotistical.
|
||||
Fantastic! This should be multi-line output
|
||||
The question is if this would translate! Ahah!
|
||||
<%= 1 + 9 + 8 + 2 %>
|
||||
<%# numbers should work and this should be ignored %>
|
||||
</div>
|
||||
<% 120.times do |number| -%>
|
||||
<%= number %>
|
||||
<% end -%>
|
||||
<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>
|
||||
<%= [ 104, 101, 108, 108, 111 ].map do |byte|
|
||||
byte.chr
|
||||
end %>
|
||||
<div class='footer'>
|
||||
<strong class='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.\n" +
|
||||
" So, I'm just making it *really* long. God, I hope this works" %>
|
||||
</strong>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,16 +0,0 @@
|
||||
require 'rubygems'
|
||||
require 'active_support'
|
||||
require 'action_controller'
|
||||
require 'action_view'
|
||||
require '../../lib/haml/template'
|
||||
require 'fileutils'
|
||||
|
||||
haml_template_engine = Haml::Template.new(ActionView::Base.new)
|
||||
haml_template_engine.render(File.dirname(__FILE__) + '/templates/standard.haml')
|
||||
|
||||
begin
|
||||
eval(File.read("template_test.rb"))
|
||||
rescue StandardError => e
|
||||
puts e.backtrace
|
||||
puts e.inspect
|
||||
end
|
@ -1,155 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'test/unit'
|
||||
require 'rubygems'
|
||||
require 'active_support'
|
||||
require 'action_controller'
|
||||
require 'action_view'
|
||||
|
||||
require File.dirname(__FILE__) + '/../../lib/haml'
|
||||
require 'haml/template'
|
||||
require File.dirname(__FILE__) + '/mocks/article'
|
||||
|
||||
class TestFilter
|
||||
def initialize(text)
|
||||
@text = text
|
||||
end
|
||||
|
||||
def render
|
||||
"TESTING HAHAHAHA!"
|
||||
end
|
||||
end
|
||||
|
||||
class TemplateTest < Test::Unit::TestCase
|
||||
@@templates = %w{ very_basic standard helpers
|
||||
whitespace_handling original_engine list helpful
|
||||
silent_script tag_parsing just_stuff partials
|
||||
filters }
|
||||
|
||||
def setup
|
||||
ActionView::Base.register_template_handler("haml", Haml::Template)
|
||||
Haml::Template.options = { :filters => { 'test'=>TestFilter } }
|
||||
@base = ActionView::Base.new(File.dirname(__FILE__) + "/templates/", {'article' => Article.new, 'foo' => 'value one'})
|
||||
end
|
||||
|
||||
def render(text)
|
||||
Haml::Engine.new(text).to_html(@base)
|
||||
end
|
||||
|
||||
def load_result(name)
|
||||
@result = ''
|
||||
File.new(File.dirname(__FILE__) + "/results/#{name}.xhtml").each_line { |l| @result += l }
|
||||
@result
|
||||
end
|
||||
|
||||
def assert_renders_correctly(name)
|
||||
test = Proc.new do |rendered|
|
||||
load_result(name).split("\n").zip(rendered.split("\n")).each_with_index do |pair, line|
|
||||
message = "template: #{name}\nline: #{line}"
|
||||
assert_equal(pair.first, pair.last, message)
|
||||
end
|
||||
end
|
||||
test.call(@base.render(name))
|
||||
|
||||
# If eval's suppressed, the partial won't render anyway :p.
|
||||
unless Haml::Template.options[:suppress_eval]
|
||||
test.call(@base.render(:file => "partialize", :locals => { :name => name }))
|
||||
end
|
||||
end
|
||||
|
||||
def test_empty_render_should_remain_empty
|
||||
assert_equal('', render(''))
|
||||
end
|
||||
|
||||
def test_templates_should_render_correctly
|
||||
@@templates.each do |template|
|
||||
assert_renders_correctly template
|
||||
end
|
||||
end
|
||||
|
||||
def test_action_view_templates_render_correctly
|
||||
@base.instance_variable_set("@content_for_layout", 'Lorem ipsum dolor sit amet')
|
||||
assert_renders_correctly 'content_for_layout'
|
||||
end
|
||||
|
||||
def test_instance_variables_should_work_inside_templates
|
||||
@base.instance_variable_set("@content_for_layout", 'something')
|
||||
assert_equal("<p>something</p>", render("%p= @content_for_layout").chomp)
|
||||
|
||||
@base.instance_eval("@author = 'Hampton Catlin'")
|
||||
assert_equal("<div class='author'>Hampton Catlin</div>", render(".author= @author").chomp)
|
||||
|
||||
@base.instance_eval("@author = 'Hampton'")
|
||||
assert_equal("Hampton", render("= @author").chomp)
|
||||
|
||||
@base.instance_eval("@author = 'Catlin'")
|
||||
assert_equal("Catlin", render("= @author").chomp)
|
||||
end
|
||||
|
||||
def test_instance_variables_should_work_inside_attributes
|
||||
@base.instance_eval("@author = 'hcatlin'")
|
||||
assert_equal("<p class='hcatlin'>foo</p>", render("%p{:class => @author} foo").chomp)
|
||||
end
|
||||
|
||||
def test_template_renders_should_eval
|
||||
assert_equal("2\n", render("= 1+1"))
|
||||
end
|
||||
|
||||
def test_rhtml_still_renders
|
||||
# Make sure it renders normally
|
||||
res = @base.render("../rhtml/standard")
|
||||
assert !(res.nil? || res.empty?)
|
||||
|
||||
# Register Haml stuff in @base...
|
||||
@base.render("standard")
|
||||
|
||||
# Does it still render?
|
||||
res = @base.render("../rhtml/standard")
|
||||
assert !(res.nil? || res.empty?)
|
||||
end
|
||||
|
||||
def test_haml_options
|
||||
Haml::Template.options = { :suppress_eval => true }
|
||||
assert_equal({ :suppress_eval => true }, Haml::Template.options)
|
||||
assert_renders_correctly("eval_suppressed")
|
||||
Haml::Template.options = {}
|
||||
end
|
||||
|
||||
def test_exceptions_should_work_correctly
|
||||
begin
|
||||
Haml::Template.new(@base).render(File.dirname(__FILE__) + '/templates/breakage.haml')
|
||||
rescue Exception => e
|
||||
assert_equal("./test/haml/templates/breakage.haml:4", e.backtrace[0])
|
||||
else
|
||||
assert false
|
||||
end
|
||||
|
||||
begin
|
||||
render("- raise 'oops!'")
|
||||
rescue Exception => e
|
||||
assert_equal("(haml):1", e.backtrace[0])
|
||||
else
|
||||
assert false
|
||||
end
|
||||
|
||||
template = <<END
|
||||
%p
|
||||
%h1 Hello!
|
||||
= "lots of lines"
|
||||
= "even more!"
|
||||
- raise 'oh no!'
|
||||
%p
|
||||
this is after the exception
|
||||
%strong yes it is!
|
||||
ho ho ho.
|
||||
END
|
||||
|
||||
begin
|
||||
render(template.chomp)
|
||||
rescue Exception => e
|
||||
assert_equal("(haml):5", e.backtrace[0])
|
||||
else
|
||||
assert false
|
||||
end
|
||||
end
|
||||
end
|
@ -1,7 +0,0 @@
|
||||
%p
|
||||
@foo =
|
||||
= @foo
|
||||
- @foo = 'value three'
|
||||
%p
|
||||
@foo =
|
||||
= @foo
|
@ -1,3 +0,0 @@
|
||||
.text_area_test_area
|
||||
~ "<textarea>" + value + "</textarea>"
|
||||
= "<textarea>BLAH\n</textarea>"
|
@ -1,8 +0,0 @@
|
||||
%p
|
||||
%h1 Hello!
|
||||
= "lots of lines"
|
||||
- raise "Oh no!"
|
||||
%p
|
||||
this is after the exception
|
||||
%strong yes it is!
|
||||
ho ho ho.
|
@ -1,10 +0,0 @@
|
||||
!!!
|
||||
%html
|
||||
%head
|
||||
%body
|
||||
#content
|
||||
= @content_for_layout
|
||||
#yieldy
|
||||
= yield :layout
|
||||
#nosym
|
||||
= yield
|
@ -1,10 +0,0 @@
|
||||
= "not me!"
|
||||
= "nor me!"
|
||||
- puts "not even me!"
|
||||
%p= "NO!"
|
||||
%h1 Me!
|
||||
#foo
|
||||
%p#bar All
|
||||
%br/
|
||||
%p.baz This
|
||||
Should render
|
@ -1,53 +0,0 @@
|
||||
%style
|
||||
:sass
|
||||
p
|
||||
:border
|
||||
:style dotted
|
||||
:width 10px
|
||||
:color #ff00ff
|
||||
h1
|
||||
:font-weight normal
|
||||
|
||||
:test
|
||||
This
|
||||
Should
|
||||
Not
|
||||
Print
|
||||
|
||||
:redcloth
|
||||
Foo
|
||||
===
|
||||
|
||||
This is preformatted!
|
||||
Look at that!
|
||||
Wowie-zowie!
|
||||
|
||||
*boldilicious!*
|
||||
|
||||
:textile
|
||||
h1. Yeah
|
||||
|
||||
_pretty much the same as above_
|
||||
|
||||
:plain
|
||||
This
|
||||
Is
|
||||
Plain
|
||||
Text
|
||||
%strong right?
|
||||
|
||||
:erb
|
||||
<% 10.times do |c| %>
|
||||
<%= (c+97).chr %>
|
||||
<% end %>
|
||||
|
||||
:markdown
|
||||
* Foo
|
||||
* Bar
|
||||
* BAZ!
|
||||
|
||||
= "Text!"
|
||||
|
||||
:ruby
|
||||
puts "Hello, World!"
|
||||
puts "How are you doing today?"
|
@ -1,63 +0,0 @@
|
||||
= h("&&&&&&&&&&&") # This is an ActionView Helper... should load
|
||||
- foo = capture do # This ActionView Helper is designed for ERB, but should work with haml
|
||||
%div
|
||||
%p.title Title
|
||||
%p.text
|
||||
Woah this is really crazy
|
||||
I mean wow,
|
||||
man.
|
||||
- 3.times do
|
||||
= foo
|
||||
%p foo
|
||||
- tab_up
|
||||
%p reeeeeeeeeeeeeeeeeeeeeeeeeeeeeeally loooooooooooooooooong
|
||||
- tab_down
|
||||
.woah
|
||||
#funky
|
||||
= capture_haml do
|
||||
%div
|
||||
%h1 Big!
|
||||
%p Small
|
||||
/ Invisible
|
||||
= capture do
|
||||
.dilly
|
||||
%p foo
|
||||
%h1 bar
|
||||
= surround '(', ')' do
|
||||
%strong parentheses!
|
||||
= precede '*' do
|
||||
%span.small Not really
|
||||
click
|
||||
= succeed '.' do
|
||||
%a{:href=>"thing"} here
|
||||
%p baz
|
||||
- buffer.tabulation = 10
|
||||
%p boom
|
||||
- concat "foo\n"
|
||||
- buffer.tabulation = 0
|
||||
- def url_for(*stuff); stuff.join(' '); end
|
||||
%p
|
||||
= form_tag 'hello/world'
|
||||
- form_tag 'heeheeaform' do
|
||||
%div= submit_tag 'save'
|
||||
- form_for :article, @article, :url => 'article_url' do |f|
|
||||
Title:
|
||||
= f.text_field :title
|
||||
Body:
|
||||
= f.text_field :body
|
||||
= list_of({:google => 'http://www.google.com'}) do |name, link|
|
||||
%a{ :href => link }= name
|
||||
%p
|
||||
- puts "foo"
|
||||
%div
|
||||
- puts "bar"
|
||||
- puts "boom"
|
||||
baz
|
||||
- puts "boom, again"
|
||||
- open :table do
|
||||
- open :tr do
|
||||
- open :td, {:class => 'cell'} do
|
||||
- open :strong, "strong!"
|
||||
- puts "data"
|
||||
- open :td do
|
||||
- puts "more_data"
|
@ -1,11 +0,0 @@
|
||||
%div[@article]
|
||||
%h1= @article.title
|
||||
%div= @article.body
|
||||
#id[@article] id
|
||||
.class[@article] class
|
||||
#id.class[@article] id class
|
||||
%div{:class => "article full"}[@article]= "boo"
|
||||
%div{'class' => "article full"}[@article]= "moo"
|
||||
%div.articleFull[@article]= "foo"
|
||||
%span[@not_a_real_variable_and_will_be_nil]
|
||||
Boo
|
@ -1,69 +0,0 @@
|
||||
!!! XML
|
||||
!!! XML ISO-8859-1
|
||||
!!! XML UtF-8 Foo bar
|
||||
!!!
|
||||
!!! 1.1
|
||||
!!! 1.1 Strict
|
||||
!!! Strict foo bar
|
||||
!!! FRAMESET
|
||||
%strong{:apos => "Foo's bar!"} Boo!
|
||||
== Embedded? false!
|
||||
== Embedded? #{true}!
|
||||
- embedded = true
|
||||
== Embedded? #{embedded}!
|
||||
.render= render :inline => "%em= 'wow!'", :type => :haml
|
||||
= "stuff followed by whitespace"
|
||||
|
||||
- if true
|
||||
|
||||
%strong block with whitespace
|
||||
%p
|
||||
\Escape
|
||||
\- character
|
||||
\%p foo
|
||||
\yee\ha
|
||||
/ Short comment
|
||||
/ This is a really long comment look how long it is it should be on a line of its own don't you think?
|
||||
/
|
||||
This is a block comment
|
||||
cool, huh?
|
||||
%strong there can even be sub-tags!
|
||||
= "Or script!"
|
||||
-# Haml comment
|
||||
-#
|
||||
Nested Haml comment
|
||||
- raise 'dead'
|
||||
%p{ :class => "" } class attribute shouldn't appear!
|
||||
/[if lte IE6] conditional comment!
|
||||
/[if gte IE7]
|
||||
%p Block conditional comment
|
||||
%div
|
||||
%h1 Cool, eh?
|
||||
/[if gte IE5.2]
|
||||
Woah a period.
|
||||
= "test" |
|
||||
"test" |
|
||||
-# Hard tabs shouldn't throw errors.
|
||||
|
||||
- case :foo
|
||||
- when :bar
|
||||
%br Blah
|
||||
- when :foo
|
||||
%br
|
||||
- case :foo
|
||||
- when :bar
|
||||
%meta{ :foo => 'blah'}
|
||||
- when :foo
|
||||
%meta{ :foo => 'bar'}
|
||||
%img
|
||||
%hr
|
||||
%link
|
||||
%script Inline content
|
||||
%br
|
||||
Nested content
|
||||
%p.foo{:class => true ? 'bar' : 'baz'}[@article] Blah
|
||||
%p.foo{:class => false ? 'bar' : ''}[@article] Blah
|
||||
%p.qux{:class => 'quux'}[@article] Blump
|
||||
== #{"Woah inner quotes"}
|
||||
%p.dynamic_quote{:quotes => "single '", :dyn => 1 + 2}
|
||||
%p.dynamic_atomic{:dyn => 1 + 2}/
|
@ -1,12 +0,0 @@
|
||||
! Not a Doctype !
|
||||
%ul
|
||||
%li a
|
||||
%li b
|
||||
%li c
|
||||
%li d
|
||||
%li e
|
||||
%li f
|
||||
%li g
|
||||
%li h
|
||||
%li i
|
||||
|
@ -1,17 +0,0 @@
|
||||
!!!
|
||||
%html
|
||||
%head
|
||||
%title Stop. haml time
|
||||
#content
|
||||
%h1 This is a title!
|
||||
%p Lorem ipsum dolor sit amet, consectetur adipisicing elit
|
||||
%p{:class => 'foo'} Cigarettes!
|
||||
%h2 Man alive!
|
||||
%ul.things
|
||||
%li Slippers
|
||||
%li Shoes
|
||||
%li Bathrobe
|
||||
%li Coffee
|
||||
%pre
|
||||
This is some text that's in a pre block!
|
||||
Let's see what happens when it's rendered! What about now, since we're on a new line?
|
@ -1 +0,0 @@
|
||||
= render :file => "#{name}.haml"
|
Some files were not shown because too many files have changed in this diff Show More
Reference in new issue