Merge branch 'master' of git://github.com/chriseppstein/haml
This commit is contained in:
commit
4f41cf00fc
|
@ -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
|
||||
|
|
10
lib/sass.rb
10
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.
|
||||
#
|
||||
# [<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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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; }
|
|
@ -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; }
|
|
@ -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; }
|
|
@ -0,0 +1,2 @@
|
|||
#foo
|
||||
:background-color #baf
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue