Merge branch 'master' of git://github.com/chriseppstein/haml

This commit is contained in:
Nathan Weizenbaum 2008-08-10 22:03:31 -04:00
commit 4f41cf00fc
12 changed files with 281 additions and 78 deletions

View File

@ -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

View File

@ -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.
#
# [<tt>:template_location</tt>] The directory where Sass templates should be read from.
# [<tt>:template_location</tt>] 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 <tt>RAILS_ROOT + "/public/stylesheets/sass"</tt>
# or <tt>MERB_ROOT + "/public/stylesheets/sass"</tt>.
# 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.
#
# [<tt>:css_location</tt>] The directory where CSS output should be written to.
# [<tt>:css_location</tt>] The path where CSS output should be written to.
# This option is ignored when :template_location is a Hash.
# Defaults to <tt>RAILS_ROOT + "/public/stylesheets"</tt>
# or <tt>MERB_ROOT + "/public/stylesheets"</tt>.
# Only has meaning within Ruby on Rails or Merb.

View File

@ -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

View File

@ -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 <tt>options[:css_location]</tt>
# to see if it needs updating,
# and updates it using the corresponding template
# from <tt>options[:templates]</tt>
# if it does.
# 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

View File

@ -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

View File

@ -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; }

View File

@ -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; }

29
test/sass/more_results/more_import.css vendored Normal file
View File

@ -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; }

View File

@ -0,0 +1,2 @@
#foo
:background-color #baf

View File

@ -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

View File

@ -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

View File

@ -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