Skip to content
Browse files

Merge commit 'nex3/master' into plugin_v2

* commit 'nex3/master': (75 commits)
  Move a Haml changelog entry out of the Sass changelog.
  Bump VERSION to 3.0.18.
  [Haml] Fix version number checks between release/prerelease versions.
  [Haml] Make tests pass with Rails 3 rc 2.
  [Sass] Let SCSS @imports import multiple files.
  Add changelog entries for ::Rails.
  Check for Rails.env rather than Rails.root when deciding between Rails.env and RAILS_ENV.
  Reference the Rails module as ::Rails.
  Get rid of Haml::Exec::Generic.
  Get rid of --rails.
  Touch up the FAQ.
  [Haml] Fix a bug caused by the haml_tag fix.
  Set has_rdoc = false in the gemfile.
  [Sass] Convert haml_tag attribute keys to strings.
  [Sass] Add a --stop-on-error option.
  Rails 2 uses config.gem, not gem.
  Update the installation instructions in the README.
  [Haml] Document that :html5 is the default format in Rails 3.
  Document previous.
  Removed require rake by switching to Dir.[].
  ...

Conflicts:
	lib/sass/plugin.rb
	lib/sass/plugin/configuration.rb
  • Loading branch information...
2 parents 8e03bfc + ffd10a5 commit fe9b6f254542d546b37a90aa7d88863877c4f261 @chriseppstein chriseppstein committed
Showing with 898 additions and 405 deletions.
  1. +3 −2 README.md
  2. +1 −1 Rakefile
  3. +5 −4 doc-src/FAQ.md
  4. +66 −1 doc-src/HAML_CHANGELOG.md
  5. +8 −7 doc-src/HAML_REFERENCE.md
  6. +85 −3 doc-src/SASS_CHANGELOG.md
  7. +31 −9 doc-src/SASS_REFERENCE.md
  8. +31 −31 extra/haml-mode.el
  9. +10 −9 extra/sass-mode.el
  10. +5 −18 haml.gemspec
  11. +1 −1 lib/haml/engine.rb
  12. +81 −108 lib/haml/exec.rb
  13. +3 −3 lib/haml/helpers.rb
  14. +14 −10 lib/haml/helpers/action_view_mods.rb
  15. +17 −0 lib/haml/html.rb
  16. +18 −17 lib/haml/precompiler.rb
  17. +54 −4 lib/haml/util.rb
  18. +31 −17 lib/sass/engine.rb
  19. +7 −3 lib/sass/plugin.rb
  20. +2 −3 lib/sass/plugin/compiler.rb
  21. +15 −0 lib/sass/plugin/generic.rb
  22. +1 −1 lib/sass/script/color.rb
  23. +2 −2 lib/sass/script/lexer.rb
  24. +9 −0 lib/sass/scss/css_parser.rb
  25. +43 −14 lib/sass/scss/parser.rb
  26. +1 −1 lib/sass/scss/rx.rb
  27. +1 −3 lib/sass/scss/static_parser.rb
  28. +1 −1 lib/sass/tree/comment_node.rb
  29. +8 −4 lib/sass/tree/node.rb
  30. +1 −1 lib/sass/tree/prop_node.rb
  31. +88 −3 test/haml/engine_test.rb
  32. +29 −3 test/haml/helper_test.rb
  33. +16 −0 test/haml/html2haml/erb_tests.rb
  34. +0 −23 test/haml/results/helpers.xhtml
  35. +2 −2 test/haml/template_test.rb
  36. +0 −66 test/haml/templates/helpers.haml
  37. +23 −0 test/haml/util_test.rb
  38. +1 −1 test/sass/conversion_test.rb
  39. +35 −6 test/sass/engine_test.rb
  40. +16 −0 test/sass/results/scss_import.css
  41. +25 −13 test/sass/script_test.rb
  42. +15 −1 test/sass/scss/css_test.rb
  43. +63 −7 test/sass/scss/scss_test.rb
  44. +1 −1 test/sass/templates/basic.sass
  45. +2 −1 test/sass/templates/scss_import.scss
  46. +27 −0 test/test_helper.rb
View
5 README.md
@@ -29,8 +29,9 @@ For more information on these commands, check out
haml --help
sass --help
-To install Haml and Sass as a Rails plugin,
-just run `haml --rails path/to/rails/app`
+To install Haml and Sass 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;
View
2 Rakefile
@@ -97,7 +97,7 @@ task :release_elpa do
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
+ 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}))")
View
9 doc-src/FAQ.md
@@ -132,8 +132,9 @@ in Haml, you have to do
{#q-blank-page}
There are several reasons these things might be happening.
-First of all, make sure `vendor/plugins/haml` really exists
-and has an `init.rb` file in there.
+First of all, make sure that Haml really is installed;
+either you've loaded the gem (via `config.gem` in Rails 2.3 or in the Gemfile in Rails 3),
+or `vendor/plugins/haml` exists and contains files.
Then try restarting Mongrel or WEBrick or whatever you might be using.
Finally, if none of these work,
@@ -179,8 +180,8 @@ from your Sass functions.
## You still haven't answered my question!
-Sorry! Try looking at the Haml or Sass references,
-in the doucmentation for the haml and Sass modules, respectively.
+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,
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
67 doc-src/HAML_CHANGELOG.md
@@ -3,13 +3,78 @@
* Table of contents
{:toc}
-## 3.0.13 (Unreleased)
+## 3.2.0 (Unreleased)
+
+* Get rid of the `--rails` flag for the `haml` executable.
+ This flag hasn't been necessary since Rails 2.0.
+ Existing Rails 2.0 installations will continue to work.
+
+## 3.0.18
+
+[Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.18).
+
+* Don't require `rake` in the gemspec, for bundler compatibility under
+ JRuby. Thanks to [Gordon McCreight](http://www.gmccreight.com/blog).
+
+* Get rid of the annoying RDoc errors on install.
+
+* Disambiguate references to the `Rails` module when `haml-rails` is installed.
+
+* Fix a bug in `haml_tag` that would allow duplicate attributes to be added
+ and make `data-` attributes not work.
+
+* Compatibility with Rails 3 final.
+
+## 3.0.17
+
+[Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.17).
+
+* Understand that mingw counts as Windows.
+
+## 3.0.16
+
+[Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.16).
+
+* Fix an html2haml ERB-parsing bug where ERB blocks were occasionally
+ left without indentation in Haml.
+
+* Fix parsing of `if` and `case` statements whose values were assigned to variables.
+ This is still bad style, though.
+
+* Fix `form_for` and `form_tag` when they're passed a block that
+ returns a string in a helper.
+
+## 3.0.15
+
+[Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.15).
+
+There were no changes made to Haml between versions 3.0.14 and 3.0.15.
+
+## 3.0.14
+
+[Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.14).
+
+* Allow CSS-style classes and ids to contain colons.
+
+* Fix an obscure bug with if statements.
+
+### Rails 3 Support
+
+* Don't use the `#returning` method, which Rails 3 no longer provides.
+
+## 3.0.13
+
+[Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.13).
## Rails 3 Support
Support for Rails 3 versions prior to beta 4 has been removed.
Upgrade to Rails 3.0.0.beta4 if you haven't already.
+### Minor Improvements
+
+* Properly process frozen strings with encoding declarations.
+
## 3.0.12
[Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.12).
View
15 doc-src/HAML_REFERENCE.md
@@ -128,7 +128,8 @@ in `environment.rb` in Rails...
Available options are:
{#format-option} `:format`
-: Determines the output format. The default is `:xhtml`.
+: Determines the output format. Normally the default is `:xhtml`,
+ although under Rails 3 it's `:html5`, since that's the Rails 3's default format.
Other options are `:html4` and `:html5`, which are
identical to `:xhtml` except there are no self-closing tags,
the XML prolog is ignored and correct DOCTYPEs are generated.
@@ -360,10 +361,10 @@ For example:
could render as any of:
- <div class="column numeric sort ascending">Contents</div>
- <div class="column numeric">Contents</div>
- <div class="column sort descending">Contents</div>
- <div class="column">Contents</div>
+ <div class="numeric sort ascending">Contents</div>
+ <div class="numeric">Contents</div>
+ <div class="sort descending">Contents</div>
+ <div>Contents</div>
depending on whether `@item.type` is `"numeric"` or `nil`,
whether `@item == @sortcol`,
@@ -766,7 +767,7 @@ is compiled to:
</html>
You can also specify the specific doctype after the `!!!`
-When the [`:format`](#format-option) is set to `:xhtml` (the default),
+When the [`:format`](#format-option) is set to `:xhtml` (the default except in Rails 3),
the following doctypes are supported:
`!!!`
@@ -1279,7 +1280,7 @@ It's placed at the end of a line (after some whitespace)
and means that all following lines that end with `|`
will be evaluated as though they were on the same line.
**Note that even the last line in the multiline block
-should end wit `|`.**
+should end with `|`.**
For example:
%whoo
View
88 doc-src/SASS_CHANGELOG.md
@@ -3,7 +3,84 @@
* Table of contents
{:toc}
-## 3.0.13 (Unreleased)
+## 3.2.0 (Unreleased)
+
+* Get rid of the `--rails` flag for the `sass` executable.
+ This flag hasn't been necessary since Rails 2.0.
+ Existing Rails 2.0 installations will continue to work.
+
+## 3.0.18
+
+[Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.18).
+
+* Don't require `rake` in the gemspec, for bundler compatibility under
+ JRuby. Thanks to [Gordon McCreight](http://www.gmccreight.com/blog).
+
+* Add a command-line option `--stop-on-error` that causes Sass to exit
+ when a file fails to compile using `--watch` or `--update`.
+
+* Fix a bug in `haml_tag` that would allow duplicate attributes to be added
+ and make `data-` attributes not work.
+
+* Get rid of the annoying RDoc errors on install.
+
+* Disambiguate references to the `Rails` module when `haml-rails` is installed.
+
+* Allow `@import` in SCSS to import multiple files in the same `@import` rule.
+
+## 3.0.17
+
+[Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.17).
+
+* Disallow `#{}` interpolation in `@media` queries or unrecognized directives.
+ This was never allowed, but now it explicitly throws an error
+ rather than just producing invalid CSS.
+
+* Make `sass --watch` not throw an error when passed a single file or directory.
+
+* Understand that mingw counts as Windows.
+
+* Make `sass --update` return a non-0 exit code if one or more files being updated
+ contained an error.
+
+## 3.0.16
+
+[Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.16).
+
+* Fix a bug where certain sorts of comments would get improperly
+ rendered in the `:compact` style.
+
+* Always allow a trailing `*/` in loud comments in the indented syntax.
+
+* Fix a performance issue with SCSS parsing in rare cases.
+ Thanks to [Chris Eppstein](http://chriseppstein.github.com).
+
+* Use better heuristics for figuring out when someone might be using
+ the wrong syntax with `sass --watch`.
+
+## 3.0.15
+
+[Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.15).
+
+* Fix a bug where `sass --watch` and `sass --update` were completely broken.
+
+* Allow `@import`ed values to contain commas.
+
+## 3.0.14
+
+[Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.14).
+
+* Properly parse paths with drive letters on Windows (e.g. `C:\Foo\Bar.sass`)
+ in the Sass executable.
+
+* Compile Sass files in a deterministic order.
+
+* Fix a bug where comments after `@if` statements in SCSS
+ weren't getting passed through to the output document.
+
+## 3.0.13
+
+[Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.13).
## CSS `@import` Directives
@@ -38,10 +115,15 @@ is now parsed by Sass.
The Sass command-line executable can now require Ruby files
using the `--require` flag (or `-r` for short).
-## Rails 3 Support
+## Rails Support
+
+Make sure the default Rails options take precedence over the default non-Rails options.
+This makes `./script/server --daemon` work again.
+
+### Rails 3 Support
Support for Rails 3 versions prior to beta 4 has been removed.
-Upg rade to Rails 3.0.0.beta4 if you haven't already.
+Upgrade to Rails 3.0.0.beta4 if you haven't already.
## 3.0.12
View
40 doc-src/SASS_REFERENCE.md
@@ -759,7 +759,7 @@ For example:
$translucent-red: rgba(255, 0, 0, 0.5);
p {
- color: opacify($translucent-red, 80%);
+ color: opacify($translucent-red, 0.8);
background-color: transparentize($translucent-red, 50%);
}
@@ -847,12 +847,12 @@ SassScript defines some useful functions
that are called using the normal CSS function syntax:
p {
- color: hsl(0, 100%, 50%);
+ color: hsl(0, 100%, 0.5);
}
is compiled to:
- #main {
+ p {
color: #ff0000; }
See {Sass::Script::Functions} for a full listing of Sass functions,
@@ -912,8 +912,8 @@ For example:
is compiled to:
#main {
- content: First content;
- new-content: First time reference; }
+ content: "First content";
+ new-content: "First time reference"; }
## `@`-Rules and Directives {#directives}
@@ -939,9 +939,18 @@ Additional search directories may be specified
using the [`:load_paths`](#load_paths-option) option,
or the `--load-path` option on the command line.
-`@import` takes a filename with or without an extension.
-If the extension is `.css`, it will be treated as a plain CSS `@import` rule.
-If the extension is `.scss` or `.sass`, that file will be imported.
+`@import` takes a filename to import.
+By default, it looks for a Sass file to import directly,
+but there are a few circumstances under which it will compile to a CSS `@import` rule:
+
+* If the file's extension is `.css`.
+* If the filename begins with `http://`.
+* If the filename is a `url()`.
+* If the `@import` has any media queries.
+
+If none of the above conditions are met
+and the extension is `.scss` or `.sass`,
+then the named Sass or SCSS file will be imported.
If there is no extension,
Sass will try to find a file with that name and the `.scss` or `.sass` extension
and import it.
@@ -958,10 +967,22 @@ would both import the file `foo.scss`,
whereas
@import "foo.css";
+ @import "foo" screen;
+ @import "http://foo.com/bar";
+ @import url(foo);
-would simply compile to
+would all compile to
@import "foo.css";
+ @import "foo" screen;
+ @import "http://foo.com/bar";
+ @import url(foo);
+
+It's also possible to import multiple files in one `@import`. For example:
+
+ @import "rounded-corners", "text-shadow";
+
+would import both the `rounded-corners` and the `text-shadow` files.
#### Partials {#partials}
@@ -1550,6 +1571,7 @@ For example:
width: $width;
style: dashed;
}
+ }
p { @include sexy-border(blue); }
h1 { @include sexy-border(blue, 2in); }
View
62 extra/haml-mode.el
@@ -4,7 +4,7 @@
;; Author: Nathan Weizenbaum
;; URL: http://github.com/nex3/haml/tree/master
-;; Version: 3.0.10
+;; Version: 3.0.14
;; Created: 2007-03-08
;; By: Nathan Weizenbaum
;; Keywords: markup, language, html
@@ -57,13 +57,6 @@ re-indented along with the line itself."
:type 'boolean
:group 'haml)
-(defface haml-tab-face
- '((((class color)) (:background "hotpink"))
- (t (:reverse-video t)))
- "Face to use for highlighting tabs in Haml files."
- :group 'faces
- :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
@@ -73,19 +66,19 @@ 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
- "^ *\\(?:[%\\.#][a-z0-9_:\\-]*\\)+\\(?:(.*)\\|{.*}\\|\\[.*\\]\\)*"
+ "^[ \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]*$")
- "^ *[&!]?[-=~].*do[ \t]*\\(|.*|[ \t]*\\)?$"
- ,(concat "^ *[&!]?[-=~][ \t]*\\("
+ "^[ \t]*[&!]?[-=~].*do[ \t]*\\(|.*|[ \t]*\\)?$"
+ ,(concat "^[ \t]*[&!]?[-=~][ \t]*\\("
(regexp-opt '("if" "unless" "while" "until" "else"
"begin" "elsif" "rescue" "ensure" "when"))
"\\)")
- "^ */\\(\\[.*\\]\\)?[ \t]*$"
- "^ *-#"
- "^ *:")
+ "^[ \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.")
@@ -95,7 +88,7 @@ be matched by a regexp in this list.")
(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 "^\\( *\\)" re "\\(\n\\(?:\\(?:\\1 .*\\| *\\)\n\\)*\\(?:\\1 .*\\| *\\)?\\)?"))
+ (concat "^\\([ \t]*\\)" re "\\(\n\\(?:\\(?:\\1 .*\\| *\\)\n\\)*\\(?:\\1 .*\\| *\\)?\\)?"))
(defconst haml-font-lock-keywords
`((,(haml-nested-regexp "\\(?:-#\\|/\\).*") 0 font-lock-comment-face)
@@ -108,12 +101,11 @@ The line containing RE is matched, as well as all lines indented beneath it."
(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)
- ("^ *\\(\t\\)" 1 'haml-tab-face)
("^!!!.*" 0 font-lock-constant-face)
("| *$" 0 font-lock-string-face)))
-(defconst haml-filter-re "^ *:\\w+")
-(defconst haml-comment-re "^ *\\(?:-\\#\\|/\\)")
+(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.
@@ -208,7 +200,7 @@ This requires that `markdown-mode' be available."
(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 "^ *\\(-\\|[&!]?[=~]\\) \\(.*\\)$" limit t)
+ (when (re-search-forward "^[ \t]*\\(-\\|[&!]?[=~]\\) \\(.*\\)$" limit t)
(haml-fontify-region-as-ruby (match-beginning 2) (match-end 2))))
(defun haml-highlight-ruby-tag (limit)
@@ -223,7 +215,7 @@ For example, this will highlight all of the following:
%p[@bar]
%p= 'baz'
%p{:foo => 'bar'}[@bar]= 'baz'"
- (when (re-search-forward "^ *[%.#]" limit t)
+ (when (re-search-forward "^[ \t]*[%.#]" limit t)
(forward-char -1)
;; Highlight tag, classes, and ids
@@ -407,7 +399,6 @@ With ARG, do it that many times."
(set (make-local-variable 'indent-region-function) 'haml-indent-region)
(set (make-local-variable 'parse-sexp-lookup-properties) t)
(setq comment-start "-#")
- (setq indent-tabs-mode nil)
(setq font-lock-defaults '((haml-font-lock-keywords) t t)))
;; Useful functions
@@ -620,18 +611,18 @@ 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 " *")
+ (haml-move "[ \t]*")
(unless (haml-move "[a-z0-9_:\\-]+")
- (return-from haml-parse-new-attr-hash (haml-move " *$")))
+ (return-from haml-parse-new-attr-hash (haml-move "[ \t]*$")))
(funcall fn 'name (match-beginning 0) (match-end 0))
- (haml-move " *")
+ (haml-move "[ \t]*")
(when (haml-move "=")
- (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 " *")))
+ (haml-move "[ \t]*")))
nil))
(defun haml-compute-indentation ()
@@ -704,10 +695,19 @@ back-dent the line by `haml-indent-offset' spaces. On reaching column
"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)))
+ (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 (concat "^" (make-string ci ?\s)) (mark) t)
- (replace-match (make-string (max 0 (+ ci n)) ?\s))))))
+ (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.
@@ -730,8 +730,8 @@ the current line."
(haml-mark-sexp-but-not-next-line)
(set-mark (save-excursion (end-of-line) (point))))
(haml-reindent-region-by (* (- arg) haml-indent-offset))
- (back-to-indentation)
- (pop-mark)))))
+ (pop-mark)))
+ (back-to-indentation)))
(defun haml-kill-line-and-indent ()
"Kill the current line, and re-indent all lines nested beneath it."
View
19 extra/sass-mode.el
@@ -4,11 +4,11 @@
;; Author: Nathan Weizenbaum
;; URL: http://github.com/nex3/haml/tree/master
-;; Version: 3.0.10
+;; Version: 3.0.14
;; Created: 2007-03-15
;; By: Nathan Weizenbaum
;; Keywords: markup, language, css
-;; Package-Requires: ((haml-mode "3.0.10"))
+;; Package-Requires: ((haml-mode "3.0.14"))
;;; Commentary:
@@ -43,11 +43,12 @@
:group 'sass)
(defvar sass-non-block-openers
- '("^ *:[^ \t]+[ \t]+[^ \t]"
- "^ *[^ \t:]+[ \t]*[=:][ \t]*[^ \t]")
- "A list of regexps that match lines of Sass that don't open blocks.
-That is, a Sass line that can't have text nested beneath it
-should be matched by a regexp in this list.")
+ '("^.*,$" ;; Continued selectors
+ "^ *@\\(extend\\|debug\\|warn\\|include\\|import\\)" ;; Single-line mixins
+ "^ *[$!]" ;; Variables
+ )
+ "A list of regexps that match lines of Sass that couldn't have
+text nested beneath them.")
;; Font lock
@@ -195,8 +196,8 @@ LIMIT is the limit of the search."
(defun sass-indent-p ()
"Return non-nil if the current line can have lines nested beneath it."
(loop for opener in sass-non-block-openers
- unless (looking-at opener) return t
- return nil))
+ if (looking-at opener) return nil
+ finally return t))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.sass$" . sass-mode))
View
23 haml.gemspec
@@ -1,5 +1,4 @@
require 'rubygems'
-require 'rake'
# Note that Haml's gem-compilation process requires access to the filesystem.
# This means that it cannot be automatically run by e.g. GitHub's gem system.
@@ -25,24 +24,12 @@ HAML_GEMSPEC = Gem::Specification.new do |spec|
spec.add_development_dependency 'yard', '>= 0.5.3'
spec.add_development_dependency 'maruku', '>= 0.5.9'
- readmes = FileList.new('*') do |list|
- list.exclude(/(^|[^.a-z])[a-z]+/)
- list.exclude('TODO')
- list.include('REVISION') if File.exist?('REVISION')
- end.to_a
+ readmes = Dir['*'].reject{ |x| x =~ /(^|[^.a-z])[a-z]+/ || x == "TODO" }
spec.executables = ['haml', 'html2haml', 'sass', 'css2sass', 'sass-convert']
- spec.files = FileList['rails/init.rb', 'lib/**/*', 'vendor/**/*',
+ spec.files = Dir['rails/init.rb', 'lib/**/*', 'vendor/**/*',
'bin/*', 'test/**/*', 'extra/**/*', 'Rakefile', 'init.rb',
- '.yardopts'].to_a + readmes
+ '.yardopts'] + readmes
spec.homepage = 'http://haml-lang.com/'
- spec.has_rdoc = true
- spec.extra_rdoc_files = readmes
- spec.rdoc_options += [
- '--title', 'Haml',
- '--main', 'README.rdoc',
- '--exclude', 'lib/haml/buffer.rb',
- '--line-numbers',
- '--inline-source'
- ]
- spec.test_files = FileList['test/**/*_test.rb'].to_a
+ spec.has_rdoc = false
+ spec.test_files = Dir['test/**/*_test.rb']
end
View
2 lib/haml/engine.rb
@@ -99,7 +99,7 @@ def initialize(template, options = {})
@index = 0
unless [:xhtml, :html4, :html5].include?(@options[:format])
- raise Haml::Error, "Invalid format #{@options[:format].inspect}"
+ raise Haml::Error, "Invalid output format #{@options[:format].inspect}"
end
if @options[:encoding] && @options[:encoding].is_a?(Encoding)
View
189 lib/haml/exec.rb
@@ -1,6 +1,5 @@
require 'optparse'
require 'fileutils'
-require 'rbconfig'
module Haml
# This module handles the various Haml executables (`haml`, `sass`, `sass-convert`, etc).
@@ -80,7 +79,7 @@ def set_opts(opts)
@options[:trace] = true
end
- if RbConfig::CONFIG['host_os'] =~ /mswin|windows/i
+ if ::Haml::Util.windows?
opts.on('--unix-newlines', 'Use Unix-style newlines in written files.') do
@options[:unix_newlines] = true
end
@@ -162,125 +161,40 @@ def handle_load_error(err)
raise err if @options[:trace] || dep.nil? || dep.empty?
$stderr.puts <<MESSAGE
Required dependency #{dep} not found!
- Run "gem install #{dep}" to get it.
+ Run "gem install #{dep}" to get it.
Use --trace for backtrace.
MESSAGE
exit 1
end
end
- # An abstrac class that encapsulates the code
- # specific to the `haml` and `sass` executables.
- class HamlSass < Generic
+ # The `sass` executable.
+ class Sass < Generic
# @param args [Array<String>] The command-line arguments
def initialize(args)
super
- @options[:for_engine] = {}
+ @options[:for_engine] = {
+ :load_paths => ['.'] + (ENV['SASSPATH'] || '').split(File::PATH_SEPARATOR)
+ }
end
protected
- # Tells optparse how to parse the arguments
- # available for the `haml` and `sass` executables.
- #
- # This is meant to be overridden by subclasses
- # so they can add their own options.
+ # Tells optparse how to parse the arguments.
#
# @param opts [OptionParser]
def set_opts(opts)
+ super
+
opts.banner = <<END
-Usage: #{@name.downcase} [options] [INPUT] [OUTPUT]
+Usage: sass [options] [INPUT] [OUTPUT]
Description:
- Uses the #{@name} engine to parse the specified template
- and outputs the result to the specified file.
+ Converts SCSS or Sass files to CSS.
Options:
END
- opts.on('--rails RAILS_DIR', "Install Haml and Sass from the Gem to a Rails project") do |dir|
- original_dir = dir
-
- env = File.join(dir, "config", "environment.rb")
- if File.exists?(File.join(dir, "Gemfile"))
- puts("haml --rails isn't needed for Rails 3 or greater.",
- "Add 'gem \"haml\"' to your Gemfile instead.", "",
- "haml --rails will no longer work in the next version of #{@name}.", "")
- elsif File.exists?(env) && File.open(env) {|env| env.grep(/config\.gem/)}
- puts("haml --rails isn't needed for Rails 2.1 or greater.",
- "Add 'gem \"haml\"' to config/environment.rb instead.", "",
- "haml --rails will no longer work in the next version of #{@name}.", "")
- end
-
- dir = File.join(dir, 'vendor', 'plugins')
-
- unless File.exists?(dir)
- puts "Directory #{dir} doesn't exist"
- exit 1
- end
-
- dir = File.join(dir, 'haml')
-
- if File.exists?(dir)
- print "Directory #{dir} already exists, overwrite [y/N]? "
- exit 2 if gets !~ /y/i
- FileUtils.rm_rf(dir)
- end
-
- begin
- Dir.mkdir(dir)
- rescue SystemCallError
- puts "Cannot create #{dir}"
- exit 1
- end
-
- File.open(File.join(dir, 'init.rb'), 'w') do |file|
- file << File.read(File.dirname(__FILE__) + "/../../init.rb")
- end
-
- puts "Haml plugin added to #{original_dir}"
- exit
- end
-
- opts.on('-c', '--check', "Just check syntax, don't evaluate.") do
- require 'stringio'
- @options[:check_syntax] = true
- @options[:output] = StringIO.new
- end
-
- super
- end
-
- # Processes the options set by the command-line arguments.
- # In particular, sets `@options[:for_engine][:filename]` to the input filename
- # and requires the appropriate file.
- #
- # This is meant to be overridden by subclasses
- # so they can run their respective programs.
- def process_result
- super
- @options[:for_engine][:filename] = @options[:filename] if @options[:filename]
- require File.dirname(__FILE__) + "/../#{@name.downcase}"
- end
- end
-
- # The `sass` executable.
- class Sass < HamlSass
- # @param args [Array<String>] The command-line arguments
- def initialize(args)
- super
- @name = "Sass"
- @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.on('--scss',
'Use the CSS-superset SCSS syntax.') do
@options[:for_engine][:syntax] = :scss
@@ -295,6 +209,15 @@ def set_opts(opts)
'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
@@ -337,10 +260,12 @@ def set_opts(opts)
# 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 && @args.first.include?(':')
+ @args.first && colon_path?(@args.first)
if @args.size == 1
- @args = @args.first.split(':', 2)
+ @args = split_colon_path(@args.first)
else
@options[:update] = true
end
@@ -349,6 +274,7 @@ def process_result
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]
@@ -378,18 +304,22 @@ def process_result
private
def interactive
- require 'sass'
require 'sass/repl'
::Sass::Repl.new(@options).run
end
def watch_or_update
- require 'sass'
require 'sass/plugin'
::Sass::Plugin.options.merge! @options[:for_engine]
::Sass::Plugin.options[:unix_newlines] = @options[:unix_newlines]
- if @args[1] && !@args[0].include?(':')
+ 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])
@@ -399,11 +329,11 @@ def watch_or_update
end
raise <<MSG if err
File #{@args[1]} #{err}.
- Did you mean: sass #{flag} #{@args[0]}:#{@args[1]}
+ Did you mean: sass #{flag} #{@args[0]}:#{@args[1]}
MSG
end
- dirs, files = @args.map {|name| name.split(':', 2)}.
+ 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]}
@@ -417,15 +347,18 @@ def watch_or_update
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)
+ 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
@@ -437,14 +370,38 @@ def watch_or_update
::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 < HamlSass
+ class Haml < Generic
# @param args [Array<String>] The command-line arguments
def initialize(args)
super
- @name = "Haml"
+ @options[:for_engine] = {}
@options[:requires] = []
@options[:load_paths] = []
end
@@ -455,6 +412,21 @@ def initialize(args)
def set_opts(opts)
super
+ opts.banner = <<END
+Usage: haml [options] [INPUT] [OUTPUT]
+
+Description:
+ Converts Haml files to HTML.
+
+Options:
+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 indented (default) or ugly.') do |name|
@options[:for_engine][:ugly] = true if name.to_sym == :ugly
@@ -500,6 +472,7 @@ def set_opts(opts)
# and runs the Haml compiler appropriately.
def process_result
super
+ @options[:for_engine][:filename] = @options[:filename]
input = @options[:input]
output = @options[:output]
View
6 lib/haml/helpers.rb
@@ -442,7 +442,8 @@ def haml_tag(name, *rest, &block)
text = rest.shift.to_s unless [Symbol, Hash, NilClass].any? {|t| rest.first.is_a? t}
flags = []
flags << rest.shift while rest.first.is_a? Symbol
- name, attrs = merge_name_and_attributes(name.to_s, rest.shift || {})
+ attrs = Haml::Util.map_keys(rest.shift || {}) {|key| key.to_s}
+ name, attrs = merge_name_and_attributes(name.to_s, attrs)
attributes = Haml::Precompiler.build_attributes(haml_buffer.html?,
haml_buffer.options[:attr_wrapper],
@@ -551,8 +552,7 @@ def merge_name_and_attributes(name, attributes_hash = {})
return name, attributes_hash unless name =~ /^(.+?)?([\.#].*)$/
return $1 || "div", Buffer.merge_attrs(
- Precompiler.parse_class_and_id($2),
- Haml::Util.map_keys(attributes_hash) {|key| key.to_s})
+ Precompiler.parse_class_and_id($2), attributes_hash)
end
# Runs a block of code with the given buffer as the currently active buffer.
View
24 lib/haml/helpers/action_view_mods.rb
@@ -107,9 +107,9 @@ def content_tag_with_haml(name, *args, &block)
return content_tag_without_haml(name, *args) {preserve(&block)}
end
- returning content_tag_without_haml(name, *args, &block) do |content|
- return Haml::Helpers.preserve(content) if preserve && content
- end
+ content = content_tag_without_haml(name, *args, &block)
+ content = Haml::Helpers.preserve(content) if preserve && content
+ content
end
alias_method :content_tag_without_haml, :content_tag
@@ -140,7 +140,8 @@ def content_tag(*args)
module FormTagHelper
def form_tag_with_haml(url_for_options = {}, options = {}, *parameters_for_url, &proc)
if is_haml?
- if block_given?
+ wrap_block = block_given? && block_is_haml?(proc)
+ if wrap_block
oldproc = proc
proc = haml_bind_proc do |*args|
concat "\n"
@@ -148,7 +149,7 @@ def form_tag_with_haml(url_for_options = {}, options = {}, *parameters_for_url,
end
end
res = form_tag_without_haml(url_for_options, options, *parameters_for_url, &proc) + "\n"
- res << "\n" if block_given?
+ res << "\n" if wrap_block
res
else
form_tag_without_haml(url_for_options, options, *parameters_for_url, &proc)
@@ -160,12 +161,13 @@ def form_tag_with_haml(url_for_options = {}, options = {}, *parameters_for_url,
module FormHelper
def form_for_with_haml(object_name, *args, &proc)
- if block_given? && is_haml?
+ wrap_block = block_given? && is_haml? && block_is_haml?(proc)
+ if wrap_block
oldproc = proc
proc = proc {|*args| with_tabs(1) {oldproc.call(*args)}}
end
res = form_for_without_haml(object_name, *args, &proc)
- res << "\n" if block_given? && is_haml?
+ res << "\n" if wrap_block
res
end
alias_method :form_for_without_haml, :form_for
@@ -191,7 +193,8 @@ def fragment_for_with_haml(*args, &block)
module FormTagHelper
def form_tag_with_haml(url_for_options = {}, options = {}, *parameters_for_url, &proc)
if is_haml?
- if block_given?
+ wrap_block = block_given? && block_is_haml?(proc)
+ if wrap_block
oldproc = proc
proc = haml_bind_proc do |*args|
concat "\n"
@@ -218,7 +221,8 @@ def form_tag_with_haml(url_for_options = {}, options = {}, *parameters_for_url,
module FormHelper
def form_for_with_haml(object_name, *args, &proc)
- if block_given? && is_haml?
+ wrap_block = block_given? && is_haml? && block_is_haml?(proc)
+ if wrap_block
oldproc = proc
proc = haml_bind_proc do |*args|
tab_up
@@ -229,7 +233,7 @@ def form_for_with_haml(object_name, *args, &proc)
concat haml_indent
end
form_for_without_haml(object_name, *args, &proc)
- concat "\n" if block_given? && is_haml?
+ concat "\n" if wrap_block
Haml::Helpers::ErrorReturn.new("form_for") if is_haml?
end
alias_method :form_for_without_haml, :form_for
View
17 lib/haml/html.rb
@@ -100,6 +100,23 @@ class BaseEle
require 'hpricot'
+# @private
+HAML_TAGS = %w[haml:block haml:loud haml:silent]
+
+Hpricot::ElementContent.keys.each do |k|
+ HAML_TAGS.each do |el|
+ val = Hpricot::ElementContent[k]
+ val[el.hash] = true if val.is_a?(Hash)
+ end
+end
+
+HAML_TAGS.each do |t|
+ Hpricot::ElementContent[t] = {}
+ Hpricot::ElementContent.keys.each do |key|
+ Hpricot::ElementContent[t][key.hash] = true
+ end
+end
+
module Haml
# Converts HTML documents into Haml templates.
# Depends on [Hpricot](http://github.com/whymirror/hpricot) for HTML parsing.
View
35 lib/haml/precompiler.rb
@@ -242,29 +242,28 @@ def process_line(text, index)
# Handle stuff like - end.join("|")
@to_close_stack.last << false if text =~ /^-\s*end\b/ && !block_opened?
- case_stmt = text =~ /^-\s*case\b/
keyword = mid_block_keyword?(text)
block = block_opened? && !keyword
# It's important to preserve tabulation modification for keywords
# that involve choosing between posible blocks of code.
if %w[else elsif when].include?(keyword)
- # @to_close_stack may not have a :script on top
- # when the preceding "- if" has nothing nested
- if @to_close_stack.last && @to_close_stack.last.first == :script
+ # Whether a script block has already been opened immediately above this line
+ was_opened = @to_close_stack.last && @to_close_stack.last.first == :script
+ if was_opened
@dont_indent_next_line, @dont_tab_up_next_text = @to_close_stack.last[1..2]
- else
- push_and_tabulate([:script, @dont_indent_next_line, @dont_tab_up_next_text])
end
# when is unusual in that either it will be indented twice,
- # or the case won't have created its own indentation
- if keyword == "when"
- push_and_tabulate([:script, @dont_indent_next_line, @dont_tab_up_next_text, false])
+ # or the case won't have created its own indentation.
+ # Also, if no block has been opened yet, we need to make sure we add an end
+ # once we de-indent.
+ if !was_opened || keyword == "when"
+ push_and_tabulate([
+ :script, @dont_indent_next_line, @dont_tab_up_next_text,
+ !was_opened])
end
- elsif block || case_stmt
- push_and_tabulate([:script, @dont_indent_next_line, @dont_tab_up_next_text])
- elsif block && case_stmt
+ elsif block || text =~ /^-\s*(case|if)\b/
push_and_tabulate([:script, @dont_indent_next_line, @dont_tab_up_next_text])
end
when FILTER; start_filtered(text[1..-1].downcase)
@@ -491,7 +490,7 @@ def close_nil(*args)
# that can then be merged with another attributes hash.
def self.parse_class_and_id(list)
attributes = {}
- list.scan(/([#.])([-_a-zA-Z0-9]+)/) do |type, property|
+ list.scan(/([#.])([-:_a-zA-Z0-9]+)/) do |type, property|
case type
when '.'
if attributes['class']
@@ -535,8 +534,8 @@ def self.build_attributes(is_html, attr_wrapper, attributes = {})
result = attributes.collect do |attr, value|
next if value.nil?
- value = filter_and_join(value, ' ') if attr == :class
- value = filter_and_join(value, '_') if attr == :id
+ value = filter_and_join(value, ' ') if attr == 'class'
+ value = filter_and_join(value, '_') if attr == 'id'
if value == true
next " #{attr}" if is_html
@@ -562,8 +561,10 @@ def self.build_attributes(is_html, attr_wrapper, attributes = {})
end
def self.filter_and_join(value, separator)
+ return "" if value == ""
value = [value] unless value.is_a?(Array)
- return value.flatten.collect {|item| item ? item.to_s : nil}.compact.join(separator)
+ value = value.flatten.collect {|item| item ? item.to_s : nil}.compact.join(separator)
+ return !value.empty? && value
end
def prerender_tag(name, self_close, attributes)
@@ -573,7 +574,7 @@ def prerender_tag(name, self_close, attributes)
# Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
def parse_tag(line)
- raise SyntaxError.new("Invalid tag: \"#{line}\".") unless match = line.scan(/%([-:\w]+)([-\w\.\#]*)(.*)/)[0]
+ raise SyntaxError.new("Invalid tag: \"#{line}\".") unless match = line.scan(/%([-:\w]+)([-:\w\.\#]*)(.*)/)[0]
tag_name, attributes, rest = match
new_attributes_hash = old_attributes_hash = last_line = object_ref = nil
attributes_hashes = []
View
58 lib/haml/util.rb
@@ -3,6 +3,8 @@
require 'enumerator'
require 'stringio'
require 'strscan'
+require 'rbconfig'
+
require 'haml/root'
require 'haml/util/subset_map'
@@ -229,6 +231,44 @@ def caller_info(entry = caller[1])
info
end
+ # Returns whether one version string represents a more recent version than another.
+ #
+ # @param v1 [String] A version string.
+ # @param v2 [String] Another version string.
+ # @return [Boolean]
+ def version_gt(v1, v2)
+ # Construct an array to make sure the shorter version is padded with nil
+ Array.new([v1.length, v2.length].max).zip(v1.split("."), v2.split(".")) do |_, p1, p2|
+ p1 ||= "0"
+ p2 ||= "0"
+ release1 = p1 =~ /^[0-9]+$/
+ release2 = p2 =~ /^[0-9]+$/
+ if release1 && release2
+ # Integer comparison if both are full releases
+ p1, p2 = p1.to_i, p2.to_i
+ next if p1 == p2
+ return p1 > p2
+ elsif !release1 && !release2
+ # String comparison if both are prereleases
+ next if p1 == p2
+ return p1 > p2
+ else
+ # If only one is a release, that one is newer
+ return release1
+ end
+ end
+ end
+
+ # Returns whether one version string represents the same or a more
+ # recent version than another.
+ #
+ # @param v1 [String] A version string.
+ # @param v2 [String] Another version string.
+ # @return [Boolean]
+ def version_geq(v1, v2)
+ version_gt(v1, v2) || !version_gt(v2, v1)
+ end
+
# Silence all output to STDERR within a block.
#
# @yield A block in which no output will be printed to STDERR
@@ -267,8 +307,8 @@ def haml_warn(msg)
#
# @return [String, nil]
def rails_root
- if defined?(Rails.root)
- return Rails.root.to_s if Rails.root
+ if defined?(::Rails.root)
+ return ::Rails.root.to_s if ::Rails.root
raise "ERROR: Rails.root is nil!"
end
return RAILS_ROOT.to_s if defined?(RAILS_ROOT)
@@ -281,7 +321,7 @@ def rails_root
#
# @return [String, nil]
def rails_env
- return Rails.env.to_s if defined?(Rails.root)
+ return ::Rails.env.to_s if defined?(::Rails.env)
return RAILS_ENV.to_s if defined?(RAILS_ENV)
return nil
end
@@ -306,7 +346,7 @@ def ap_geq?(version)
return false unless defined?(ActionPack) && defined?(ActionPack::VERSION) &&
defined?(ActionPack::VERSION::STRING)
- ActionPack::VERSION::STRING >= version
+ version_geq(ActionPack::VERSION::STRING, version)
end
# Returns an ActionView::Template* class.
@@ -366,6 +406,15 @@ def rails_safe_buffer_class
return ActionView::SafeBuffer
end
+ ## Cross-OS Compatibility
+
+ # Whether or not this is running on Windows.
+ #
+ # @return [Boolean]
+ def windows?
+ RbConfig::CONFIG['host_os'] =~ /mswin|windows|mingw/i
+ end
+
## Cross-Ruby-Version Compatibility
# Whether or not this is running under Ruby 1.8 or lower.
@@ -441,6 +490,7 @@ def check_encoding(str)
# @raise [ArgumentError] if the document declares an unknown encoding
def check_haml_encoding(str, &block)
return check_encoding(str, &block) if ruby1_8?
+ str = str.dup if str.frozen?
bom, encoding = parse_haml_magic_comment(str)
if encoding; str.force_encoding(encoding)
View
48 lib/sass/engine.rb
@@ -594,25 +594,38 @@ def parse_import(line, value)
raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
:line => @line + 1) unless line.children.empty?
- if (match = value.match(Sass::SCSS::RX::STRING) || value.match(Sass::SCSS::RX::URI)) &&
- match.offset(0).first == 0 && !match.post_match.strip.empty? &&
- match.post_match.strip[0] != ?,
- # @import "filename" media-type
- return Tree::DirectiveNode.new("@import #{value}")
- end
+ scanner = StringScanner.new(value)
+ values = []
- value.split(/,\s*/).map do |f|
- if f =~ Sass::SCSS::RX::URI
- # All url()s are literal CSS @imports
- next Tree::DirectiveNode.new("@import #{f}")
- elsif f =~ Sass::SCSS::RX::STRING
- f = $1 || $2
+ loop do
+ unless node = parse_import_arg(scanner)
+ raise SyntaxError.new("Invalid @import: expected file to import, was #{scanner.rest.inspect}",
+ :line => @line)
end
+ values << node
+ break unless scanner.scan(/,\s*/)
+ end
+
+ return values
+ end
- # http:// URLs are always literal CSS imports
- next Tree::DirectiveNode.new("@import url(#{f})") if f =~ /^http:\/\//
+ def parse_import_arg(scanner)
+ return if scanner.eos?
+ unless (str = scanner.scan(Sass::SCSS::RX::STRING)) ||
+ (uri = scanner.scan(Sass::SCSS::RX::URI))
+ return Tree::ImportNode.new(scanner.scan(/[^,]+/))
+ end
- Tree::ImportNode.new(f)
+ val = scanner[1] || scanner[2]
+ scanner.scan(/\s*/)
+ if media = scanner.scan(/[^,].*/)
+ Tree::DirectiveNode.new("@import #{str || uri} #{media}")
+ elsif uri
+ Tree::DirectiveNode.new("@import #{uri}")
+ elsif val =~ /^http:\/\//
+ Tree::DirectiveNode.new("@import url(#{val})")
+ else
+ Tree::ImportNode.new(val)
end
end
@@ -656,13 +669,14 @@ def format_comment_text(text, silent)
end
return silent ? "//" : "/* */" if content.empty?
+ content.last.gsub!(%r{ ?\*/ *$}, '')
content.map! {|l| l.gsub!(/^\*( ?)/, '\1') || (l.empty? ? "" : " ") + l}
content.first.gsub!(/^ /, '') unless removed_first
- content.last.gsub!(%r{ ?\*/ *$}, '')
if silent
"//" + content.join("\n//")
else
- "/*" + content.join("\n *") + " */"
+ # The #gsub fixes the case of a trailing */
+ "/*" + content.join("\n *").gsub(/ \*\Z/, '') + " */"
end
end
View
10 lib/sass/plugin.rb
@@ -1,5 +1,4 @@
require 'fileutils'
-require 'rbconfig'
require 'sass'
require 'sass/plugin/compiler'
@@ -118,5 +117,10 @@ def method_missing(method, *args, &block)
end
end
-require 'sass/plugin/rails' if defined?(ActionController)
-require 'sass/plugin/merb' if defined?(Merb::Plugins)
+if defined?(ActionController)
+ require 'sass/plugin/rails'
+elsif defined?(Merb::Plugins)
+ require 'sass/plugin/merb'
+else
+ require 'sass/plugin/generic'
+end
View
5 lib/sass/plugin/compiler.rb
@@ -1,5 +1,4 @@
require 'fileutils'
-require 'rbconfig'
require 'sass'
# XXX CE: is this still necessary now that we have the compiler class?
@@ -170,7 +169,7 @@ def update_stylesheets(individual_files = [])
template_location_array.each do |template_location, css_location|
- Dir.glob(File.join(template_location, "**", "*.s[ca]ss")).each do |file|
+ Dir.glob(File.join(template_location, "**", "*.s[ca]ss")).sort.each do |file|
# Get the relative path to the file
name = file.sub(template_location.sub(/\/*$/, '/'), "")
css = css_filename(name, css_location)
@@ -313,7 +312,7 @@ def update_stylesheet(filename, css)
# Finally, write the file
flag = 'w'
- flag = 'wb' if RbConfig::CONFIG['host_os'] =~ /mswin|windows/i && options[:unix_newlines]
+ flag = 'wb' if Haml::Util.windows? && options[:unix_newlines]
File.open(css, flag) {|file| file.print(result)}
end
View
15 lib/sass/plugin/generic.rb
@@ -0,0 +1,15 @@
+# The reason some options are declared here rather than in sass/plugin/configuration.rb
+# is that otherwise they'd clobber the Rails-specific options.
+# Since Rails' options are lazy-loaded in Rails 3,
+# they're reverse-merged with the default options
+# so that user configuration is preserved.
+# This means that defaults that differ from Rails'
+# must be declared here.
+
+unless defined?(Sass::GENERIC_LOADED)
+ Sass::GENERIC_LOADED = true
+
+ Sass::Plugin.options.merge!(:css_location => './public/stylesheets',
+ :always_update => false,
+ :always_check => true)
+end
View
2 lib/sass/script/color.rb
@@ -114,7 +114,7 @@ def initialize(attrs, allow_both_rgb_and_hsl = false)
end
unless (0..1).include?(@attrs[:alpha])
- raise Sass::SyntaxError.new("Alpha channel must between 0 and 1")
+ raise Sass::SyntaxError.new("Alpha channel must be between 0 and 1")
end
end
View
4 lib/sass/script/lexer.rb
@@ -92,7 +92,7 @@ class Lexer
:number => /(-)?(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
:color => HEXCOLOR,
:bool => /(true|false)\b/,
- :ident_op => %r{(#{Regexp.union(*IDENT_OP_NAMES.map{|s| Regexp.new(Regexp.escape(s) + '(?:\b|$)')})})},
+ :ident_op => %r{(#{Regexp.union(*IDENT_OP_NAMES.map{|s| Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|$)")})})},
:op => %r{(#{Regexp.union(*OP_NAMES)})},
}
@@ -278,7 +278,7 @@ def number
def color
return unless s = scan(REGULAR_EXPRESSIONS[:color])
- raise Sass::SyntaxError.new(<<MESSAGE) unless s.size == 4 || s.size == 7
+ raise Sass::SyntaxError.new(<<MESSAGE.rstrip) unless s.size == 4 || s.size == 7
Colors must have either three or six digits: '#{s}'
MESSAGE
value = s.scan(/^#(..?)(..?)(..?)$/).first.
View
9 lib/sass/scss/css_parser.rb
@@ -7,6 +7,15 @@ module SCSS
# parent references, nested selectors, and so forth.
# It does support all the same CSS hacks as the SCSS parser, though.
class CssParser < StaticParser
+ # Parse a selector, and return its value as a string.
+ #
+ # @return [String, nil] The parsed selector, or nil if no selector was parsed
+ # @raise [Sass::SyntaxError] if there's a syntax error in the selector
+ def parse_selector_string
+ init_scanner!
+ str {return unless selector}
+ end
+
private
def parent_selector; nil; end
View
57 lib/sass/scss/parser.rb
@@ -109,11 +109,10 @@ def directive
return dir
end
- val = str do
- # Most at-rules take expressions (e.g. @import),
- # but some (e.g. @page) take selector-like arguments
- expr || selector
- end
+ # Most at-rules take expressions (e.g. @import),
+ # but some (e.g. @page) take selector-like arguments
+ val = str {break unless expr}
+ val ||= CssParser.new(@scanner, @line).parse_selector_string
node = node(Sass::Tree::DirectiveNode.new("@#{name} #{val}".strip))
if tok(/\{/)
@@ -179,19 +178,33 @@ def if_directive
expr = sass_script(:parse)
ss
node = block(node(Sass::Tree::IfNode.new(expr)), :directive)
+ pos = @scanner.pos
ss
- else_block(node)
+
+ else_block(node) ||
+ begin
+ # Backtrack in case there are any comments we want to parse
+ @scanner.pos = pos
+ node
+ end
end
def else_block(node)
- return node unless tok(/@else/)
+ return unless tok(/@else/)
ss
else_node = block(
Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))),
:directive)
node.add_else(else_node)
+ pos = @scanner.pos
ss
- else_block(node)
+
+ else_block(node) ||
+ begin
+ # Backtrack in case there are any comments we want to parse
+ @scanner.pos = pos
+ node
+ end
end
def extend_directive
@@ -199,8 +212,18 @@ def extend_directive
end
def import_directive
- @expected = "string or url()"
- arg = tok(STRING) || (uri = tok!(URI))
+ values = []
+
+ loop do
+ values << expr!(:import_arg)
+ break if use_css_import? || !tok(/,\s*/)
+ end
+
+ return values
+ end
+
+ def import_arg
+ return unless arg = tok(STRING) || (uri = tok!(URI))
path = @scanner[1] || @scanner[2] || @scanner[3]
ss
@@ -305,7 +328,7 @@ def block(node, context)
def block_contents(node, context)
block_given? ? yield : ss_comments(node)
node << (child = block_child(context))
- while tok(/;/) || (child && child.has_children)
+ while tok(/;/) || has_children?(child)
block_given? ? yield : ss_comments(node)
node << (child = block_child(context))
end
@@ -317,6 +340,12 @@ def block_child(context)
variable || directive || declaration_or_ruleset
end
+ def has_children?(child_or_array)
+ return false unless child_or_array
+ return child_or_array.last.has_children if child_or_array.is_a?(Array)
+ return child_or_array.has_children
+ end
+
# This is a nasty hack, and the only place in the parser
# that requires backtracking.
# The reason is that we can't figure out if certain strings
@@ -630,11 +659,10 @@ def term
unless e = tok(NUMBER) ||
tok(URI) ||
function ||
- interp_string ||
+ tok(STRING) ||
tok(UNICODERANGE) ||
tok(IDENT) ||
- tok(HEXCOLOR) ||
- interpolation
+ tok(HEXCOLOR)
return unless op = unary_operator
@expected = "number or function"
@@ -735,6 +763,7 @@ def merge(arr)
:expr => "expression (e.g. 1px, bold)",
:selector_comma_sequence => "selector",
:simple_selector_sequence => "selector",
+ :import_arg => "file to import (string or url())",
}
TOK_NAMES = Haml::Util.to_hash(
View
2 lib/sass/scss/rx.rb
@@ -63,7 +63,7 @@ def self.quote(str, flags = 0)
IDENT = /-?#{NMSTART}#{NMCHAR}*/
NAME = /#{NMCHAR}+/
- NUM = /[0-9]+|[0-9]*.[0-9]+/
+ NUM = /[0-9]+|[0-9]*\.[0-9]+/
STRING = /#{STRING1}|#{STRING2}/
URLCHAR = /[#%&*-~]|#{NONASCII}|#{ESCAPE}/
URL = /(#{URLCHAR}*)/
View
4 lib/sass/scss/static_parser.rb
@@ -8,11 +8,9 @@ module SCSS
class StaticParser < Parser
# Parses the text as a selector.
#
- # @param line [Fixnum] The line on which the selector appears.
- # Used for error reporting
# @param filename [String, nil] The file in which the selector appears,
# or nil if there is no such file.
- # Used for error reporting
+ # Used for error reporting.
# @return [Selector::CommaSequence] The parsed selector
# @raise [Sass::SyntaxError] if there's a syntax error in the selector
def parse_selector(filename)
View
2 lib/sass/tree/comment_node.rb
@@ -98,7 +98,7 @@ def _to_s(tabs = 0, _ = nil)
spaces = (' ' * [tabs - 1 - value[/^ */].size, 0].max)
content = value.gsub(/^/, spaces)
- content.gsub!(/\n +(\* *)?/, ' ') if style == :compact
+ content.gsub!(/\n +(\* *(?!\/))?/, ' ') if style == :compact
content
end
View
12 lib/sass/tree/node.rb
@@ -84,14 +84,18 @@ def filename
# Appends a child to the node.
#
- # @param child [Tree::Node] The child node
+ # @param child [Tree::Node, Array<Tree::Node>] The child node or nodes
# @raise [Sass::SyntaxError] if `child` is invalid
# @see #invalid_child?
def <<(child)
return if child.nil?
- check_child! child
- self.has_children = true
- @children << child
+ if child.is_a?(Array)
+ child.each {|c| self << c}
+ else
+ check_child! child
+ self.has_children = true
+ @children << child
+ end
end
# Raises an error if the given child node is invalid.
View
2 lib/sass/tree/prop_node.rb
@@ -167,7 +167,7 @@ def check!
def declaration(tabs = 0, opts = {:old => @prop_syntax == :old}, fmt = :sass)
name = self.name.map {|n| n.is_a?(String) ? n : "\#{#{n.to_sass(opts)}}"}.join
if name[0] == ?:
- raise Sass::SyntaxError.new("The \":#{name}: #{self.class.val_to_sass(value, opts)}\" hack is not allowed in the Sass indented syntax")
+ raise Sass::SyntaxError.new("The \"#{name}: #{self.class.val_to_sass(value, opts)}\" hack is not allowed in the Sass indented syntax")
end
old = opts[:old] && fmt == :sass
View
91 test/haml/engine_test.rb
@@ -148,7 +148,7 @@ def test_class_attr_with_array
assert_equal("<p class='b css'>foo</p>\n", render("%p.css{:class => %w[css b]} foo")) # merge uniquely
assert_equal("<p class='a b c d'>foo</p>\n", render("%p{:class => [%w[a b], %w[c d]]} foo")) # flatten
assert_equal("<p class='a b'>foo</p>\n", render("%p{:class => [:a, :b] } foo")) # stringify
- assert_equal("<p class=''>foo</p>\n", render("%p{:class => [nil, false] } foo")) # strip falsey
+ assert_equal("<p>foo</p>\n", render("%p{:class => [nil, false] } foo")) # strip falsey
assert_equal("<p class='a'>foo</p>\n", render("%p{:class => :a} foo")) # single stringify
assert_equal("<p>foo</p>\n", render("%p{:class => false} foo")) # single falsey
assert_equal("<p class='a b html'>foo</p>\n", render("%p(class='html'){:class => %w[a b]} foo")) # html attrs
@@ -159,12 +159,20 @@ def test_id_attr_with_array
assert_equal("<p id='css_a_b'>foo</p>\n", render("%p#css{:id => %w[a b]} foo")) # merge with css
assert_equal("<p id='a_b_c_d'>foo</p>\n", render("%p{:id => [%w[a b], %w[c d]]} foo")) # flatten
assert_equal("<p id='a_b'>foo</p>\n", render("%p{:id => [:a, :b] } foo")) # stringify
- assert_equal("<p id=''>foo</p>\n", render("%p{:id => [nil, false] } foo")) # strip falsey
+ assert_equal("<p>foo</p>\n", render("%p{:id => [nil, false] } foo")) # strip falsey
assert_equal("<p id='a'>foo</p>\n", render("%p{:id => :a} foo")) # single stringify
assert_equal("<p>foo</p>\n", render("%p{:id => false} foo")) # single falsey
assert_equal("<p id='html_a_b'>foo</p>\n", render("%p(id='html'){:id => %w[a b]} foo")) # html attrs
end
+ def test_colon_in_class_attr
+ assert_equal("<p class='foo:bar' />\n", render("%p.foo:bar/"))
+ end
+
+ def test_colon_in_id_attr
+ assert_equal("<p id='foo:bar' />\n", render("%p#foo:bar/"))
+ end
+
def test_dynamic_attributes_with_no_content
assert_equal(<<HTML, render(<<HAML))
<p>
@@ -660,6 +668,15 @@ def test_if_without_content_and_else
- else
foo
HAML
+
+ assert_equal(<<HTML, render(<<HAML))
+foo
+HTML
+- if true
+ - if false
+ - else
+ foo
+HAML
end
def test_html_attributes_with_hash
@@ -680,6 +697,72 @@ def test_filter_with_newline_and_interp
HAML
end
+ def test_case_assigned_to_var
+ assert_equal(<<HTML, render(<<HAML))
+bar
+HTML
+- var = case 12
+- when 1; "foo"
+- when 12; "bar"
+= var
+HAML
+
+ assert_equal(<<HTML, render(<<HAML))
+bar
+HTML
+- var = case 12
+- when 1
+ - "foo"
+- when 12
+ - "bar"
+= var
+HAML
+
+ assert_equal(<<HTML, render(<<HAML))
+bar
+HTML
+- var = case 12
+ - when 1
+ - "foo"
+ - when 12
+ - "bar"
+= var
+HAML
+ end
+
+ def test_if_assigned_to_var
+ assert_equal(<<HTML, render(<<HAML))
+foo
+HTML
+- var = if false
+- else
+ - "foo"
+= var
+HAML
+
+ assert_equal(<<HTML, render(<<HAML))
+foo
+HTML
+- var = if false
+- elsif 12
+ - "foo"
+- elsif 14; "bar"
+- else
+ - "baz"
+= var
+HAML
+
+ assert_equal(<<HTML, render(<<HAML))
+foo
+HTML
+- var = if false
+ - "bar"
+- else
+ - "foo"
+= var
+HAML
+ end
+
# HTML escaping tests
def test_ampersand_equals_should_escape
@@ -1153,7 +1236,9 @@ def test_xhtml_output_option
end
def test_arbitrary_output_option
- assert_raise(Haml::Error, "Invalid output format :html1") { engine("%br", :format => :html1) }
+ assert_raise_message(Haml::Error, "Invalid output format :html1") do
+ engine("%br", :format => :html1)
+ end
end
def test_static_hashes
View
32 test/haml/helper_test.rb
@@ -5,6 +5,10 @@ class ActionView::Base
def nested_tag
content_tag(:span) {content_tag(:div) {"something"}}
end
+
+ def wacky_form
+ form_tag("/foo") {"bar"}
+ end
end
module Haml::Helpers
@@ -118,7 +122,7 @@ def test_form_tag
# This is usually provided by ActionController::Base.
def @base.protect_against_forgery?; false; end
assert_equal(<<HTML, render(<<HAML, :action_view))
-<form action="foo" method="post">
+<form #{rails_form_attr}action="foo" method="post">#{rails_form_opener}
<p>bar</p>
<strong>baz</strong>
</form>
@@ -167,7 +171,7 @@ def test_content_tag_error_wrapping
def @base.protect_against_forgery?; false; end
error_class = Haml::Util.ap_geq_3? ? "field_with_errors" : "fieldWithErrors"
assert_equal(<<HTML, render(<<HAML, :action_view))
-<form action="" method="post">
+<form #{rails_form_attr}action="" method="post">#{rails_form_opener}
<div class="#{error_class}"><label for="post_error_field">Error field</label></div>
</form>
HTML
@@ -176,10 +180,23 @@ def @base.protect_against_forgery?; false; end
HAML