diff --git a/lib/haml/exec.rb b/lib/haml/exec.rb
index 42df9cc7..f6e86a6b 100644
--- a/lib/haml/exec.rb
+++ b/lib/haml/exec.rb
@@ -180,6 +180,10 @@ END
'Output style. Can be nested (default), compact, compressed, or expanded.') do |name|
@options[:for_engine][:style] = name.to_sym
end
+ opts.on('-l', '--line-comments',
+ 'Line Comments. Emit comments in the generated CSS indicating the corresponding sass line.') do
+ @options[:for_engine][:line_comments] = true
+ end
end
def process_result
diff --git a/lib/sass.rb b/lib/sass.rb
index 890c67a1..d87a1b90 100644
--- a/lib/sass.rb
+++ b/lib/sass.rb
@@ -846,12 +846,18 @@ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
# Defaults to false in production mode, true otherwise.
# Only has meaning within Ruby on Rails or Merb.
#
-# [:template_location] The directory where Sass templates should be read from.
+# [:template_location] A path to the root sass template directory for you application.
+# If a hash, :css_location is ignored and this option designates
+# both a mapping between input and output directories.
+# May also be given a list of 2-element lists, instead of a hash.
# Defaults to RAILS_ROOT + "/public/stylesheets/sass"
# or MERB_ROOT + "/public/stylesheets/sass".
# Only has meaning within Ruby on Rails or Merb.
+# This will be derived from the :css_location path list if not provided
+# by appending a folder of "sass" to each corresponding css location.
#
-# [:css_location] The directory where CSS output should be written to.
+# [:css_location] The path where CSS output should be written to.
+# This option is ignored when :template_location is a Hash.
# Defaults to RAILS_ROOT + "/public/stylesheets"
# or MERB_ROOT + "/public/stylesheets".
# Only has meaning within Ruby on Rails or Merb.
diff --git a/lib/sass/engine.rb b/lib/sass/engine.rb
index 37653e51..bd5b7409 100644
--- a/lib/sass/engine.rb
+++ b/lib/sass/engine.rb
@@ -438,6 +438,12 @@ END
raise SyntaxError.new("Unbalanced brackets.", @line)
end
+ def import_paths
+ paths = @options[:load_paths] || []
+ paths.unshift(File.dirname(@options[:filename])) if @options[:filename]
+ paths
+ end
+
def import(files)
nodes = []
@@ -445,7 +451,7 @@ END
engine = nil
begin
- filename = self.class.find_file_to_import(filename, @options[:load_paths])
+ filename = self.class.find_file_to_import(filename, import_paths)
rescue Exception => e
raise SyntaxError.new(e.message, @line)
end
@@ -503,7 +509,9 @@ END
def self.find_full_path(filename, load_paths)
load_paths.each do |path|
- ["_#{filename}", filename].each do |name|
+ segments = filename.split(File::SEPARATOR)
+ segments.push "_#{segments.pop}"
+ [segments.join(File::SEPARATOR), filename].each do |name|
full_path = File.join(path, name)
if File.readable?(full_path)
return full_path
diff --git a/lib/sass/plugin.rb b/lib/sass/plugin.rb
index 11176f2d..b6564a7d 100644
--- a/lib/sass/plugin.rb
+++ b/lib/sass/plugin.rb
@@ -8,7 +8,6 @@ module Sass
module Plugin
class << self
@@options = {
- :template_location => './public/stylesheets/sass',
:css_location => './public/stylesheets',
:always_update => false,
:always_check => true,
@@ -35,44 +34,20 @@ module Sass
@@options.merge!(value)
end
- # Checks each stylesheet in options[:css_location]
- # to see if it needs updating,
- # and updates it using the corresponding template
- # from options[:templates]
- # if it does.
+ # Checks each css stylesheet to see if it needs updating,
+ # and updates it using the corresponding sass template if it does.
def update_stylesheets
return if options[:never_update]
@@checked_for_updates = true
- Dir.glob(File.join(options[:template_location], "**", "*.sass")).entries.each do |file|
+ template_locations.zip(css_locations).each do |template_location, css_location|
- # Get the relative path to the file with no extension
- name = file.sub(options[:template_location] + "/", "")[0...-5]
+ Dir.glob(File.join(template_location, "**", "*.sass")).each do |file|
+ # Get the relative path to the file with no extension
+ name = file.sub(template_location + "/", "")[0...-5]
- if !forbid_update?(name) && (options[:always_update] || stylesheet_needs_update?(name))
- css = css_filename(name)
- File.delete(css) if File.exists?(css)
-
- filename = template_filename(name)
- l_options = @@options.dup
- l_options[:css_filename] = css
- l_options[:filename] = filename
- l_options[:load_paths] = load_paths
- engine = Engine.new(File.read(filename), l_options)
- result = begin
- engine.render
- rescue Exception => e
- exception_string(e)
- end
-
- # Create any directories that might be necessary
- dirs = [l_options[:css_location]]
- name.split("/")[0...-1].each { |dir| dirs << "#{dirs[-1]}/#{dir}" }
- dirs.each { |dir| Dir.mkdir(dir) unless File.exist?(dir) }
-
- # Finally, write the file
- File.open(css, 'w') do |file|
- file.print(result)
+ if !forbid_update?(name) && (options[:always_update] || stylesheet_needs_update?(name, template_location, css_location))
+ update_stylesheet(name, template_location, css_location)
end
end
end
@@ -80,8 +55,57 @@ module Sass
private
+ def update_stylesheet(name, template_location, css_location)
+ css = css_filename(name, css_location)
+ File.delete(css) if File.exists?(css)
+
+ filename = template_filename(name, template_location)
+ l_options = @@options.dup
+ l_options[:css_filename] = css
+ l_options[:filename] = filename
+ l_options[:load_paths] = load_paths
+ engine = Engine.new(File.read(filename), l_options)
+ result = begin
+ engine.render
+ rescue Exception => e
+ exception_string(e)
+ end
+
+ # Create any directories that might be necessary
+ mkpath(css_location, name)
+
+ # Finally, write the file
+ File.open(css, 'w') do |file|
+ file.print(result)
+ end
+ end
+
+ # Create any successive directories required to be able to write a file to: File.join(base,name)
+ def mkpath(base, name)
+ dirs = [base]
+ name.split(File::SEPARATOR)[0...-1].each { |dir| dirs << File.join(dirs[-1],dir) }
+ dirs.each { |dir| Dir.mkdir(dir) unless File.exist?(dir) }
+ end
+
def load_paths
- (options[:load_paths] || []) + [options[:template_location]]
+ (options[:load_paths] || []) + template_locations
+ end
+
+ def template_locations
+ location = (options[:template_location] || File.join(options[:css_location],'sass'))
+ if location.is_a?(String)
+ [location]
+ else
+ location.to_a.map { |l| l.first }
+ end
+ end
+
+ def css_locations
+ if options[:template_location] && !options[:template_location].is_a?(String)
+ options[:template_location].to_a.map { |l| l.last }
+ else
+ [options[:css_location]]
+ end
end
def exception_string(e)
@@ -98,10 +122,14 @@ module Sass
e_string << "\n\n"
min = [e.sass_line - 5, 0].max
- File.read(e.sass_filename).rstrip.split("\n")[
- min .. e.sass_line + 5
- ].each_with_index do |line, i|
- e_string << "#{min + i + 1}: #{line}\n"
+ begin
+ File.read(e.sass_filename).rstrip.split("\n")[
+ min .. e.sass_line + 5
+ ].each_with_index do |line, i|
+ e_string << "#{min + i + 1}: #{line}\n"
+ end
+ rescue
+ e_string << "Couldn't read sass file: #{e.sass_filename}"
end
end
end
@@ -123,25 +151,27 @@ END
end
end
- def template_filename(name)
- "#{options[:template_location]}/#{name}.sass"
+ def template_filename(name, path)
+ "#{path}/#{name}.sass"
end
- def css_filename(name)
- "#{options[:css_location]}/#{name}.css"
+ def css_filename(name, path)
+ "#{path}/#{name}.css"
end
def forbid_update?(name)
name.sub(/^.*\//, '')[0] == ?_
end
- def stylesheet_needs_update?(name)
- if !File.exists?(css_filename(name))
+ def stylesheet_needs_update?(name, template_path, css_path)
+ css_file = css_filename(name, css_path)
+ template_file = template_filename(name, template_path)
+ if !File.exists?(css_file)
return true
else
- css_mtime = File.mtime(css_filename(name))
- File.mtime(template_filename(name)) > css_mtime ||
- dependencies(template_filename(name)).any?(&dependency_updated?(css_mtime))
+ css_mtime = File.mtime(css_file)
+ File.mtime(template_file) > css_mtime ||
+ dependencies(template_file).any?(&dependency_updated?(css_mtime))
end
end
diff --git a/lib/sass/tree/rule_node.rb b/lib/sass/tree/rule_node.rb
index 1711958b..ad900d60 100644
--- a/lib/sass/tree/rule_node.rb
+++ b/lib/sass/tree/rule_node.rb
@@ -68,8 +68,16 @@ module Sass::Tree
if @options[:line_comments] && @style != :compressed
to_return << "#{old_spaces}/* line #{line}"
- if filename && @options[:css_filename]
- relative_filename = Pathname.new(filename).relative_path_from(Pathname.new(File.dirname(@options[:css_filename]))).to_s
+ if filename
+ relative_filename = if @options[:css_filename]
+ begin
+ Pathname.new(filename).relative_path_from(
+ Pathname.new(File.dirname(@options[:css_filename]))).to_s
+ rescue ArgumentError
+ nil
+ end
+ end
+ relative_filename ||= filename
to_return << ", #{relative_filename}"
end
diff --git a/test/sass/more_results/more1.css b/test/sass/more_results/more1.css
new file mode 100644
index 00000000..b0d1182f
--- /dev/null
+++ b/test/sass/more_results/more1.css
@@ -0,0 +1,9 @@
+body { font: Arial; background: blue; }
+
+#page { width: 700px; height: 100; }
+#page #header { height: 300px; }
+#page #header h1 { font-size: 50px; color: blue; }
+
+#content.user.show #container.top #column.left { width: 100px; }
+#content.user.show #container.top #column.right { width: 600px; }
+#content.user.show #container.bottom { background: brown; }
diff --git a/test/sass/more_results/more1_with_line_comments.css b/test/sass/more_results/more1_with_line_comments.css
new file mode 100644
index 00000000..f31dbca2
--- /dev/null
+++ b/test/sass/more_results/more1_with_line_comments.css
@@ -0,0 +1,26 @@
+/* line 3, ../more_templates/more1.sass */
+body {
+ font: Arial;
+ background: blue; }
+
+/* line 7, ../more_templates/more1.sass */
+#page {
+ width: 700px;
+ height: 100; }
+ /* line 10, ../more_templates/more1.sass */
+ #page #header {
+ height: 300px; }
+ /* line 12, ../more_templates/more1.sass */
+ #page #header h1 {
+ font-size: 50px;
+ color: blue; }
+
+/* line 18, ../more_templates/more1.sass */
+#content.user.show #container.top #column.left {
+ width: 100px; }
+/* line 20, ../more_templates/more1.sass */
+#content.user.show #container.top #column.right {
+ width: 600px; }
+/* line 22, ../more_templates/more1.sass */
+#content.user.show #container.bottom {
+ background: brown; }
diff --git a/test/sass/more_results/more_import.css b/test/sass/more_results/more_import.css
new file mode 100644
index 00000000..97c4797e
--- /dev/null
+++ b/test/sass/more_results/more_import.css
@@ -0,0 +1,29 @@
+imported { otherconst: hello; myconst: goodbye; pre-mixin: here; }
+
+body { font: Arial; background: blue; }
+
+#page { width: 700px; height: 100; }
+#page #header { height: 300px; }
+#page #header h1 { font-size: 50px; color: blue; }
+
+#content.user.show #container.top #column.left { width: 100px; }
+#content.user.show #container.top #column.right { width: 600px; }
+#content.user.show #container.bottom { background: brown; }
+
+midrule { inthe: middle; }
+
+body { font: Arial; background: blue; }
+
+#page { width: 700px; height: 100; }
+#page #header { height: 300px; }
+#page #header h1 { font-size: 50px; color: blue; }
+
+#content.user.show #container.top #column.left { width: 100px; }
+#content.user.show #container.top #column.right { width: 600px; }
+#content.user.show #container.bottom { background: brown; }
+
+@import url(basic.css);
+@import url(../results/complex.css);
+#foo { background-color: #baf; }
+
+nonimported { myconst: hello; otherconst: goodbye; post-mixin: here; }
diff --git a/test/sass/more_templates/_more_partial.sass b/test/sass/more_templates/_more_partial.sass
new file mode 100644
index 00000000..bef627d2
--- /dev/null
+++ b/test/sass/more_templates/_more_partial.sass
@@ -0,0 +1,2 @@
+#foo
+ :background-color #baf
diff --git a/test/sass/more_templates/more1.sass b/test/sass/more_templates/more1.sass
new file mode 100644
index 00000000..71117bf5
--- /dev/null
+++ b/test/sass/more_templates/more1.sass
@@ -0,0 +1,23 @@
+
+
+body
+ :font Arial
+ :background blue
+
+#page
+ :width 700px
+ :height 100
+ #header
+ :height 300px
+ h1
+ :font-size 50px
+ :color blue
+
+#content.user.show
+ #container.top
+ #column.left
+ :width 100px
+ #column.right
+ :width 600px
+ #container.bottom
+ :background brown
\ No newline at end of file
diff --git a/test/sass/more_templates/more_import.sass b/test/sass/more_templates/more_import.sass
new file mode 100644
index 00000000..fa329b32
--- /dev/null
+++ b/test/sass/more_templates/more_import.sass
@@ -0,0 +1,11 @@
+!preconst = hello
+
+=premixin
+ pre-mixin: here
+
+@import importee, basic, basic.css, ../results/complex.css, more_partial
+
+nonimported
+ :myconst = !preconst
+ :otherconst = !postconst
+ +postmixin
diff --git a/test/sass/plugin_test.rb b/test/sass/plugin_test.rb
index f1a379e0..ef025669 100644
--- a/test/sass/plugin_test.rb
+++ b/test/sass/plugin_test.rb
@@ -14,13 +14,15 @@ class SassPluginTest < Test::Unit::TestCase
}
def setup
- FileUtils.mkdir File.dirname(__FILE__) + '/tmp'
+ FileUtils.mkdir tempfile_loc
+ FileUtils.mkdir tempfile_loc(nil,"more_")
set_plugin_opts
Sass::Plugin.update_stylesheets
end
def teardown
- FileUtils.rm_r File.dirname(__FILE__) + '/tmp'
+ FileUtils.rm_r tempfile_loc
+ FileUtils.rm_r tempfile_loc(nil,"more_")
end
def test_templates_should_render_correctly
@@ -29,32 +31,32 @@ class SassPluginTest < Test::Unit::TestCase
def test_no_update
File.delete(tempfile_loc('basic'))
- assert Sass::Plugin.stylesheet_needs_update?('basic')
+ assert Sass::Plugin.stylesheet_needs_update?('basic', template_loc, tempfile_loc)
Sass::Plugin.update_stylesheets
- assert !Sass::Plugin.stylesheet_needs_update?('basic')
+ assert !Sass::Plugin.stylesheet_needs_update?('basic', template_loc, tempfile_loc)
end
def test_update_needed_when_modified
sleep(1)
FileUtils.touch(template_loc('basic'))
- assert Sass::Plugin.stylesheet_needs_update?('basic')
+ assert Sass::Plugin.stylesheet_needs_update?('basic', template_loc, tempfile_loc)
Sass::Plugin.update_stylesheets
- assert !Sass::Plugin.stylesheet_needs_update?('basic')
+ assert !Sass::Plugin.stylesheet_needs_update?('basic', template_loc, tempfile_loc)
end
def test_update_needed_when_dependency_modified
sleep(1)
FileUtils.touch(template_loc('basic'))
- assert Sass::Plugin.stylesheet_needs_update?('import')
+ assert Sass::Plugin.stylesheet_needs_update?('import', template_loc, tempfile_loc)
Sass::Plugin.update_stylesheets
- assert !Sass::Plugin.stylesheet_needs_update?('import')
+ assert !Sass::Plugin.stylesheet_needs_update?('import', template_loc, tempfile_loc)
end
def test_full_exception_handling
File.delete(tempfile_loc('bork'))
Sass::Plugin.update_stylesheets
File.open(tempfile_loc('bork')) do |file|
- assert_equal("/*\nSass::SyntaxError: Undefined constant: \"!bork\".\non line 2 of #{File.dirname(__FILE__) + '/templates/bork.sass'}\n\n1: bork\n2: :bork= !bork", file.read.split("\n")[0...6].join("\n"))
+ assert_equal("/*\nSass::SyntaxError: Undefined constant: \"!bork\".\non line 2 of #{template_loc('bork')}\n\n1: bork\n2: :bork= !bork", file.read.split("\n")[0...6].join("\n"))
end
File.delete(tempfile_loc('bork'))
end
@@ -69,14 +71,34 @@ class SassPluginTest < Test::Unit::TestCase
Sass::Plugin.options[:full_exception] = true
end
+
+ def test_two_template_directories
+ set_plugin_opts :template_location => {
+ template_loc => tempfile_loc,
+ template_loc(nil,'more_') => tempfile_loc(nil,'more_')
+ }
+ Sass::Plugin.update_stylesheets
+ ['more1', 'more_import'].each { |name| assert_renders_correctly(name, :prefix => 'more_') }
+ end
+
+ def test_two_template_directories_with_line_annotations
+ set_plugin_opts :line_comments => true,
+ :style => :nested,
+ :template_location => {
+ template_loc => tempfile_loc,
+ template_loc(nil,'more_') => tempfile_loc(nil,'more_')
+ }
+ Sass::Plugin.update_stylesheets
+ assert_renders_correctly('more1_with_line_comments', 'more1', :prefix => 'more_')
+ end
def test_rails_update
File.delete(tempfile_loc('basic'))
- assert Sass::Plugin.stylesheet_needs_update?('basic')
+ assert Sass::Plugin.stylesheet_needs_update?('basic', template_loc, tempfile_loc)
ActionController::Base.new.process
- assert !Sass::Plugin.stylesheet_needs_update?('basic')
+ assert !Sass::Plugin.stylesheet_needs_update?('basic', template_loc, tempfile_loc)
end
def test_merb_update
@@ -105,33 +127,58 @@ class SassPluginTest < Test::Unit::TestCase
private
- def assert_renders_correctly(name)
- File.read(result_loc(name)).split("\n").zip(File.read(tempfile_loc(name)).split("\n")).each_with_index do |pair, line|
- message = "template: #{name}\nline: #{line + 1}"
+ def assert_renders_correctly(*arguments)
+ options = arguments.last.is_a?(Hash) ? arguments.pop : {}
+ prefix = options[:prefix]
+ result_name = arguments.shift
+ tempfile_name = arguments.shift || result_name
+ expected_lines = File.read(result_loc(result_name, prefix)).split("\n")
+ actual_lines = File.read(tempfile_loc(tempfile_name, prefix)).split("\n")
+ expected_lines.zip(actual_lines).each_with_index do |pair, line|
+ message = "template: #{result_name}\nline: #{line + 1}"
assert_equal(pair.first, pair.last, message)
end
+ if expected_lines.size < actual_lines.size
+ assert(false, "#{actual_lines.size - expected_lines.size} Trailing lines found in #{tempfile_name}.css: #{actual_lines[expected_lines.size..-1].join('\n')}")
+ end
end
- def template_loc(name)
- File.dirname(__FILE__) + "/templates/#{name}.sass"
+ def template_loc(name = nil, prefix = nil)
+ if name
+ absolutize "#{prefix}templates/#{name}.sass"
+ else
+ absolutize "#{prefix}templates"
+ end
end
- def tempfile_loc(name)
- File.dirname(__FILE__) + "/tmp/#{name}.css"
+ def tempfile_loc(name = nil, prefix = nil)
+ if name
+ absolutize "#{prefix}tmp/#{name}.css"
+ else
+ absolutize "#{prefix}tmp"
+ end
end
- def result_loc(name)
- File.dirname(__FILE__) + "/results/#{name}.css"
+ def result_loc(name = nil, prefix = nil)
+ if name
+ absolutize "#{prefix}results/#{name}.css"
+ else
+ absolutize "#{prefix}results"
+ end
end
- def set_plugin_opts
+ def absolutize(file)
+ "#{File.dirname(__FILE__)}/#{file}"
+ end
+
+ def set_plugin_opts(overrides = {})
Sass::Plugin.options = {
- :template_location => File.dirname(__FILE__) + '/templates',
- :css_location => File.dirname(__FILE__) + '/tmp',
+ :template_location => template_loc,
+ :css_location => tempfile_loc,
:style => :compact,
- :load_paths => [File.dirname(__FILE__) + '/results'],
+ :load_paths => [result_loc],
:always_update => true,
- }
+ }.merge(overrides)
end
end