Excise Sass from within files.

This commit is contained in:
Nathan Weizenbaum 2010-08-22 16:04:02 -07:00
parent dca79538bc
commit fd9a367bff
20 changed files with 50 additions and 1679 deletions

1
.gitignore vendored
View File

@ -3,7 +3,6 @@
/doc
/pkg
/test/rails
/.sass-cache
/.haml
/site
*.rbc

View File

@ -2,7 +2,7 @@
--markup markdown
--markup-provider maruku
--default-return ""
--title "Haml/Sass Documentation"
--title "Haml Documentation"
--query 'object.type != :classvariable'
--query 'object.type != :constant || @api && @api.text == "public"'
--hide-void-return

View File

@ -1,4 +1,3 @@
Contributions are welcomed. Please see the following sites for guidelines:
http://haml-lang.com/development.html#contributing
http://sass-lang.com/development.html#contributing

View File

@ -1,4 +1,4 @@
Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
Copyright (c) 2006-2009 Hampton Catlin and Nathan Weizenbaum
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

185
README.md
View File

@ -1,72 +1,40 @@
# Haml and Sass
# Haml
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,
Haml is a templating engine for HTML.
It's are designed to make it both easier and more pleasant
to write HTML documents,
by eliminating redundancy,
reflecting the underlying structure that the document represents,
and providing elegant, easily understandable, and powerful syntax.
## Using
Haml and Sass can be used from the command line
Haml can be used from the command line
or as part of a Ruby web framework.
The first step is to install the gem:
gem install haml
After you convert some HTML to Haml or some CSS to Sass,
you can run
After you convert some HTML to Haml, you can run
haml document.haml
sass style.scss
to compile them.
For more information on these commands, check out
haml --help
sass --help
To install Haml and Sass in Rails 2,
To install Haml in Rails 2,
just add `config.gem "haml"` to `config/environment.rb`.
In Rails 3, add `gem "haml"` to your Gemfile instead.
and both Haml and Sass will be installed.
Views with the `.html.haml` extension will automatically use Haml.
Sass is a little more complicated;
`.sass` 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 reference](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#template_location-option) for details).
For Merb, `.html.haml` views will work without any further modification.
To enable Sass, you also need to add a dependency.
To do so, just add
dependency "merb-haml"
to `config/dependencies.rb` (or `config/init.rb` in a flat/very flat Merb application).
Then it'll work just like it does in Rails.
Sass can also be used with any Rack-enabled web framework.
To do so, just add
require 'sass/plugin/rack'
use Sass::Plugin::Rack
to `config.ru`.
Then any Sass files in `public/stylesheets/sass`
will be compiled CSS files in `public/stylesheets` on every request.
To use Haml and Sass programatically,
To use Haml programatically,
check out the [YARD documentation](http://haml-lang.com/docs/yardoc/).
## Formatting
### Haml
The most basic element of Haml
is a shorthand for creating HTML:
@ -133,141 +101,16 @@ Haml provides far more tools than those presented here.
Check out the [reference documentation](http://beta.haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html)
for full details.
#### Indentation
### Indentation
Haml's indentation can be made up of one or more tabs or spaces.
However, indentation must be consistent within a given document.
Hard tabs and spaces can't be mixed,
and the same number of tabs or spaces must be used throughout.
### Sass
Sass is an extension of CSS
that adds power and elegance to the basic language.
It allows you to use [variables][vars], [nested rules][nested],
[mixins][mixins], [inline imports][imports],
and more, all with a fully CSS-compatible syntax.
Sass helps keep large stylesheets well-organized,
and get small stylesheets up and running quickly,
particularly with the help of
[the Compass style library](http://compass-style.org).
[vars]: http://beta.sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#variables_
[nested]: http://beta.sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#nested_rules_
[mixins]: http://beta.sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#mixins
[imports]: http://beta.sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import
Sass has two syntaxes.
The one presented here, known as "SCSS" (for "Sassy CSS"),
is fully CSS-compatible.
The other (older) syntax, known as the indented syntax or just "Sass",
is whitespace-sensitive and indentation-based.
For more information, see the [reference documentation][syntax].
[syntax]: http://beta.sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#syntax
To run the following examples and see the CSS they produce,
put them in a file called `test.scss` and run `sass test.scss`.
#### Nesting
Sass avoids repetition by nesting selectors within one another.
The same thing works for properties.
table.hl {
margin: 2em 0;
td.ln { text-align: right; }
}
li {
font: {
family: serif;
weight: bold;
size: 1.2em;
}
}
#### Variables
Use the same color all over the place?
Need to do some math with height and width and text size?
Sass supports variables, math operations, and many useful functions.
$blue: #3bbfce;
$margin: 16px;
.content_navigation {
border-color: $blue;
color: darken($blue, 10%);
}
.border {
padding: $margin / 2;
margin: $margin / 2;
border-color: $blue;
}
#### Mixins
Even more powerful than variables,
mixins allow you to re-use whole chunks of CSS,
properties or selectors.
You can even give them arguments.
@mixin table-scaffolding {
th {
text-align: center;
font-weight: bold;
}
td, th { padding: 2px; }
}
@mixin left($dist) {
float: left;
margin-left: $dist;
}
#data {
@include left(10px);
@include table-scaffolding;
}
A comprehensive list of features is available
in the [Sass reference](http://beta.sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html).
## Executables
The Haml gem includes several executables that are useful
for dealing with Haml and Sass from the command line.
### `haml`
The `haml` executable transforms a source Haml file into HTML.
See `haml --help` for further information and options.
### `sass`
The `sass` executable transforms a source Sass file into CSS.
See `sass --help` for further information and options.
### `html2haml`
The `html2haml` executable attempts to transform HTML,
optionally with ERB markup, into Haml code.
Since HTML is so variable, this transformation is not always perfect;
it's a good idea to have a human check the output of this tool.
See `html2haml --help` for further information and options.
### `sass-convert`
The `sass-convert` executable converts between CSS, Sass, and SCSS.
When converting from CSS to Sass or SCSS,
nesting is applied where appropriate.
See `sass-convert --help` for further information and options.
## Authors
Haml and Sass were created by [Hampton Catlin](http://hamptoncatlin.com)
Haml was created by [Hampton Catlin](http://hamptoncatlin.com)
(hcatlin) and he is the author of the original implementation. However, Hampton
doesn't even know his way around the code anymore and now occasionally consults
on the language issues. Hampton lives in Jacksonville, Florida and is the lead
@ -281,14 +124,6 @@ getting Hampton coffee (a fitting task for a boy-genius). Nathan lives in
Seattle, Washington and while not being a student at the University of
Washington or working at an internship, he consults for Unspace Interactive.
[Chris Eppstein](http://acts-as-architect.blogspot.com) is a core contributor to
Sass and the creator of Compass, the first Sass-based framework. Chris focuses
on making Sass more powerful, easy to use, and on ways to speed its adoption
through the web development community. Chris lives in San Jose, California with
his wife and daughter. He is the Software Architect for
[Caring.com](http://caring.com), a website devoted to the 34 Million caregivers
whose parents are sick or elderly, that uses Haml and Sass.
If you use this software, you must pay Hampton a compliment. And
buy Nathan some jelly beans. Maybe pet a kitten. Yeah. Pet that kitty.

108
Rakefile
View File

@ -77,7 +77,7 @@ task :install => [:package] do
end
desc "Release a new Haml package to Rubyforge."
task :release => [:check_release, :release_elpa, :package] do
task :release => [:check_release, :package] do
name = File.read(scope("VERSION_NAME")).strip
version = File.read(scope("VERSION")).strip
sh %{rubyforge add_release haml haml "#{name} (v#{version})" pkg/haml-#{version}.gem}
@ -85,46 +85,6 @@ task :release => [:check_release, :release_elpa, :package] do
sh %{gem push pkg/haml-#{version}.gem}
end
# Releases haml-mode.el and sass-mode.el to ELPA.
task :release_elpa do
require 'tlsmail'
require 'time'
require scope('lib/haml')
next if Haml.version[:prerelease]
version = Haml.version[:number]
haml_unchanged = mode_unchanged?(:haml, version)
sass_unchanged = mode_unchanged?(:sass, version)
next if haml_unchanged && sass_unchanged
raise "haml-mode.el and sass-mode.el are out of sync." if (!!haml_unchanged) ^ (!!sass_unchanged)
if sass_unchanged && File.read(scope("extra/sass-mode.el")).
include?(";; Package-Requires: ((haml-mode #{sass_unchanged.inspect}))")
raise "sass-mode.el doesn't require the same version of haml-mode."
end
from = `git config user.email`.strip
raise "Don't know how to send emails except via Gmail" unless from =~ /@gmail.com$/
to = "elpa@tromey.com"
Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE)
Net::SMTP.start('smtp.gmail.com', 587, 'gmail.com', from, read_password("GMail Password"), :login) do |smtp|
smtp.send_message(<<CONTENT, from, to)
From: Nathan Weizenbaum <#{from}>
To: #{to}
Subject: Submitting haml-mode and sass-mode #{version}
Date: #{Time.now.rfc2822}
haml-mode and sass-mode #{version} are packaged and ready to be included in ELPA.
They can be downloaded from:
http://github.com/nex3/haml/raw/#{Haml.version[:rev]}/extra/haml-mode.el
http://github.com/nex3/haml/raw/#{Haml.version[:rev]}/extra/sass-mode.el
CONTENT
end
end
# Ensures that the version have been updated for a new release.
task :check_release do
version = File.read(scope("VERSION")).strip
@ -155,20 +115,6 @@ def changed_since?(rev, *files)
return !$?.success?
end
# Returns whether or not the given Emacs mode file (haml or sass)
# has changed since the given version.
#
# @param mode [String, Symbol] The name of the mode
# @param version [String] The version number
# @return [String, nil] The version number if the version has changed
def mode_unchanged?(mode, version)
mode_version = File.read(scope("extra/#{mode}-mode.el")).scan(/^;; Version: (.*)$/).first.first
return false if mode_version == version
return mode_version unless changed_since?(mode_version, "extra/#{mode}-mode.el")
raise "#{mode}-mode.el version is #{version.inspect}, but it has changed as of #{version.inspect}"
return false
end
task :submodules do
if File.exist?(File.dirname(__FILE__) + "/.git")
sh %{git submodule sync}
@ -240,7 +186,7 @@ begin
namespace :doc do
task :sass do
require scope('lib/sass')
require 'sass'
Dir[scope("yard/default/**/*.sass")].each do |sass|
File.open(sass.gsub(/sass$/, 'css'), 'w') do |f|
f.write(Sass::Engine.new(File.read(sass)).render)
@ -267,9 +213,6 @@ OPTS
list.exclude('lib/haml/railtie.rb')
list.exclude('lib/haml/helpers/action_view_mods.rb')
list.exclude('lib/haml/helpers/xss_mods.rb')
list.exclude('lib/sass/plugin/merb.rb')
list.exclude('lib/sass/plugin/rails.rb')
list.exclude('lib/sass/less.rb')
end.to_a
t.options << '--incremental' if Rake.application.top_level_tasks.include?('redoc')
t.options += FileList.new(scope('yard/*.rb')).to_a.map {|f| ['-e', f]}.flatten
@ -298,20 +241,19 @@ rescue LoadError
end
task :pages do
puts "#{'=' * 50} Running rake pages"
ensure_git_cleanup do
puts "#{'=' * 50} Running rake pages PROJ=#{ENV["PROJ"].inspect}"
raise 'No ENV["PROJ"]!' unless proj = ENV["PROJ"]
sh %{git checkout #{proj}-pages}
sh %{git reset --hard origin/#{proj}-pages}
sh %{git checkout haml-pages}
sh %{git reset --hard origin/haml-pages}
Dir.chdir("/var/www/#{proj}-pages") do
Dir.chdir("/var/www/haml-pages") do
sh %{git fetch origin}
sh %{git checkout stable}
sh %{git reset --hard origin/stable}
sh %{git checkout #{proj}-pages}
sh %{git reset --hard origin/#{proj}-pages}
sh %{git checkout haml-pages}
sh %{git reset --hard origin/haml-pages}
sh %{rake build --trace}
sh %{mkdir -p tmp}
sh %{touch tmp/restart.txt}
@ -341,31 +283,21 @@ begin
desc <<END
Run a profile of haml.
ENGINE=str sets the engine to be profiled. Defaults to Haml.
TIMES=n sets the number of runs. Defaults to 1000.
FILE=str sets the file to profile.
Defaults to 'standard' for Haml and 'complex' for Sass.
FILE=str sets the file to profile. Defaults to 'standard'
OUTPUT=str sets the ruby-prof output format.
Can be Flat, CallInfo, or Graph. Defaults to Flat. Defaults to Flat.
END
task :profile do
engine = (ENV['ENGINE'] || 'haml').downcase
times = (ENV['TIMES'] || '1000').to_i
file = ENV['FILE']
if engine == 'sass'
require 'lib/sass'
require 'lib/haml'
file = File.read(scope("test/sass/templates/#{file || 'complex'}.sass"))
result = RubyProf.profile { times.times { Sass::Engine.new(file).render } }
else
require 'lib/haml'
file = File.read(scope("test/haml/templates/#{file || 'standard'}.haml"))
obj = Object.new
Haml::Engine.new(file).def_method(obj, :render)
result = RubyProf.profile { times.times { obj.render } }
end
file = File.read(scope("test/haml/templates/#{file || 'standard'}.haml"))
obj = Object.new
Haml::Engine.new(file).def_method(obj, :render)
result = RubyProf.profile { times.times { obj.render } }
RubyProf.const_get("#{(ENV['OUTPUT'] || 'Flat').capitalize}Printer").new(result).print
end
@ -434,7 +366,7 @@ end
task :handle_update do
email_on_error do
unless ENV["REF"] =~ %r{^refs/heads/(master|stable|(?:haml|sass)-pages)$}
unless ENV["REF"] =~ %r{^refs/heads/(master|stable|haml-pages)$}
puts "#{'=' * 20} Ignoring rake handle_update REF=#{ENV["REF"].inspect}"
next
end
@ -451,13 +383,11 @@ task :handle_update do
sh %{git checkout master}
sh %{git reset --hard origin/master}
if branch == "master"
case branch
when "master"
sh %{rake release_edge --trace}
elsif branch == "stable"
sh %{rake pages --trace PROJ=haml}
sh %{rake pages --trace PROJ=sass}
elsif branch =~ /^(haml|sass)-pages$/
sh %{rake pages --trace PROJ=#{$1}}
when "stable", "haml-pages"
sh %{rake pages --trace}
end
puts 'Done running handle_update'

31
TODO
View File

@ -1,4 +1,4 @@
y# -*- mode: org -*-
# -*- mode: org -*-
#+STARTUP: nofold
* Documentation
@ -10,9 +10,6 @@ y# -*- mode: org -*-
* Code
Keep track of error offsets everywhere
Use this to show error location in messages
Just clean up SassScript syntax errors in general
Lexer errors in particular are icky
See in particular error changes made in c07b5c8
** Haml
Support finer-grained HTML-escaping in filters
Speed
@ -25,29 +22,3 @@ y# -*- mode: org -*-
Don't quote attributes that don't require it
http://www.w3.org/TR/REC-html40/intro/sgmltut.html#h-3.2.2
http://www.w3.org/TR/html5/syntax.html#attributes
** Sass
Benchmark the effects of storing the raw template in sassc
If it's expensive, overload RootNode dumping/loading to dup and set @template to nil
Then fall back on reading from actual file
Make Rack middleware the default for Rails and Merb versions that support it
CSS superset
Classes are mixins
Can refer to specific property values? Syntax?
Pull in Compass watcher stuff
Internationalization
Particularly word constituents in Regexps
Optimization
http://csstidy.sourceforge.net/
http://developer.yahoo.com/yui/compressor/
Also comma-folding identical rules where possible
Multiple levels
0: No optimization
1: Nothing that changes doc structure
No comma-folding
2: Anything that keeps functionality identical to O2 (default)
3: Assume order of rules doesn't matter
Comma-fold even if there are intervening rules that might interfere
CSS3
Add (optional) support for http://www.w3.org/TR/css3-values/#calc
Cross-unit arithmetic should compile into this
Should we use "mod" in Sass for consistency?

View File

@ -152,36 +152,9 @@ to
For other plugins, a little searching will probably turn up a way to fix them as well.
## Sass
### Can I use a variable from my controller in my Sass file?
{#q-ruby-code}
No. Sass files aren't views.
They're compiled once into static CSS files,
then left along until they're changed and need to be compiled again.
Not only don't you want to be running a full request cycle
every time someone requests a stylesheet,
but it's not a great idea to put much logic in there anyway
due to how browsers handle them.
If you really need some sort of dynamic CSS,
you can define your own {Sass::Script::Functions Sass functions} using Ruby
that can access the database or other configuration.
*Be aware when doing this that Sass files are by default only compiled once
and then served statically.*
If you really, really need to compile Sass on each request,
first make sure you have adequate caching set up.
Then you can use {Sass::Engine} to render the code,
using the {file:SASS_REFERENCE.md#custom-option `:custom` option}
to pass in data that {Sass::Script::Functions::EvaluationContext#options can be accessed}
from your Sass functions.
## You still haven't answered my question!
Sorry! Try looking at the [Haml](http://haml-lang.com/docs/yardoc/HAML_REFERENCE.md.html)
or [Sass](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html) references,
Sorry! Try looking at the [Haml](http://haml-lang.com/docs/yardoc/HAML_REFERENCE.md.html) reference,
If you can't find an answer there,
feel free to ask in `#haml` on irc.freenode.net
or send an email to the [mailing list](http://groups.google.com/group/haml?hl=en).

View File

@ -1,753 +0,0 @@
;;; haml-mode.el --- Major mode for editing Haml files
;; Copyright (c) 2007, 2008 Nathan Weizenbaum
;; Author: Nathan Weizenbaum
;; URL: http://github.com/nex3/haml/tree/master
;; Version: 3.0.14
;; Created: 2007-03-08
;; By: Nathan Weizenbaum
;; Keywords: markup, language, html
;;; Commentary:
;; Because Haml's indentation schema is similar
;; to that of YAML and Python, many indentation-related
;; functions are similar to those in yaml-mode and python-mode.
;; To install, save this on your load path and add the following to
;; your .emacs file:
;;
;; (require 'haml-mode)
;;; Code:
(eval-when-compile (require 'cl))
(require 'ruby-mode)
;; Additional (optional) libraries for fontification
(require 'css-mode nil t)
(require 'textile-mode nil t)
(require 'markdown-mode nil t)
(require 'javascript-mode "javascript" t)
(require 'js nil t)
;; User definable variables
(defgroup haml nil
"Support for the Haml template language."
:group 'languages
:prefix "haml-")
(defcustom haml-mode-hook nil
"Hook run when entering Haml mode."
:type 'hook
:group 'haml)
(defcustom haml-indent-offset 2
"Amount of offset per level of indentation."
:type 'integer
:group 'haml)
(defcustom haml-backspace-backdents-nesting t
"Non-nil to have `haml-electric-backspace' re-indent blocks of code.
This means that all code nested beneath the backspaced line is
re-indented along with the line itself."
:type 'boolean
:group 'haml)
(defvar haml-indent-function 'haml-indent-p
"A function for checking if nesting is allowed.
This function should look at the current line and return t
if the next line could be nested within this line.
The function can also return a positive integer to indicate
a specific level to which the current line could be indented.")
(defconst haml-tag-beg-re
"^[ \t]*\\(?:[%\\.#][a-z0-9_:\\-]*\\)+\\(?:(.*)\\|{.*}\\|\\[.*\\]\\)*"
"A regexp matching the beginning of a Haml tag, through (), {}, and [].")
(defvar haml-block-openers
`(,(concat haml-tag-beg-re "[><]*[ \t]*$")
"^[ \t]*[&!]?[-=~].*do[ \t]*\\(|.*|[ \t]*\\)?$"
,(concat "^[ \t]*[&!]?[-=~][ \t]*\\("
(regexp-opt '("if" "unless" "while" "until" "else"
"begin" "elsif" "rescue" "ensure" "when"))
"\\)")
"^[ \t]*/\\(\\[.*\\]\\)?[ \t]*$"
"^[ \t]*-#"
"^[ \t]*:")
"A list of regexps that match lines of Haml that open blocks.
That is, a Haml line that can have text nested beneath it should
be matched by a regexp in this list.")
;; Font lock
(defun haml-nested-regexp (re)
"Create a regexp to match a block starting with RE.
The line containing RE is matched, as well as all lines indented beneath it."
(concat "^\\([ \t]*\\)" re "\\(\n\\(?:\\(?:\\1 .*\\| *\\)\n\\)*\\(?:\\1 .*\\| *\\)?\\)?"))
(defconst haml-font-lock-keywords
`((,(haml-nested-regexp "\\(?:-#\\|/\\).*") 0 font-lock-comment-face)
(,(haml-nested-regexp ":\\w+") 0 font-lock-string-face)
(haml-highlight-ruby-filter-block 1 font-lock-preprocessor-face)
(haml-highlight-css-filter-block 1 font-lock-preprocessor-face)
(haml-highlight-textile-filter-block 1 font-lock-preprocessor-face)
(haml-highlight-markdown-filter-block 1 font-lock-preprocessor-face)
(haml-highlight-js-filter-block 1 font-lock-preprocessor-face)
(haml-highlight-interpolation 1 font-lock-variable-name-face prepend)
(haml-highlight-ruby-tag 1 font-lock-preprocessor-face)
(haml-highlight-ruby-script 1 font-lock-preprocessor-face)
("^!!!.*" 0 font-lock-constant-face)
("| *$" 0 font-lock-string-face)))
(defconst haml-filter-re "^[ \t]*:\\w+")
(defconst haml-comment-re "^[ \t]*\\(?:-\\#\\|/\\)")
(defun haml-fontify-region (beg end keywords syntax-table syntactic-keywords)
"Fontify a region between BEG and END using another mode's fontification.
KEYWORDS, SYNTAX-TABLE, and SYNTACTIC-KEYWORDS are the values of that mode's
`font-lock-keywords', `font-lock-syntax-table',
and `font-lock-syntactic-keywords', respectively."
(save-excursion
(save-match-data
(let ((font-lock-keywords keywords)
(font-lock-syntax-table syntax-table)
(font-lock-syntactic-keywords syntactic-keywords)
(font-lock-multiline 'undecided)
font-lock-keywords-only
font-lock-extend-region-functions
font-lock-keywords-case-fold-search)
;; font-lock-fontify-region apparently isn't inclusive,
;; so we have to move the beginning back one char
(font-lock-fontify-region (- beg 1) end)))))
(defun haml-fontify-region-as-ruby (beg end)
"Use Ruby's font-lock variables to fontify the region between BEG and END."
(haml-fontify-region beg end ruby-font-lock-keywords
ruby-font-lock-syntax-table
ruby-font-lock-syntactic-keywords))
(defun haml-handle-filter (filter-name limit fn)
"If a FILTER-NAME filter is found within LIMIT, run FN on that filter.
FN is passed a pair of points representing the beginning and end
of the filtered text."
(when (re-search-forward (haml-nested-regexp (concat ":" filter-name)) limit t)
(funcall fn (+ 2 (match-beginning 2)) (match-end 2))))
(defun haml-fontify-filter-region (filter-name limit &rest fontify-region-args)
"If a FILTER-NAME filter is found within LIMIT, fontify it.
The fontification is done by passing FONTIFY-REGION-ARGS to
`haml-fontify-region'."
(haml-handle-filter filter-name limit
(lambda (beg end)
(apply 'haml-fontify-region
(append (list beg end)
fontify-region-args)))))
(defun haml-highlight-ruby-filter-block (limit)
"If a :ruby filter is found within LIMIT, highlight it."
(haml-handle-filter "ruby" limit 'haml-fontify-region-as-ruby))
(defun haml-highlight-css-filter-block (limit)
"If a :css filter is found within LIMIT, highlight it.
This requires that `css-mode' is available.
`css-mode' is included with Emacs 23."
(if (boundp 'css-font-lock-keywords)
(haml-fontify-filter-region "css" limit css-font-lock-keywords nil nil)))
(defun haml-highlight-js-filter-block (limit)
"If a :javascript filter is found within LIMIT, highlight it.
This requires that Karl Landström's javascript mode be available, either as the
\"js.el\" bundled with Emacs 23, or as \"javascript.el\" found in ELPA and
elsewhere."
(let ((keywords (or (and (featurep 'js) js--font-lock-keywords-3)
(and (featurep 'javascript-mode) js-font-lock-keywords-3)))
(syntax-table (or (and (featurep 'js) js-mode-syntax-table)
(and (featurep 'javascript-mode) javascript-mode-syntax-table))))
(when keywords
(haml-fontify-filter-region "javascript" limit keywords syntax-table nil))))
(defun haml-highlight-textile-filter-block (limit)
"If a :textile filter is found within LIMIT, highlight it.
This requires that `textile-mode' be available.
Note that the results are not perfect, since `textile-mode' expects
certain constructs such as \"h1.\" to be at the beginning of a line,
and indented Haml filters always have leading whitespace."
(if (boundp 'textile-font-lock-keywords)
(haml-fontify-filter-region "textile" limit textile-font-lock-keywords nil nil)))
(defun haml-highlight-markdown-filter-block (limit)
"If a :markdown filter is found within LIMIT, highlight it.
This requires that `markdown-mode' be available."
(if (boundp 'markdown-mode-font-lock-keywords)
(haml-fontify-filter-region "markdown" limit
markdown-mode-font-lock-keywords
markdown-mode-syntax-table
nil)))
(defun haml-highlight-ruby-script (limit)
"Highlight a Ruby script expression (-, =, or ~).
LIMIT works as it does in `re-search-forward'."
(when (re-search-forward "^[ \t]*\\(-\\|[&!]?[=~]\\) \\(.*\\)$" limit t)
(haml-fontify-region-as-ruby (match-beginning 2) (match-end 2))))
(defun haml-highlight-ruby-tag (limit)
"Highlight Ruby code within a Haml tag.
LIMIT works as it does in `re-search-forward'.
This highlights the tag attributes and object refs of the tag,
as well as the script expression (-, =, or ~) following the tag.
For example, this will highlight all of the following:
%p{:foo => 'bar'}
%p[@bar]
%p= 'baz'
%p{:foo => 'bar'}[@bar]= 'baz'"
(when (re-search-forward "^[ \t]*[%.#]" limit t)
(forward-char -1)
;; Highlight tag, classes, and ids
(while (haml-move "\\([.#%]\\)[a-z0-9_:\\-]*")
(put-text-property (match-beginning 0) (match-end 0) 'face
(case (char-after (match-beginning 1))
(?% font-lock-function-name-face)
(?# font-lock-keyword-face)
(?. font-lock-type-face))))
(block loop
(while t
(let ((eol (save-excursion (end-of-line) (point))))
(case (char-after)
;; Highlight obj refs
(?\[
(let ((beg (point)))
(haml-limited-forward-sexp eol)
(haml-fontify-region-as-ruby beg (point))))
;; Highlight new attr hashes
(?\(
(forward-char 1)
(while
(and (haml-parse-new-attr-hash
(lambda (type beg end)
(case type
(name (put-text-property beg end 'face font-lock-constant-face))
(value (haml-fontify-region-as-ruby beg end)))))
(not (eobp)))
(forward-line 1)
(beginning-of-line)))
;; Highlight old attr hashes
(?\{
(let ((beg (point)))
(haml-limited-forward-sexp eol)
;; Check for multiline
(while (and (eolp) (eq (char-before) ?,) (not (eobp)))
(forward-line)
(let ((eol (save-excursion (end-of-line) (point))))
;; If no sexps are closed,
;; we're still continuing a multiline hash
(if (>= (car (parse-partial-sexp (point) eol)) 0)
(end-of-line)
;; If sexps have been closed,
;; set the point at the end of the total sexp
(goto-char beg)
(haml-limited-forward-sexp eol))))
(haml-fontify-region-as-ruby (+ 1 beg) (point))))
(t (return-from loop))))))
;; Move past end chars
(when (looking-at "[<>&!]+") (goto-char (match-end 0)))
;; Highlight script
(if (looking-at "\\([=~]\\) \\(.*\\)$")
(haml-fontify-region-as-ruby (match-beginning 2) (match-end 2))
;; Give font-lock something to highlight
(forward-char -1)
(looking-at "\\(\\)"))
t))
(defun haml-move (re)
"Try matching and moving to the end of regular expression RE.
Returns non-nil if the expression was sucessfully matched."
(when (looking-at re)
(goto-char (match-end 0))
t))
(defun haml-highlight-interpolation (limit)
"Highlight Ruby interpolation (#{foo}).
LIMIT works as it does in `re-search-forward'."
(when (re-search-forward "\\(#{\\)" limit t)
(save-match-data
(forward-char -1)
(let ((beg (point)))
(haml-limited-forward-sexp limit)
(haml-fontify-region-as-ruby (+ 1 beg) (point)))
(when (eq (char-before) ?})
(put-text-property (- (point) 1) (point)
'face font-lock-variable-name-face))
t)))
(defun haml-limited-forward-sexp (limit &optional arg)
"Move forward using `forward-sexp' or to LIMIT, whichever comes first.
With ARG, do it that many times."
(let (forward-sexp-function)
(condition-case err
(save-restriction
(narrow-to-region (point) limit)
(forward-sexp arg))
(scan-error
(unless (equal (nth 1 err) "Unbalanced parentheses")
(signal 'scan-error (cdr err)))
(goto-char limit)))))
(defun* haml-extend-region-filters-comments ()
"Extend the font-lock region to encompass filters and comments."
(let ((old-beg font-lock-beg)
(old-end font-lock-end))
(save-excursion
(goto-char font-lock-beg)
(beginning-of-line)
(unless (or (looking-at haml-filter-re)
(looking-at haml-comment-re))
(return-from haml-extend-region-filters-comments))
(setq font-lock-beg (point))
(haml-forward-sexp)
(beginning-of-line)
(setq font-lock-end (max font-lock-end (point))))
(or (/= old-beg font-lock-beg)
(/= old-end font-lock-end))))
(defun* haml-extend-region-multiline-hashes ()
"Extend the font-lock region to encompass multiline attribute hashes."
(let ((old-beg font-lock-beg)
(old-end font-lock-end))
(save-excursion
(goto-char font-lock-beg)
(let ((attr-props (haml-parse-multiline-attr-hash))
multiline-end)
(when attr-props
(setq font-lock-beg (cdr (assq 'point attr-props)))
(end-of-line)
;; Move through multiline attrs
(when (eq (char-before) ?,)
(save-excursion
(while (progn (end-of-line)
(and (eq (char-before) ?,) (not (eobp))))
(forward-line))
(forward-line -1)
(end-of-line)
(setq multiline-end (point))))
(goto-char (+ (cdr (assq 'point attr-props))
(cdr (assq 'hash-indent attr-props))
-1))
(haml-limited-forward-sexp
(or multiline-end
(save-excursion (end-of-line) (point))))
(setq font-lock-end (max font-lock-end (point))))))
(or (/= old-beg font-lock-beg)
(/= old-end font-lock-end))))
;; Mode setup
(defvar haml-mode-syntax-table
(let ((table (make-syntax-table)))
(modify-syntax-entry ?: "." table)
(modify-syntax-entry ?_ "w" table)
table)
"Syntax table in use in `haml-mode' buffers.")
(defvar haml-mode-map
(let ((map (make-sparse-keymap)))
(define-key map [backspace] 'haml-electric-backspace)
(define-key map "\C-?" 'haml-electric-backspace)
(define-key map "\C-c\C-f" 'haml-forward-sexp)
(define-key map "\C-c\C-b" 'haml-backward-sexp)
(define-key map "\C-c\C-u" 'haml-up-list)
(define-key map "\C-c\C-d" 'haml-down-list)
(define-key map "\C-c\C-k" 'haml-kill-line-and-indent)
(define-key map "\C-c\C-r" 'haml-output-region)
(define-key map "\C-c\C-l" 'haml-output-buffer)
map))
;;;###autoload
(define-derived-mode haml-mode fundamental-mode "Haml"
"Major mode for editing Haml files.
\\{haml-mode-map}"
(set-syntax-table haml-mode-syntax-table)
(add-to-list 'font-lock-extend-region-functions 'haml-extend-region-filters-comments)
(add-to-list 'font-lock-extend-region-functions 'haml-extend-region-multiline-hashes)
(set (make-local-variable 'font-lock-multiline) t)
(set (make-local-variable 'indent-line-function) 'haml-indent-line)
(set (make-local-variable 'indent-region-function) 'haml-indent-region)
(set (make-local-variable 'parse-sexp-lookup-properties) t)
(setq comment-start "-#")
(setq font-lock-defaults '((haml-font-lock-keywords) t t)))
;; Useful functions
(defun haml-comment-block ()
"Comment the current block of Haml code."
(interactive)
(save-excursion
(let ((indent (current-indentation)))
(back-to-indentation)
(insert "-#")
(newline)
(indent-to indent)
(beginning-of-line)
(haml-mark-sexp)
(haml-reindent-region-by haml-indent-offset))))
(defun haml-uncomment-block ()
"Uncomment the current block of Haml code."
(interactive)
(save-excursion
(beginning-of-line)
(while (not (looking-at haml-comment-re))
(haml-up-list)
(beginning-of-line))
(haml-mark-sexp)
(kill-line 1)
(haml-reindent-region-by (- haml-indent-offset))))
(defun haml-replace-region (start end)
"Replace the current block of Haml code with the HTML equivalent.
Called from a program, START and END specify the region to indent."
(interactive "r")
(save-excursion
(goto-char end)
(setq end (point-marker))
(goto-char start)
(let ((ci (current-indentation)))
(while (re-search-forward "^ +" end t)
(replace-match (make-string (- (current-indentation) ci) ? ))))
(shell-command-on-region start end "haml" "haml-output" t)))
(defun haml-output-region (start end)
"Displays the HTML output for the current block of Haml code.
Called from a program, START and END specify the region to indent."
(interactive "r")
(kill-new (buffer-substring start end))
(with-temp-buffer
(yank)
(haml-indent-region (point-min) (point-max))
(shell-command-on-region (point-min) (point-max) "haml" "haml-output")))
(defun haml-output-buffer ()
"Displays the HTML output for entire buffer."
(interactive)
(haml-output-region (point-min) (point-max)))
;; Navigation
(defun haml-forward-through-whitespace (&optional backward)
"Move the point forward through any whitespace.
The point will move forward at least one line, until it reaches
either the end of the buffer or a line with no whitespace.
If BACKWARD is non-nil, move the point backward instead."
(let ((arg (if backward -1 1))
(endp (if backward 'bobp 'eobp)))
(loop do (forward-line arg)
while (and (not (funcall endp))
(looking-at "^[ \t]*$")))))
(defun haml-at-indent-p ()
"Return non-nil if the point is before any text on the line."
(let ((opoint (point)))
(save-excursion
(back-to-indentation)
(>= (point) opoint))))
(defun haml-forward-sexp (&optional arg)
"Move forward across one nested expression.
With ARG, do it that many times. Negative arg -N means move
backward across N balanced expressions.
A sexp in Haml is defined as a line of Haml code as well as any
lines nested beneath it."
(interactive "p")
(or arg (setq arg 1))
(if (and (< arg 0) (not (haml-at-indent-p)))
(back-to-indentation)
(while (/= arg 0)
(let ((indent (current-indentation)))
(loop do (haml-forward-through-whitespace (< arg 0))
while (and (not (eobp))
(not (bobp))
(> (current-indentation) indent)))
(back-to-indentation)
(setq arg (+ arg (if (> arg 0) -1 1)))))))
(defun haml-backward-sexp (&optional arg)
"Move backward across one nested expression.
With ARG, do it that many times. Negative arg -N means move
forward across N balanced expressions.
A sexp in Haml is defined as a line of Haml code as well as any
lines nested beneath it."
(interactive "p")
(haml-forward-sexp (if arg (- arg) -1)))
(defun haml-up-list (&optional arg)
"Move out of one level of nesting.
With ARG, do this that many times."
(interactive "p")
(or arg (setq arg 1))
(while (> arg 0)
(let ((indent (current-indentation)))
(loop do (haml-forward-through-whitespace t)
while (and (not (bobp))
(>= (current-indentation) indent)))
(setq arg (- arg 1))))
(back-to-indentation))
(defun haml-down-list (&optional arg)
"Move down one level of nesting.
With ARG, do this that many times."
(interactive "p")
(or arg (setq arg 1))
(while (> arg 0)
(let ((indent (current-indentation)))
(haml-forward-through-whitespace)
(when (<= (current-indentation) indent)
(haml-forward-through-whitespace t)
(back-to-indentation)
(error "Nothing is nested beneath this line"))
(setq arg (- arg 1))))
(back-to-indentation))
(defun haml-mark-sexp ()
"Mark the next Haml block."
(let ((forward-sexp-function 'haml-forward-sexp))
(mark-sexp)))
(defun haml-mark-sexp-but-not-next-line ()
"Mark the next Haml block, but not the next line.
Put the mark at the end of the last line of the sexp rather than
the first non-whitespace character of the next line."
(haml-mark-sexp)
(set-mark
(save-excursion
(goto-char (mark))
(forward-line -1)
(end-of-line)
(point))))
;; Indentation and electric keys
(defun* haml-indent-p ()
"Returns t if the current line can have lines nested beneath it."
(let ((attr-props (haml-parse-multiline-attr-hash)))
(when attr-props
(return-from haml-indent-p
(if (haml-unclosed-attr-hash-p) (cdr (assq 'hash-indent attr-props))
(list (+ (cdr (assq 'indent attr-props)) haml-indent-offset) nil)))))
(loop for opener in haml-block-openers
if (looking-at opener) return t
finally return nil))
(defun* haml-parse-multiline-attr-hash ()
"Parses a multiline attribute hash, and returns
an alist with the following keys:
INDENT is the indentation of the line beginning the hash.
HASH-INDENT is the indentation of the first character
within the attribute hash.
POINT is the character position at the beginning of the line
beginning the hash."
(save-excursion
(while t
(beginning-of-line)
(if (looking-at (concat haml-tag-beg-re "\\([{(]\\)"))
(progn
(goto-char (- (match-end 0) 1))
(haml-limited-forward-sexp (save-excursion (end-of-line) (point)))
(return-from haml-parse-multiline-attr-hash
(when (or (string-equal (match-string 1) "(") (eq (char-before) ?,))
`((indent . ,(current-indentation))
(hash-indent . ,(- (match-end 0) (match-beginning 0)))
(point . ,(match-beginning 0))))))
(when (bobp) (return-from haml-parse-multiline-attr-hash))
(forward-line -1)
(unless (haml-unclosed-attr-hash-p)
(return-from haml-parse-multiline-attr-hash))))))
(defun* haml-unclosed-attr-hash-p ()
"Return t if this line has an unclosed attribute hash, new or old."
(save-excursion
(end-of-line)
(when (eq (char-before) ?,) (return-from haml-unclosed-attr-hash-p t))
(re-search-backward "(\\|^")
(haml-move "(")
(haml-parse-new-attr-hash)))
(defun* haml-parse-new-attr-hash (&optional (fn (lambda (type beg end) ())))
"Parse a new-style attribute hash on this line, and returns
t if it's not finished on the current line.
FN should take three parameters: TYPE, BEG, and END.
TYPE is the type of text parsed ('name or 'value)
and BEG and END delimit that text in the buffer."
(let ((eol (save-excursion (end-of-line) (point))))
(while (not (haml-move ")"))
(haml-move "[ \t]*")
(unless (haml-move "[a-z0-9_:\\-]+")
(return-from haml-parse-new-attr-hash (haml-move "[ \t]*$")))
(funcall fn 'name (match-beginning 0) (match-end 0))
(haml-move "[ \t]*")
(when (haml-move "=")
(haml-move "[ \t]*")
(unless (looking-at "[\"'@a-z]") (return-from haml-parse-new-attr-hash))
(let ((beg (point)))
(haml-limited-forward-sexp eol)
(funcall fn 'value beg (point)))
(haml-move "[ \t]*")))
nil))
(defun haml-compute-indentation ()
"Calculate the maximum sensible indentation for the current line."
(save-excursion
(beginning-of-line)
(if (bobp) (list 0 nil)
(haml-forward-through-whitespace t)
(let ((indent (funcall haml-indent-function)))
(cond
((consp indent) indent)
((integerp indent) (list indent t))
(indent (list (+ (current-indentation) haml-indent-offset) nil))
(t (list (current-indentation) nil)))))))
(defun haml-indent-region (start end)
"Indent each nonblank line in the region.
This is done by indenting the first line based on
`haml-compute-indentation' and preserving the relative
indentation of the rest of the region. START and END specify the
region to indent.
If this command is used multiple times in a row, it will cycle
between possible indentations."
(save-excursion
(goto-char end)
(setq end (point-marker))
(goto-char start)
(let (this-line-column current-column
(next-line-column
(if (and (equal last-command this-command) (/= (current-indentation) 0))
(* (/ (- (current-indentation) 1) haml-indent-offset) haml-indent-offset)
(car (haml-compute-indentation)))))
(while (< (point) end)
(setq this-line-column next-line-column
current-column (current-indentation))
;; Delete whitespace chars at beginning of line
(delete-horizontal-space)
(unless (eolp)
(setq next-line-column (save-excursion
(loop do (forward-line 1)
while (and (not (eobp)) (looking-at "^[ \t]*$")))
(+ this-line-column
(- (current-indentation) current-column))))
;; Don't indent an empty line
(unless (eolp) (indent-to this-line-column)))
(forward-line 1)))
(move-marker end nil)))
(defun haml-indent-line ()
"Indent the current line.
The first time this command is used, the line will be indented to the
maximum sensible indentation. Each immediately subsequent usage will
back-dent the line by `haml-indent-offset' spaces. On reaching column
0, it will cycle back to the maximum sensible indentation."
(interactive "*")
(let ((ci (current-indentation))
(cc (current-column)))
(destructuring-bind (need strict) (haml-compute-indentation)
(save-excursion
(beginning-of-line)
(delete-horizontal-space)
(if (and (not strict) (equal last-command this-command) (/= ci 0))
(indent-to (* (/ (- ci 1) haml-indent-offset) haml-indent-offset))
(indent-to need))))
(when (< (current-column) (current-indentation))
(forward-to-indentation 0))))
(defun haml-reindent-region-by (n)
"Add N spaces to the beginning of each line in the region.
If N is negative, will remove the spaces instead. Assumes all
lines in the region have indentation >= that of the first line."
(let* ((ci (current-indentation))
(indent-rx
(concat "^"
(if indent-tabs-mode
(concat (make-string (/ ci tab-width) ?\t)
(make-string (mod ci tab-width) ?\t))
(make-string ci ?\s)))))
(save-excursion
(while (re-search-forward indent-rx (mark) t)
(let ((ci (current-indentation)))
(delete-horizontal-space)
(beginning-of-line)
(indent-to (max 0 (+ ci n))))))))
(defun haml-electric-backspace (arg)
"Delete characters or back-dent the current line.
If invoked following only whitespace on a line, will back-dent
the line and all nested lines to the immediately previous
multiple of `haml-indent-offset' spaces. With ARG, do it that
many times.
Set `haml-backspace-backdents-nesting' to nil to just back-dent
the current line."
(interactive "*p")
(if (or (/= (current-indentation) (current-column))
(bolp)
(looking-at "^[ \t]+$"))
(backward-delete-char arg)
(save-excursion
(let ((ci (current-column)))
(beginning-of-line)
(if haml-backspace-backdents-nesting
(haml-mark-sexp-but-not-next-line)
(set-mark (save-excursion (end-of-line) (point))))
(haml-reindent-region-by (* (- arg) haml-indent-offset))
(pop-mark)))
(back-to-indentation)))
(defun haml-kill-line-and-indent ()
"Kill the current line, and re-indent all lines nested beneath it."
(interactive)
(beginning-of-line)
(haml-mark-sexp-but-not-next-line)
(kill-line 1)
(haml-reindent-region-by (* -1 haml-indent-offset)))
(defun haml-indent-string ()
"Return the indentation string for `haml-indent-offset'."
(mapconcat 'identity (make-list haml-indent-offset " ") ""))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.haml$" . haml-mode))
;; Setup/Activation
(provide 'haml-mode)
;;; haml-mode.el ends here

View File

@ -7,9 +7,9 @@ require 'rubygems'
HAML_GEMSPEC = Gem::Specification.new do |spec|
spec.rubyforge_project = 'haml'
spec.name = File.exist?(File.dirname(__FILE__) + '/EDGE_GEM_VERSION') ? 'haml-edge' : 'haml'
spec.summary = "An elegant, structured XHTML/XML templating engine.\nComes with Sass, a similar CSS templating engine."
spec.summary = "An elegant, structured XHTML/XML templating engine."
spec.version = File.read(File.dirname(__FILE__) + '/VERSION').strip
spec.authors = ['Nathan Weizenbaum', 'Chris Eppstein', 'Hampton Catlin']
spec.authors = ['Nathan Weizenbaum', 'Hampton Catlin']
spec.email = 'haml@googlegroups.com'
spec.description = <<-END
Haml (HTML Abstraction Markup Language) is a layer on top of XHTML or XML
@ -25,7 +25,7 @@ HAML_GEMSPEC = Gem::Specification.new do |spec|
spec.add_development_dependency 'maruku', '>= 0.5.9'
readmes = Dir['*'].reject{ |x| x =~ /(^|[^.a-z])[a-z]+/ || x == "TODO" }
spec.executables = ['haml', 'html2haml', 'sass', 'css2sass', 'sass-convert']
spec.executables = ['haml', 'html2haml']
spec.files = Dir['rails/init.rb', 'lib/**/*', 'vendor/**/*',
'bin/*', 'test/**/*', 'extra/**/*', 'Rakefile', 'init.rb',
'.yardopts'] + readmes

View File

@ -13,6 +13,6 @@ rescue LoadError
end
end
# Load Haml and Sass.
# Load Haml.
# Haml may be undefined if we're running gems:install.
Haml.init_rails(binding) if defined?(Haml)

View File

@ -33,12 +33,10 @@ module Haml
# it's just passed in in case it needs to be used in the future
def self.init_rails(binding)
# No &method here for Rails 2.1 compatibility
%w[haml/template sass sass/plugin].each {|f| require f}
%w[haml/template].each {|f| require f}
end
end
require 'haml/util'
unless $0 =~ /sass(-convert)?$/
require 'haml/engine'
require 'haml/railtie'
end
require 'haml/engine'
require 'haml/railtie'

View File

@ -2,7 +2,7 @@ require 'optparse'
require 'fileutils'
module Haml
# This module handles the various Haml executables (`haml`, `sass`, `sass-convert`, etc).
# This module handles the various Haml executables (`haml` and `haml-convert`).
module Exec
# An abstract class that encapsulates the executable code for all three executables.
class Generic
@ -91,7 +91,7 @@ module Haml
end
opts.on_tail("-v", "--version", "Print version") do
puts("Haml/Sass #{::Haml.version[:string]}")
puts("Haml #{::Haml.version[:string]}")
exit
end
end
@ -168,234 +168,6 @@ MESSAGE
end
end
# The `sass` executable.
class Sass < Generic
# @param args [Array<String>] The command-line arguments
def initialize(args)
super
@options[:for_engine] = {
:load_paths => ['.'] + (ENV['SASSPATH'] || '').split(File::PATH_SEPARATOR)
}
end
protected
# Tells optparse how to parse the arguments.
#
# @param opts [OptionParser]
def set_opts(opts)
super
opts.banner = <<END
Usage: sass [options] [INPUT] [OUTPUT]
Description:
Converts SCSS or Sass files to CSS.
Options:
END
opts.on('--scss',
'Use the CSS-superset SCSS syntax.') do
@options[:for_engine][:syntax] = :scss
end
opts.on('--watch', 'Watch files or directories for changes.',
'The location of the generated CSS can be set using a colon:',
' sass --watch input.sass:output.css',
' sass --watch input-dir:output-dir') do
@options[:watch] = true
end
opts.on('--update', 'Compile files or directories to CSS.',
'Locations are set like --watch.') do
@options[:update] = true
end
opts.on('--stop-on-error', 'If a file fails to compile, exit immediately.',
'Only meaningful for --watch and --update.') do
@options[:stop_on_error] = true
end
opts.on('-c', '--check', "Just check syntax, don't evaluate.") do
require 'stringio'
@options[:check_syntax] = true
@options[:output] = StringIO.new
end
opts.on('-t', '--style NAME',
'Output style. Can be nested (default), compact, compressed, or expanded.') do |name|
@options[:for_engine][:style] = name.to_sym
end
opts.on('-q', '--quiet', 'Silence warnings during compilation.') do
@options[:for_engine][:quiet] = true
end
opts.on('-g', '--debug-info',
'Emit extra information in the generated CSS that can be used by the FireSass Firebug plugin.') do
@options[:for_engine][:debug_info] = true
end
opts.on('-l', '--line-numbers', '--line-comments',
'Emit comments in the generated CSS indicating the corresponding sass line.') do
@options[:for_engine][:line_numbers] = true
end
opts.on('-i', '--interactive',
'Run an interactive SassScript shell.') do
@options[:interactive] = true
end
opts.on('-I', '--load-path PATH', 'Add a sass import path.') do |path|
@options[:for_engine][:load_paths] << path
end
opts.on('-r', '--require LIB', 'Require a Ruby library before running Sass.') do |lib|
require lib
end
opts.on('--cache-location PATH', 'The path to put cached Sass files. Defaults to .sass-cache.') do |loc|
@options[:for_engine][:cache_location] = loc
end
opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
@options[:for_engine][:cache] = false
end
unless ::Haml::Util.ruby1_8?
opts.on('-E encoding', 'Specify the default encoding for Sass files.') do |encoding|
Encoding.default_external = encoding
end
end
end
# Processes the options set by the command-line arguments,
# and runs the Sass compiler appropriately.
def process_result
require 'sass'
if !@options[:update] && !@options[:watch] &&
@args.first && colon_path?(@args.first)
if @args.size == 1
@args = split_colon_path(@args.first)
else
@options[:update] = true
end
end
return interactive if @options[:interactive]
return watch_or_update if @options[:watch] || @options[:update]
super
@options[:for_engine][:filename] = @options[:filename]
begin
input = @options[:input]
output = @options[:output]
@options[:syntax] ||= :scss if input.is_a?(File) && input.path =~ /\.scss$/
tree =
if input.is_a?(File) && !@options[:check_syntax]
::Sass::Files.tree_for(input.path, @options[:for_engine])
else
# We don't need to do any special handling of @options[:check_syntax] here,
# because the Sass syntax checking happens alongside evaluation
# and evaluation doesn't actually evaluate any code anyway.
::Sass::Engine.new(input.read(), @options[:for_engine]).to_tree
end
input.close() if input.is_a?(File)
output.write(tree.render)
output.close() if output.is_a? File
rescue ::Sass::SyntaxError => e
raise e if @options[:trace]
raise e.sass_backtrace_str("standard input")
end
end
private
def interactive
require 'sass/repl'
::Sass::Repl.new(@options).run
end
def watch_or_update
require 'sass/plugin'
::Sass::Plugin.options.merge! @options[:for_engine]
::Sass::Plugin.options[:unix_newlines] = @options[:unix_newlines]
raise <<MSG if @args.empty?
What files should I watch? Did you mean something like:
sass --watch input.sass:output.css
sass --watch input-dir:output-dir
MSG
if !colon_path?(@args[0]) && probably_dest_dir?(@args[1])
flag = @options[:update] ? "--update" : "--watch"
err =
if !File.exist?(@args[1])
"doesn't exist"
elsif @args[1] =~ /\.css$/
"is a CSS file"
end
raise <<MSG if err
File #{@args[1]} #{err}.
Did you mean: sass #{flag} #{@args[0]}:#{@args[1]}
MSG
end
dirs, files = @args.map {|name| split_colon_path(name)}.
partition {|i, _| File.directory? i}
files.map! {|from, to| [from, to || from.gsub(/\..*?$/, '.css')]}
dirs.map! {|from, to| [from, to || from]}
::Sass::Plugin.options[:template_location] = dirs
::Sass::Plugin.on_updating_stylesheet do |_, css|
if File.exists? css
puts_action :overwrite, :yellow, css
else
puts_action :create, :green, css
end
end
had_error = false
::Sass::Plugin.on_creating_directory {|dirname| puts_action :directory, :green, dirname}
::Sass::Plugin.on_deleting_css {|filename| puts_action :delete, :yellow, filename}
::Sass::Plugin.on_compilation_error do |error, _, _|
raise error unless error.is_a?(::Sass::SyntaxError) && !@options[:stop_on_error]
had_error = true
puts_action :error, :red, "#{error.sass_filename} (Line #{error.sass_line}: #{error.message})"
end
if @options[:update]
::Sass::Plugin.update_stylesheets(files)
exit 1 if had_error
return
end
puts ">>> Sass is watching for changes. Press Ctrl-C to stop."
::Sass::Plugin.on_template_modified {|template| puts ">>> Change detected to: #{template}"}
::Sass::Plugin.on_template_created {|template| puts ">>> New template detected: #{template}"}
::Sass::Plugin.on_template_deleted {|template| puts ">>> Deleted template detected: #{template}"}
::Sass::Plugin.watch(files)
end
def colon_path?(path)
!split_colon_path(path)[1].nil?
end
def split_colon_path(path)
one, two = path.split(':', 2)
if one && two && ::Haml::Util.windows? &&
one =~ /\A[A-Za-z]\Z/ && two =~ /\A[\/\\]/
# If we're on Windows and we were passed a drive letter path,
# don't split on that colon.
one2, two = two.split(':', 2)
one = one + ':' + one2
end
return one, two
end
# Whether path is likely to be meant as the destination
# in a source:dest pair.
def probably_dest_dir?(path)
return false unless path
return false if colon_path?(path)
return Dir.glob(File.join(path, "*.s[ca]ss")).empty?
end
end
# The `haml` executable.
class Haml < Generic
# @param args [Array<String>] The command-line arguments
@ -574,228 +346,5 @@ END
handle_load_error(err)
end
end
# The `sass-convert` executable.
class SassConvert < Generic
# @param args [Array<String>] The command-line arguments
def initialize(args)
super
require 'sass'
@options[:for_tree] = {}
@options[:for_engine] = {:cache => false, :read_cache => true}
end
# Tells optparse how to parse the arguments.
#
# @param opts [OptionParser]
def set_opts(opts)
opts.banner = <<END
Usage: sass-convert [options] [INPUT] [OUTPUT]
Description:
Converts between CSS, Sass, and SCSS files.
E.g. converts from SCSS to Sass,
or converts from CSS to SCSS (adding appropriate nesting).
Options:
END
opts.on('-F', '--from FORMAT',
'The format to convert from. Can be css, scss, sass, less, or sass2.',
'sass2 is the same as sass, but updates more old syntax to new.',
'By default, this is inferred from the input filename.',
'If there is none, defaults to css.') do |name|
@options[:from] = name.downcase.to_sym
unless [:css, :scss, :sass, :less, :sass2].include?(@options[:from])
raise "Unknown format for sass-convert --from: #{name}"
end
try_less_note if @options[:from] == :less
end
opts.on('-T', '--to FORMAT',
'The format to convert to. Can be scss or sass.',
'By default, this is inferred from the output filename.',
'If there is none, defaults to sass.') do |name|
@options[:to] = name.downcase.to_sym
unless [:scss, :sass].include?(@options[:to])
raise "Unknown format for sass-convert --to: #{name}"
end
end
opts.on('-R', '--recursive',
'Convert all the files in a directory. Requires --from and --to.') do
@options[:recursive] = true
end
opts.on('-i', '--in-place',
'Convert a file to its own syntax.',
'This can be used to update some deprecated syntax.') do
@options[:in_place] = true
end
opts.on('--dasherize', 'Convert underscores to dashes') do
@options[:for_tree][:dasherize] = true
end
opts.on('--old', 'Output the old-style ":prop val" property syntax.',
'Only meaningful when generating Sass.') do
@options[:for_tree][:old] = true
end
opts.on('-C', '--no-cache', "Don't cache to sassc files.") do
@options[:for_engine][:read_cache] = false
end
unless ::Haml::Util.ruby1_8?
opts.on('-E encoding', 'Specify the default encoding for Sass and CSS files.') do |encoding|
Encoding.default_external = encoding
end
end
super
end
# Processes the options set by the command-line arguments,
# and runs the CSS compiler appropriately.
def process_result
require 'sass'
if @options[:recursive]
process_directory
return
end
super
input = @options[:input]
raise "Error: '#{input.path}' is a directory (did you mean to use --recursive?)" if File.directory?(input)
output = @options[:output]
output = input if @options[:in_place]
process_file(input, output)
end
private
def process_directory
unless input = @options[:input] = @args.shift
raise "Error: directory required when using --recursive."
end
output = @options[:output] = @args.shift
raise "Error: --from required when using --recursive." unless @options[:from]
raise "Error: --to required when using --recursive." unless @options[:to]
raise "Error: '#{@options[:input]}' is not a directory" unless File.directory?(@options[:input])
if @options[:output] && File.exists?(@options[:output]) && !File.directory?(@options[:output])
raise "Error: '#{@options[:output]}' is not a directory"
end
@options[:output] ||= @options[:input]
from = @options[:from]
from = :sass if from == :sass2
if @options[:to] == @options[:from] && !@options[:in_place]
fmt = @options[:from]
raise "Error: converting from #{fmt} to #{fmt} without --in-place"
end
ext = @options[:from]
ext = :sass if ext == :sass2
Dir.glob("#{@options[:input]}/**/*.#{ext}") do |f|
output =
if @options[:in_place]
f
elsif @options[:output]
output_name = f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
output_name[0...@options[:input].size] = @options[:output]
output_name
else
f.gsub(/\.(c|sa|sc|le)ss$/, ".#{@options[:to]}")
end
unless File.directory?(File.dirname(output))
puts_action :directory, :green, File.dirname(output)
FileUtils.mkdir_p(File.dirname(output))
end
puts_action :convert, :green, f
if File.exists?(output)
puts_action :overwrite, :yellow, output
else
puts_action :create, :green, output
end
input = open_file(f)
output = @options[:in_place] ? input : open_file(output, "w")
process_file(input, output)
end
end
def process_file(input, output)
if input.is_a?(File)
@options[:from] ||=
case input.path
when /\.scss$/; :scss
when /\.sass$/; :sass
when /\.less$/; :less
when /\.css$/; :css
end
elsif @options[:in_place]
raise "Error: the --in-place option requires a filename."
end
if output.is_a?(File)
@options[:to] ||=
case output.path
when /\.scss$/; :scss
when /\.sass$/; :sass
end
end
if @options[:from] == :sass2
@options[:from] = :sass
@options[:for_engine][:sass2] = true
end
@options[:from] ||= :css
@options[:to] ||= :sass
@options[:for_engine][:syntax] = @options[:from]
out =
::Haml::Util.silence_haml_warnings do
if @options[:from] == :css
require 'sass/css'
::Sass::CSS.new(input.read, @options[:for_tree]).render(@options[:to])
elsif @options[:from] == :less
require 'sass/less'
try_less_note
input = input.read if input.is_a?(IO) && !input.is_a?(File) # Less is dumb
Less::Engine.new(input).to_tree.to_sass_tree.send("to_#{@options[:to]}", @options[:for_tree])
else
if input.is_a?(File)
::Sass::Files.tree_for(input.path, @options[:for_engine])
else
::Sass::Engine.new(input.read, @options[:for_engine]).to_tree
end.send("to_#{@options[:to]}", @options[:for_tree])
end
end
output = File.open(input.path, 'w') if @options[:in_place]
output.write(out)
rescue ::Sass::SyntaxError => e
raise e if @options[:trace]
file = " of #{e.sass_filename}" if e.sass_filename
raise "Error on line #{e.sass_line}#{file}: #{e.message}\n Use --trace for backtrace"
rescue LoadError => err
handle_load_error(err)
end
@@less_note_printed = false
def try_less_note
return if @@less_note_printed
@@less_note_printed = true
warn <<NOTE
* NOTE: Sass and Less are different languages, and they work differently.
* I'll do my best to translate, but some features -- especially mixins --
* should be checked by hand.
NOTE
end
end
end
end

View File

@ -19,7 +19,7 @@ module Haml
#
# <div class="entry show">My Div</div>
#
# Then, in a stylesheet (shown here as {Sass}),
# Then, in a stylesheet (shown here as [Sass](http://sass-lang.com)),
# you could refer to this specific action:
#
# .entry.show

View File

@ -1,6 +1,6 @@
if Haml::Util.ap_geq_3? && !Haml::Util.ap_geq?("3.0.0.beta4")
raise <<ERROR
Haml and Sass no longer support Rails 3 versions before beta 4.
Haml no longer supports Rails 3 versions before beta 4.
Please upgrade to Rails 3.0.0.beta4 or later.
ERROR
end
@ -8,12 +8,7 @@ end
# Rails 3.0.0.beta.2+
if defined?(ActiveSupport) && Haml::Util.has?(:public_method, ActiveSupport, :on_load)
require 'haml/template/options'
require 'sass/plugin/configuration'
ActiveSupport.on_load(:before_initialize) do
require 'sass'
require 'sass/plugin'
# Haml requires AV, but Sass doesn't
ActiveSupport.on_load(:action_view) do
Haml.init_rails(binding)
end

View File

@ -6,7 +6,6 @@ require 'strscan'
require 'rbconfig'
require 'haml/root'
require 'haml/util/subset_map'
module Haml
# A module containing various useful functions.
@ -433,7 +432,7 @@ MSG
# Like {\#check\_encoding}, but also checks for a Ruby-style `-# coding:` comment
# at the beginning of the template and uses that encoding if it exists.
#
# The Sass encoding rules are simple.
# The Haml encoding rules are simple.
# If a `-# coding:` comment exists,
# we assume that that's the original encoding of the document.
# Otherwise, we use whatever encoding Ruby has.
@ -462,51 +461,6 @@ MSG
return check_encoding(str, &block)
end
# Like {\#check\_encoding}, but also checks for a `@charset` declaration
# at the beginning of the file and uses that encoding if it exists.
#
# The Sass encoding rules are simple.
# If a `@charset` declaration exists,
# we assume that that's the original encoding of the document.
# Otherwise, we use whatever encoding Ruby has.
# Then we convert that to UTF-8 to process internally.
# The UTF-8 end result is what's returned by this method.
#
# @param str [String] The string of which to check the encoding
# @yield [msg] A block in which an encoding error can be raised.
# Only yields if there is an encoding error
# @yieldparam msg [String] The error message to be raised
# @return [(String, Encoding)] The original string encoded as UTF-8,
# and the source encoding of the string (or `nil` under Ruby 1.8)
# @raise [Encoding::UndefinedConversionError] if the source encoding
# cannot be converted to UTF-8
# @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
def check_sass_encoding(str, &block)
return check_encoding(str, &block), nil if ruby1_8?
# We allow any printable ASCII characters but double quotes in the charset decl
bin = str.dup.force_encoding("BINARY")
encoding = Haml::Util::ENCODINGS_TO_CHECK.find do |enc|
bin =~ Haml::Util::CHARSET_REGEXPS[enc]
end
charset, bom = $1, $2
if charset
charset = charset.force_encoding(encoding).encode("UTF-8")
if endianness = encoding[/[BL]E$/]
begin
Encoding.find(charset + endianness)
charset << endianness
rescue ArgumentError # Encoding charset + endianness doesn't exist
end
end
str.force_encoding(charset)
elsif bom
str.force_encoding(encoding)
end
str = check_encoding(str, &block)
return str.encode("UTF-8"), str.encoding
end
unless ruby1_8?
# @private
def _enc(string, encoding)

View File

@ -5,15 +5,14 @@ times = (ARGV.first || 1000).to_i
if times == 0 # Invalid parameter
puts <<END
ruby #$0 [times=1000]
Benchmark Haml against various other templating languages and Sass
on its own.
Benchmark Haml against various other templating languages.
END
exit 1
end
require File.dirname(__FILE__) + '/../lib/haml'
require File.dirname(__FILE__) + '/linked_rails'
%w[sass rubygems erb erubis markaby active_support action_controller
%w[rubygems erb erubis markaby active_support action_controller
action_view action_pack haml/template rbench].each {|dep| require(dep)}
def view
@ -91,9 +90,3 @@ RBench.run(times) do
haml_ugly { render @base, 'haml/templates/action_view_ugly' }
end
end
RBench.run(times) do
sass_template = File.read("#{File.dirname(__FILE__)}/sass/templates/complex.sass")
report("Sass") { Sass::Engine.new(sass_template).render }
end

View File

@ -1101,18 +1101,18 @@ END
end
def test_css_filter
assert_equal(<<CSS, render(<<SASS))
assert_equal(<<HTML, render(<<HAML))
<style type='text/css'>
/*<![CDATA[*/
#foo {
bar: baz; }
/*]]>*/
</style>
CSS
HTML
:css
#foo {
bar: baz; }
SASS
HAML
end
def test_local_assigns_dont_modify_class

View File

@ -5,43 +5,12 @@ require 'test/unit'
require 'fileutils'
$:.unshift lib_dir unless $:.include?(lib_dir)
require 'haml'
require 'sass'
require 'haml/template'
Haml::Template.options[:ugly] = false
Haml::Template.options[:format] = :xhtml
Sass::RAILS_LOADED = true unless defined?(Sass::RAILS_LOADED)
module Sass::Script::Functions
module UserFunctions; end
include UserFunctions
def option(name)
Sass::Script::String.new(@options[name.value.to_sym].to_s)
end
end
class Test::Unit::TestCase
def munge_filename(opts = {})
return if opts.has_key?(:filename)
opts[:filename] = filename_for_test(opts[:syntax] || :sass)
end
def filename_for_test(syntax = :sass)
test_name = caller.
map {|c| Haml::Util.caller_info(c)[2]}.
compact.
map {|c| c.sub(/^(block|rescue) in /, '')}.
find {|c| c =~ /^test_/}
"#{test_name}_inline.#{syntax}"
end
def clean_up_sassc
path = File.dirname(__FILE__) + "/../.sass-cache"
FileUtils.rm_r(path) if File.exist?(path)
end
def assert_warning(message)
the_real_stderr, $stderr = $stderr, StringIO.new
yield

View File

@ -1,41 +0,0 @@
class InheritedHashHandler < YARD::Handlers::Ruby::Legacy::Base
handles /\Ainherited_hash(\s|\()/
def process
hash_name = tokval(statement.tokens[2])
name = statement.comments.first.strip
type = statement.comments[1].strip
o = register(MethodObject.new(namespace, hash_name, scope))
o.docstring = [
"Gets a #{name} from this {Environment} or one of its \\{#parent}s.",
"@param name [String] The name of the #{name}",
"@return [#{type}] The #{name} value",
]
o.signature = true
o.parameters = ["name"]
o = register(MethodObject.new(namespace, "set_#{hash_name}", scope))
o.docstring = [
"Sets a #{name} in this {Environment} or one of its \\{#parent}s.",
"If the #{name} is already defined in some environment,",
"that one is set; otherwise, a new one is created in this environment.",
"@param name [String] The name of the #{name}",
"@param value [#{type}] The value of the #{name}",
"@return [#{type}] `value`",
]
o.signature = true
o.parameters = ["name", "value"]
o = register(MethodObject.new(namespace, "set_local_#{hash_name}", scope))
o.docstring = [
"Sets a #{name} in this {Environment}.",
"Ignores any parent environments.",
"@param name [String] The name of the #{name}",
"@param value [#{type}] The value of the #{name}",
"@return [#{type}] `value`",
]
o.signature = true
o.parameters = ["name", "value"]
end
end