Allow some config attributes that are arrays to be inherited properly

from inherited config data.
This commit is contained in:
Chris Eppstein 2012-01-29 13:06:57 -08:00
parent d642ae7b74
commit a6c045785d
6 changed files with 354 additions and 11 deletions

View File

@ -0,0 +1,93 @@
require 'compass/commands/project_base'
require 'compass/commands/update_project'
module Compass
module Commands
module StructureOptionsParser
def set_options(opts)
opts.banner = %Q{
Usage: compass structure [path/to/project] [options]
Description:
Display the import structure of your stylesheets.
Options:
}.strip.split("\n").map{|l| l.gsub(/^ {0,10}/,'')}.join("\n")
super
end
end
class ProjectStats < UpdateProject
register :structure
def initialize(working_path, options)
super
assert_project_directory_exists!
end
def perform
@compiler = new_compiler_instance
(options[:sass_files] || sorted_sass_files).each do |sass_file|
print_tree(Compass.projectize(sass_file))
end
end
def print_tree(file, depth = 0, importer = @compiler.importer)
puts ((depth > 0 ? "| " : " ") * depth) + "+- " + Compass.deprojectize(file)
@compiler.staleness_checker.send(:compute_dependencies, file, importer).each do |(dep, dep_importer)|
print_tree(dep, depth + 1, dep_importer)# unless Compass.deprojectize(dep)[0...1] == "/"
end
end
def sorted_sass_files
sass_files = @compiler.sass_files
sass_files.map! do |s|
filename = Compass.deprojectize(s, File.join(Compass.configuration.project_path, Compass.configuration.sass_dir))
[s, File.dirname(filename), File.basename(filename)]
end
sass_files = sass_files.sort_by do |s,d,f|
File.join(d, f[0] == ?_ ? f[1..-1] : f)
end
sass_files.map!{|s,d,f| s}
end
class << self
def option_parser(arguments)
parser = Compass::Exec::CommandOptionParser.new(arguments)
parser.extend(Compass::Exec::GlobalOptionsParser)
parser.extend(Compass::Exec::ProjectOptionsParser)
parser.extend(StructureOptionsParser)
end
def usage
option_parser([]).to_s
end
def description(command)
"Report statistics about your stylesheets"
end
def primary; false; end
def parse!(arguments)
parser = option_parser(arguments)
parser.parse!
parse_arguments!(parser, arguments)
parser.options
end
def parse_arguments!(parser, arguments)
if arguments.size > 0
parser.options[:project_name] = arguments.shift if File.directory?(arguments.first)
parser.options[:sass_files] = arguments
end
end
end
end
end
end

View File

@ -25,7 +25,6 @@ module Compass
attributes_for_directory(:fonts),
attributes_for_directory(:extensions, nil),
# Compilation options
:sprite_load_path,
:output_style,
:environment,
:relative_assets,
@ -44,6 +43,12 @@ module Compass
:chunky_png_options
].flatten
ARRAY_ATTRIBUTES = [
:sprite_load_path,
:required_libraries,
:loaded_frameworks,
:framework_path
]
# Registers a new configuration property.
# Extensions can use this to add new configuration options to compass.
#

View File

@ -57,10 +57,11 @@ module Compass
chained_method :run_stylesheet_error
inherited_accessor *ATTRIBUTES
inherited_accessor :required_libraries, :loaded_frameworks, :framework_path #XXX we should make these arrays add up cumulatively.
strip_trailing_separator *ATTRIBUTES.select{|a| a.to_s =~ /dir|path/}
inherited_array *ARRAY_ATTRIBUTES
def initialize(name, attr_hash = nil)
raise "I need a name!" unless name
@name = name
@ -91,7 +92,9 @@ module Compass
# The block will be passed the root-relative url of the asset.
# When called without a block, returns the block that was previously set.
def asset_host(&block)
@set_attributes ||= {}
if block_given?
@set_attributes[:asset_host] = true
@asset_host = block
else
if @asset_host
@ -116,16 +119,19 @@ module Compass
#
# asset_cache_buster :none
def asset_cache_buster(simple = nil, &block)
@set_attributes ||= {}
if block_given?
@set_attributes[:asset_cache_buster] = true
@asset_cache_buster = block
elsif !simple.nil?
if simple == :none
@set_attributes[:asset_cache_buster] = true
@asset_cache_buster = Proc.new {|_,_| nil}
else
raise ArgumentError, "Unexpected argument: #{simple.inspect}"
end
else
if @asset_cache_buster
if set?(:asset_cache_buster)
@asset_cache_buster
elsif inherited_data.respond_to?(:asset_cache_buster)
inherited_data.asset_cache_buster
@ -173,7 +179,7 @@ module Compass
private
def assert_valid_keys!(attr_hash)
illegal_attrs = attr_hash.keys - ATTRIBUTES
illegal_attrs = attr_hash.keys - ATTRIBUTES - ARRAY_ATTRIBUTES
if illegal_attrs.size == 1
raise Error, "#{illegal_attrs.first.inspect} is not a valid configuration attribute."
elsif illegal_attrs.size > 0

View File

@ -61,6 +61,97 @@ module Compass
inherited_writer(*attributes)
end
class ArrayProxy
def initialize(data, attr)
@data, @attr = data, attr
end
def to_ary
@data.send(:"read_inherited_#{@attr}_array")
end
def to_a
to_ary
end
def <<(v)
@data.send(:"add_to_#{@attr}", v)
end
def >>(v)
@data.send(:"remove_from_#{@attr}", v)
end
def serialize_to_config(prop)
if v = @data.raw(prop)
"#{prop} = #{v.inspect}"
else
s = ""
if added = @data.instance_variable_get("@added_to_#{@attr}")
added.each do |a|
s << "#{prop} << #{a.inspect}\n"
end
end
if removed = @data.instance_variable_get("@removed_from_#{@attr}")
removed.each do |r|
s << "#{prop} >> #{r.inspect}\n"
end
end
if s[-1..-1] == "\n"
s[0..-2]
else
s
end
end
end
def method_missing(m, *args, &block)
a = to_ary
if a.respond_to?(m)
a.send(m,*args, &block)
else
super
end
end
end
def inherited_array(*attributes)
inherited_reader(*attributes)
inherited_writer(*attributes)
attributes.each do |attr|
line = __LINE__ + 1
class_eval %Q{
def #{attr} # def sprite_load_paths
ArrayProxy.new(self, #{attr.inspect}) # ArrayProxy.new(self, :sprite_load_paths)
end # end
def #{attr}=(value) # def sprite_load_paths=(value)
@set_attributes ||= {} # @set_attributes ||= {}
@set_attributes[#{attr.inspect}] = true # @set_attributes[:sprite_load_paths] = true
@#{attr} = Array(value) # @sprite_load_paths = Array(value)
@added_to_#{attr} = [] # @added_to_sprite_load_paths = []
@removed_from_#{attr} = [] # @removed_from_sprite_load_paths = []
end # end
def read_inherited_#{attr}_array # def read_inherited_sprite_load_paths_array
if #{attr}_set? # if sprite_load_paths_set?
@#{attr} # Array(@#{attr})
else # else
value = Array(read(#{attr.inspect})) # value = Array(read(:sprite_load_paths))
value -= Array(@removed_from_#{attr}) # value -= Array(@removed_from_sprite_load_paths)
Array(@added_to_#{attr}) + value # Array(@added_to_sprite_load_paths) + value
end # end
end # end
def add_to_#{attr}(v) # def add_to_sprite_load_paths(v)
if #{attr}_set? # if sprite_load_paths_set?
raw_#{attr} << v # raw_sprite_load_paths << v
else # else
(@added_to_#{attr} ||= []) << v # (@added_to_sprite_load_paths ||= []) << v
end # end
end # end
def remove_from_#{attr}(v) # def remove_from_sprite_load_paths(v)
if #{attr}_set? # if sprite_load_paths_set?
raw_#{attr}.reject!{|e| e == v} # raw_sprite_load_path.reject!{|e| e == v}s
else # else
(@removed_from_#{attr} ||= []) << v # (@removed_from_sprite_load_paths ||= []) << v
end # end
end # end
}, __FILE__, line
end
end
def chained_method(method)
line = __LINE__ + 1
class_eval %Q{
@ -146,6 +237,12 @@ module Compass
end
end
# Reads the raw value that was set on this object.
# you generally should call raw_<attribute>() instead.
def raw(attribute)
instance_variable_get("@#{attribute}")
end
# Read a value that is either inherited or set on this instance, if we get to the bottom-most configuration instance,
# we ask for the default starting at the top level.
def read(attribute)

View File

@ -16,13 +16,19 @@ module Compass
end
end
def get_binding
binding
end
def parse_string(contents, filename)
bind = binding
bind = get_binding
eval(contents, bind, filename)
ATTRIBUTES.each do |prop|
value = eval(prop.to_s, bind) rescue nil
value = value.to_s if value.is_a?(Pathname)
self.send("#{prop}=", value) unless value.nil?
local_vars_set = eval("local_variables", bind)
local_vars_set.each do |local_var|
if (ATTRIBUTES+ARRAY_ATTRIBUTES).include?(local_var)
value = eval(local_var.to_s, bind)
value = value.to_s if value.is_a?(Pathname)
self.send("#{local_var}=", value)
end
end
if @added_import_paths
self.additional_import_paths ||= []
@ -45,7 +51,7 @@ module Compass
end
contents << "# Require any additional compass plugins here.\n"
contents << "\n" if (required_libraries || []).any?
ATTRIBUTES.each do |prop|
(ATTRIBUTES + ARRAY_ATTRIBUTES).each do |prop|
value = send("#{prop}_without_default")
if value.is_a?(Proc)
$stderr.puts "WARNING: #{prop} is code and cannot be written to a file. You'll need to copy it yourself."
@ -64,7 +70,11 @@ module Compass
end
def serialize_property(prop, value)
%Q(#{prop} = #{value.inspect}\n)
if value.respond_to?(:serialize_to_config)
value.serialize_to_config(prop) + "\n"
else
%Q(#{prop} = #{value.inspect}\n)
end
end
def issue_deprecation_warnings

View File

@ -68,6 +68,84 @@ class ConfigurationTest < Test::Unit::TestCase
assert_equal "WARNING: asset_host is code and cannot be written to a file. You'll need to copy it yourself.\n", warning
end
class TestData < Compass::Configuration::Data
def initialize
super(:test)
end
inherited_array :stuff
end
def test_inherited_array_can_clobber
data1 = TestData.new
data1.stuff = [:a]
data2 = TestData.new
data2.stuff = [:b]
data2.inherit_from!(data1)
assert_equal [:b], data2.stuff.to_a
end
def test_inherited_array_can_append
data1 = TestData.new
data1.stuff = [:a]
data2 = TestData.new
data2.stuff << :b
data2.inherit_from!(data1)
assert_equal [:b, :a], data2.stuff.to_a
end
def test_inherited_array_can_remove
data1 = TestData.new
data1.stuff = [:a]
data2 = TestData.new
data2.stuff >> :a
data2.inherit_from!(data1)
assert_equal [], data2.stuff.to_a
end
def test_inherited_array_combined_augmentations
data1 = TestData.new
data1.stuff = [:a]
data2 = TestData.new
data2.stuff >> :a
data2.stuff << :b
data2.inherit_from!(data1)
assert_equal [:b], data2.stuff.to_a
end
def test_inherited_array_long_methods
data1 = TestData.new
data1.stuff = [:a]
data2 = TestData.new
data2.remove_from_stuff(:a)
data2.add_to_stuff(:b)
data2.inherit_from!(data1)
assert_equal [:b], data2.stuff.to_a
end
def test_inherited_array_augmentations_can_be_clobbered
data1 = TestData.new
data1.stuff = [:a]
data2 = TestData.new
data2.stuff >> :a
data2.stuff << :b
data2.stuff = [:c]
data2.inherit_from!(data1)
assert_equal [:c], data2.stuff.to_a
end
def test_inherited_array_augmentations_after_clobbering
data1 = TestData.new
data1.stuff = [:a]
data2 = TestData.new
data2.stuff >> :a
data2.stuff << :b
data2.stuff = [:c, :d]
data2.stuff << :e
data2.stuff >> :c
data2.inherit_from!(data1)
assert_equal [:d, :e], data2.stuff.to_a
end
def test_serialization_warns_with_asset_cache_buster_set
contents = StringIO.new(<<-CONFIG)
asset_cache_buster do |path|
@ -77,12 +155,36 @@ class ConfigurationTest < Test::Unit::TestCase
Compass.add_configuration(contents, "test_serialization_warns_with_asset_cache_buster_set")
assert_kind_of Proc, Compass.configuration.asset_cache_buster_without_default
assert_equal "http://example.com", Compass.configuration.asset_cache_buster_without_default.call("whatever")
warning = capture_warning do
Compass.configuration.serialize
end
assert_equal "WARNING: asset_cache_buster is code and cannot be written to a file. You'll need to copy it yourself.\n", warning
end
def test_inherited_arrays_augmentations_serialize
inherited = TestData.new
inherited.stuff << :a
d = TestData.new
d.stuff << :b
d.stuff >> :c
assert_equal <<CONFIG, d.serialize_property(:stuff, d.stuff)
stuff << :b
stuff >> :c
CONFIG
end
def test_inherited_arrays_clobbering_with_augmentations_serialize
inherited = TestData.new
inherited.stuff << :a
d = TestData.new
d.stuff << :b
d.stuff = [:c, :d]
d.stuff << :e
assert_equal <<CONFIG, d.serialize_property(:stuff, d.stuff)
stuff = [:c, :d, :e]
CONFIG
end
def test_additional_import_paths
contents = StringIO.new(<<-CONFIG)
http_path = "/"
@ -190,6 +292,36 @@ http_path = \"/\"
# To disable debugging comments that display the original location of your selectors. Uncomment:
# line_comments = false
EXPECTED
assert_correct(expected_serialization, Compass.configuration.serialize)
end
def test_sprite_load_path_clobbers
contents = StringIO.new(<<-CONFIG)
sprite_load_path = ["/Users/chris/Projects/my_compass_project/images/sprites"]
CONFIG
Compass.add_configuration(contents, "test_sass_options")
assert_equal ["/Users/chris/Projects/my_compass_project/images/sprites"], Compass.configuration.sprite_load_path.to_a
expected_serialization = <<EXPECTED
# Require any additional compass plugins here.
# Set this to the root of your project when deployed:
http_path = "/"
# You can select your preferred output style here (can be overridden via the command line):
# output_style = :expanded or :nested or :compact or :compressed
# To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true
# To disable debugging comments that display the original location of your selectors. Uncomment:
# line_comments = false
sprite_load_path = ["/Users/chris/Projects/my_compass_project/images/sprites"]
EXPECTED
assert_correct(expected_serialization, Compass.configuration.serialize)