Refactor of the internal datastructures used to access project

configuration. Configuration is now a singly linked list of configuration objects
that inherit values and defaults from the next configuration instance.
All instances hold a reference to the top of the configuration chain.

There is now a consistent API for reading configuration property values:

                <attr>: Reads the fully-resolved attribute after taking
                        configuration inheritance and defaults into account.
            raw_<attr>: reads attribute from a configuration object without
                        inheritance or defaults.
   default_for(<attr>): reads the default value for an attribute
    default_for_<attr>: specifies the default value for an attribute.
<attr>_without_default: reads the inherited attribute without applying defaults.
    comment_for_<attr>: Specifies a comment that will be emitted above the
                        property when serializing the configuration to a file.

Additionally, method_missing and respond_to both work down the
configuration chain, so any method that is added to a configuration
instance, can be accessed from the top level.

The distinction between default and explicitly set values allows compass
to more correctly manage the serialization of attributes when creating
configuration files for projects.

The compass configuration can still be accessed via
Compass.configuration, however, the configuration object is no longer a
singleton. This means that you can build several configuration chains
to track several projects at once. This should ease the use of compass
in other frameworks and plugins that want to use compass internally.
This commit is contained in:
Chris Eppstein 2009-08-25 14:18:58 -07:00
parent 2cfc9ef1f9
commit f59ca512ce
22 changed files with 732 additions and 379 deletions

View File

@ -6,10 +6,10 @@ module Compass
include Compass::Installers include Compass::Installers
def configure! def configure!
Compass.add_configuration(installer.default_configuration)
read_project_configuration read_project_configuration
Compass.configuration.set_maybe(options) Compass.add_configuration(options)
Compass.configuration.default_all(installer.configuration_defaults) Compass.add_configuration(installer.completed_configuration)
Compass.configuration.set_defaults!
end end
def installer def installer

View File

@ -23,8 +23,6 @@ module Compass
def configure! def configure!
read_project_configuration read_project_configuration
Compass.configuration.set_maybe(options)
Compass.configuration.set_defaults!
end end
def projectize(path) def projectize(path)
@ -49,8 +47,8 @@ module Compass
# Read the configuration file for this project # Read the configuration file for this project
def read_project_configuration def read_project_configuration
if file = detect_configuration_file if (file = detect_configuration_file) && File.readable?(file)
Compass.configuration.parse(file) if File.readable?(file) Compass.add_configuration(file)
end end
end end

View File

@ -1,324 +1,36 @@
require 'singleton'
module Compass module Compass
class Configuration module Configuration
ATTRIBUTES = [ ATTRIBUTES = [
:project_type, :project_type,
:project_path, :project_path,
:http_path,
:css_dir, :css_dir,
:sass_dir, :sass_dir,
:images_dir, :images_dir,
:javascripts_dir, :javascripts_dir,
:output_style, :css_path,
:environment, :sass_path,
:relative_assets, :images_path,
:javascripts_path,
:http_path,
:http_images_dir,
:http_stylesheets_dir,
:http_javascripts_dir,
:http_images_path, :http_images_path,
:http_stylesheets_path, :http_stylesheets_path,
:http_javascripts_path, :http_javascripts_path,
:output_style,
:environment,
:relative_assets,
:additional_import_paths, :additional_import_paths,
:sass_options :sass_options,
:asset_host,
:asset_cache_buster
] ]
attr_accessor *ATTRIBUTES
attr_accessor :required_libraries
def initialize
self.required_libraries = []
end
# parses a manifest file which is a ruby script
# evaluated in a Manifest instance context
def parse(config_file)
open(config_file) do |f|
parse_string(f.read, config_file)
end
end
def parse_string(contents, filename)
bind = binding
eval(contents, bind, filename)
ATTRIBUTES.each do |prop|
value = eval(prop.to_s, bind) rescue nil
self.send("#{prop}=", value) if value
end
if @added_import_paths
self.additional_import_paths ||= []
self.additional_import_paths += @added_import_paths
end
issue_deprecation_warnings
end
def set_all(options)
ATTRIBUTES.each do |a|
self.send("#{a}=", options[a]) if options.has_key?(a)
end
end
def set_maybe(options)
ATTRIBUTES.each do |a|
self.send("#{a}=", options[a]) if options[a]
end
end
def default_all(options)
ATTRIBUTES.each do |a|
set_default_unless_set(a, options[a])
end
end
def set_default_unless_set(attribute, value)
self.send("#{attribute}=", value) unless self.send(attribute)
end
def set_defaults!
ATTRIBUTES.each do |a|
set_default_unless_set(a, default_for(a))
end
end
def default_for(attribute)
method = "default_#{attribute}".to_sym
self.send(method) if respond_to?(method)
end
def default_sass_dir
"src"
end
def default_css_dir
"stylesheets"
end
def default_images_dir
"images"
end
def default_http_path
"/"
end
def comment_for_http_path
"# Set this to the root of your project when deployed:\n"
end
def relative_assets?
# the http_images_path is deprecated, but here for backwards compatibility.
relative_assets || http_images_path == :relative
end
def comment_for_relative_assets
unless relative_assets
%q{# To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true
}
else
""
end
end
def default_output_style
if environment == :development
:expanded
else
:compact
end
end
def default_line_comments
environment == :development
end
def sass_path
if project_path && sass_dir
File.join(project_path, sass_dir)
end
end
def css_path
if project_path && css_dir
File.join(project_path, css_dir)
end
end
def http_root_relative(path)
hp = http_path || default_http_path
hp = hp[0..-2] if hp[-1..-1] == "/"
"#{hp}/#{path}"
end
def add_import_path(*paths)
# The @added_import_paths variable works around an issue where
# the additional_import_paths gets overwritten during parse
@added_import_paths ||= []
@added_import_paths += paths
self.additional_import_paths ||= []
self.additional_import_paths += paths
end
# When called with a block, defines the asset host url to be used.
# The block must return a string that starts with a protocol (E.g. http).
# 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)
if block_given?
@asset_host = block
else
@asset_host
end
end
# When called with a block, defines the cache buster strategy to be used.
# The block must return nil or a string that can be appended to a url as a query parameter.
# The returned string must not include the starting '?'.
# The block will be passed the root-relative url of the asset.
# If the block accepts two arguments, it will also be passed a File object
# that points to the asset on disk -- which may or may not exist.
# When called without a block, returns the block that was previously set.
def asset_cache_buster(&block)
if block_given?
@asset_cache_buster = block
else
@asset_cache_buster
end
end
def serialize
if asset_cache_buster
raise Compass::Error, "Cannot serialize a configuration with asset_cache_buster set."
end
if asset_host
raise Compass::Error, "Cannot serialize a configuration with asset_host set."
end
contents = ""
required_libraries.each do |lib|
contents << %Q{require '#{lib}'\n}
end
contents << "# Require any additional compass plugins here.\n"
contents << "\n" if required_libraries.any?
ATTRIBUTES.each do |prop|
value = send(prop)
if respond_to?("comment_for_#{prop}")
contents << send("comment_for_#{prop}")
end
if block_given? && (to_emit = yield(prop, value))
contents << to_emit
else
contents << Configuration.serialize_property(prop, value) unless value.nil?
end
end
contents
end
def self.serialize_property(prop, value)
%Q(#{prop} = #{value.inspect}\n)
end
def to_compiler_arguments(additional_options)
[project_path, sass_path, css_path, to_sass_engine_options.merge(additional_options)]
end
def to_sass_plugin_options
locations = {}
locations[sass_path] = css_path if sass_path && css_path
Compass::Frameworks::ALL.each do |framework|
locations[framework.stylesheets_directory] = css_path || css_dir || "."
end
resolve_additional_import_paths.each do |additional_path|
locations[additional_path] = File.join(css_path || css_dir || ".", File.basename(additional_path))
end
plugin_opts = {:template_location => locations}
plugin_opts[:style] = output_style if output_style
plugin_opts[:line_comments] = default_line_comments if environment
plugin_opts.merge!(sass_options || {})
plugin_opts
end
def resolve_additional_import_paths
(additional_import_paths || []).map do |path|
if project_path && !absolute_path?(path)
File.join(project_path, path)
else
path
end
end
end
def to_sass_engine_options
engine_opts = {:load_paths => sass_load_paths}
engine_opts[:style] = output_style if output_style
engine_opts[:line_comments] = default_line_comments if environment
engine_opts.merge!(sass_options || {})
end
def sass_load_paths
load_paths = []
load_paths << sass_path if sass_path
Compass::Frameworks::ALL.each do |framework|
load_paths << framework.stylesheets_directory
end
load_paths += resolve_additional_import_paths
load_paths
end
# Support for testing.
def reset!
ATTRIBUTES.each do |attr|
send("#{attr}=", nil)
end
@asset_cache_buster = nil
@asset_host = nil
@added_import_paths = nil
self.required_libraries = []
end
def issue_deprecation_warnings
if http_images_path == :relative
puts "DEPRECATION WARNING: Please set relative_assets = true to enable relative paths."
end
end
def require(lib)
required_libraries << lib
super
end
def absolute_path?(path)
# This is only going to work on unix, gonna need a better implementation.
path.index(File::SEPARATOR) == 0
end
end end
end
module ConfigHelpers
def configuration ['adapters', 'comments', 'defaults', 'helpers', 'inheritance', 'serialization', 'data'].each do |lib|
@configuration ||= Configuration.new require File.join(File.dirname(__FILE__), 'configuration', lib)
if block_given?
yield @configuration
end
@configuration
end
def sass_plugin_configuration
configuration.to_sass_plugin_options
end
def configure_sass_plugin!
@sass_plugin_configured = true
Sass::Plugin.options.merge!(sass_plugin_configuration)
end
def sass_plugin_configured?
@sass_plugin_configured
end
def sass_engine_options
configuration.to_sass_engine_options
end
end
extend ConfigHelpers
end end

View File

@ -0,0 +1,59 @@
module Compass
module Configuration
# The adapters module provides methods that make configuration data from a compass project
# adapt to various consumers of configuration data
module Adapters
def to_compiler_arguments(additional_options)
[project_path, sass_path, css_path, to_sass_engine_options.merge(additional_options)]
end
def to_sass_plugin_options
locations = {}
locations[sass_path] = css_path if sass_path && css_path
Compass::Frameworks::ALL.each do |framework|
locations[framework.stylesheets_directory] = css_path || css_dir || "."
end
resolve_additional_import_paths.each do |additional_path|
locations[additional_path] = File.join(css_path || css_dir || ".", File.basename(additional_path))
end
plugin_opts = {:template_location => locations}
plugin_opts[:style] = output_style if output_style
plugin_opts[:line_comments] = line_comments if environment
plugin_opts.merge!(sass_options || {})
plugin_opts
end
def resolve_additional_import_paths
(additional_import_paths || []).map do |path|
if project_path && !absolute_path?(path)
File.join(project_path, path)
else
path
end
end
end
def absolute_path?(path)
# This is only going to work on unix, gonna need a better implementation.
path.index(File::SEPARATOR) == 0
end
def to_sass_engine_options
engine_opts = {:load_paths => sass_load_paths}
engine_opts[:style] = output_style if output_style
engine_opts[:line_comments] = line_comments if environment
engine_opts.merge!(sass_options || {})
end
def sass_load_paths
load_paths = []
load_paths << sass_path if sass_path
Compass::Frameworks::ALL.each do |framework|
load_paths << framework.stylesheets_directory
end
load_paths += resolve_additional_import_paths
load_paths
end
end
end
end

View File

@ -0,0 +1,22 @@
module Compass
module Configuration
# Comments are emitted into the configuration file when serialized and make it easier to understand for new users.
module Comments
def comment_for_http_path
"# Set this to the root of your project when deployed:\n"
end
def comment_for_relative_assets
unless relative_assets
%q{# To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true
}
else
""
end
end
end
end
end

View File

@ -0,0 +1,103 @@
module Compass
module Configuration
# The Compass configuration data storage class manages configuration data that comes from a variety of
# different sources and aggregates them together into a consistent API
# Some of the possible sources of configuration data:
# * Compass default project structure for stand alone projects
# * App framework specific project structures for rails, etc.
# * User supplied explicit configuration
# * Configuration data provided via the command line interface
#
# There are two kinds of configuration data that doesn't come from the user:
#
# 1. Configuration data that is defaulted as if the user had provided it themselves.
# This is useful for providing defaults that the user is likely to want to edit
# but shouldn't have to provide explicitly when getting started
# 2. Configuration data that is defaulted behind the scenes because _some_ value is
# required.
class Data
attr_accessor :required_libraries
include Compass::Configuration::Inheritance
include Compass::Configuration::Serialization
include Compass::Configuration::Adapters
inherited_accessor *ATTRIBUTES
def initialize(attr_hash = nil)
self.required_libraries = []
set_all(attr_hash) if attr_hash
self.top_level = self
end
def set_all(attr_hash)
# assert_valid_keys!(attr_hash)
attr_hash.each do |a, v|
if self.respond_to?("#{a}=")
self.send("#{a}=", v)
end
end
end
def add_import_path(*paths)
# The @added_import_paths variable works around an issue where
# the additional_import_paths gets overwritten during parse
@added_import_paths ||= []
@added_import_paths += paths
self.additional_import_paths ||= []
self.additional_import_paths += paths
end
# When called with a block, defines the asset host url to be used.
# The block must return a string that starts with a protocol (E.g. http).
# 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)
if block_given?
@asset_host = block
else
@asset_host
end
end
# When called with a block, defines the cache buster strategy to be used.
# The block must return nil or a string that can be appended to a url as a query parameter.
# The returned string must not include the starting '?'.
# The block will be passed the root-relative url of the asset.
# If the block accepts two arguments, it will also be passed a File object
# that points to the asset on disk -- which may or may not exist.
# When called without a block, returns the block that was previously set.
def asset_cache_buster(&block)
if block_given?
@asset_cache_buster = block
else
@asset_cache_buster
end
end
# Require a compass plugin and capture that it occured so that the configuration serialization works next time.
def require(lib)
required_libraries << lib
super
end
def relative_assets?
# the http_images_path is deprecated, but here for backwards compatibility.
relative_assets || http_images_path == :relative
end
private
def assert_valid_keys!(attr_hash)
illegal_attrs = attr_hash.keys - ATTRIBUTES
if illegal_attrs.size == 1
raise Error, "#{illegal_attrs.first.inspect} is not a valid configuration attribute."
elsif illegal_attrs.size > 0
raise Error, "Illegal configuration attributes: #{illegal_attrs.map{|a| a.inspect}.join(", ")}"
end
end
end
end
end

View File

@ -0,0 +1,92 @@
module Compass
module Configuration
module Defaults
def http_path_without_default
"/"
end
def default_output_style
if top_level.environment == :development
:expanded
else
:compact
end
end
def default_line_comments
top_level.environment == :development
end
def default_sass_path
if (pp = top_level.project_path) && (dir = top_level.sass_dir)
File.join(pp, dir)
end
end
def default_css_path
if (pp = top_level.project_path) && (dir = top_level.css_dir)
File.join(pp, dir)
end
end
def default_images_path
if (pp = top_level.project_path) && (dir = top_level.images_dir)
File.join(pp, dir)
end
end
def default_javascripts_path
if (pp = top_level.project_path) && (dir = top_level.javascripts_dir)
File.join(pp, dir)
end
end
def default_http_images_dir
top_level.images_dir
end
def default_http_images_path
http_root_relative top_level.http_images_dir
end
def default_http_stylesheets_dir
top_level.css_dir
end
def default_http_stylesheets_path
http_root_relative top_level.http_stylesheets_dir
end
def default_http_javascripts_dir
top_level.javascripts_dir
end
def default_http_javascripts_path
http_root_relative top_level.http_javascripts_dir
end
# helper functions
def http_join(*segments)
segments.map do |segment|
segment = http_pathify(segment)
segment[-1..-1] == "/" ? segment[0..-2] : segment
end.join("/")
end
def http_pathify(path)
if File::SEPARATOR == "/"
path
else
path.gsub(File::SEPARATOR, "/")
end
end
def http_root_relative(path)
http_join top_level.http_path, path
end
end
end
end

View File

@ -0,0 +1,61 @@
module Compass
module Configuration
# The helpers are available as methods on the Compass module. E.g. Compass.configuration
module Helpers
def configuration
@configuration ||= default_configuration
if block_given?
yield @configuration
end
@configuration
end
def default_configuration
Data.new.extend(Defaults).extend(Comments)
end
def add_configuration(config, filename = nil)
return if config.nil?
data = if config.is_a?(Compass::Configuration::Data)
config
elsif config.respond_to?(:read)
Compass::Configuration::Data.new_from_string(config.read, filename)
elsif config.is_a?(Hash)
Compass::Configuration::Data.new(config)
elsif config.is_a?(String)
Compass::Configuration::Data.new_from_file(config)
else
raise "I don't know what to do with: #{config.inspect}"
end
data.inherit_from!(configuration)
data.on_top!
@configuration = data
end
# Support for testing.
def reset_configuration!
@configuration = nil
end
def sass_plugin_configuration
configuration.to_sass_plugin_options
end
def configure_sass_plugin!
@sass_plugin_configured = true
Sass::Plugin.options.merge!(sass_plugin_configuration)
end
def sass_plugin_configured?
@sass_plugin_configured
end
def sass_engine_options
configuration.to_sass_engine_options
end
end
end
extend Configuration::Helpers
end

View File

@ -0,0 +1,170 @@
module Compass
module Configuration
# The inheritance module makes it easy for configuration data to inherit from
# other instances of configuration data. This makes it easier for external code to layer
# bits of configuration from various sources.
module Inheritance
def self.included(base)
# inherited_data stores configuration data that this configuration object will
# inherit if not provided explicitly.
base.send :attr_accessor, :inherited_data, :set_attributes, :top_level
base.send(:include, InstanceMethods)
base.extend(ClassMethods)
end
module ClassMethods
def inherited_writer(*attributes)
attributes.each do |attribute|
line = __LINE__ + 1
class_eval %Q{
def #{attribute}=(value) # def css_dir=(value)
@set_attributes ||= {} # @set_attributes ||= {}
@set_attributes[#{attribute.inspect}] = true # @set_attributes[:css_dir] = true
@#{attribute} = value # @css_dir = value
end # end
def unset_#{attribute}! # def unset_css_dir!
unset!(#{attribute.inspect}) # unset!(:css_dir)
end # end
def #{attribute}_set? # def css_dir_set?
set?(#{attribute.inspect}) # set?(:css_dir)
end # end
}, __FILE__, line
end
end
# Defines the default reader to be an inherited_reader that will look at the inherited_data for its
# value when not set. The inherited reader calls to a raw reader that acts like a normal attribute
# reader but prefixes the attribute name with "raw_".
def inherited_reader(*attributes)
attributes.each do |attribute|
line = __LINE__ + 1
class_eval %Q{
def raw_#{attribute} # def raw_css_dir
@#{attribute} # @css_dir
end # end
def #{attribute}_without_default # def css_dir_without_default
read_without_default(#{attribute.inspect}) # read_without_default(:css_dir)
end # end
def #{attribute} # def css_dir
read(#{attribute.inspect}) # read(:css_dir)
end # end
}, __FILE__, line
end
end
def inherited_accessor(*attributes)
inherited_reader(*attributes)
inherited_writer(*attributes)
end
end
module InstanceMethods
def on_top!
self.set_top_level(self)
end
def set_top_level(new_top)
self.top_level = new_top
if self.inherited_data.respond_to?(:set_top_level)
self.inherited_data.set_top_level(new_top)
end
end
def inherit_from!(data)
if self.inherited_data
self.inherited_data.inherit_from!(data)
else
self.inherited_data = data
end
self
end
def unset!(attribute)
@set_attributes ||= {}
send("#{attribute}=", nil)
@set_attributes.delete(attribute)
nil
end
def set?(attribute)
@set_attributes ||= {}
@set_attributes[attribute]
end
def default_for(attribute)
method = "default_#{attribute}".to_sym
if respond_to?(method)
send(method)
end
end
# Read an explicitly set value that is either inherited or set on this instance
def read_without_default(attribute)
if set?(attribute)
send("raw_#{attribute}")
elsif inherited_data.respond_to?("#{attribute}_without_default")
inherited_data.send("#{attribute}_without_default")
elsif inherited_data.respond_to?(attribute)
inherited_data.send(attribute)
end
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)
if !(v = send("#{attribute}_without_default")).nil?
v
else
top_level.default_for(attribute)
end
end
def method_missing(meth)
if inherited_data
inherited_data.send(meth)
else
raise NoMethodError, meth.to_s
end
end
def respond_to?(meth)
if super
true
elsif inherited_data
inherited_data.respond_to?(meth)
else
false
end
end
def debug
instances = [self]
instances << instances.last.inherited_data while instances.last.inherited_data
normalized_attrs = {}
ATTRIBUTES.each do |prop|
values = []
instances.each do |instance|
values << {
:raw => (instance.send("raw_#{prop}") rescue nil),
:value => (instance.send("#{prop}_without_default") rescue nil),
:default => (instance.send("default_#{prop}") rescue nil),
:resoved => instance.send(prop)
}
end
normalized_attrs[prop] = values
end
normalized_attrs
end
end
end
end
end

View File

@ -0,0 +1,82 @@
module Compass
module Configuration
# The serialization module manages reading and writing the configuration file(s).
module Serialization
def self.included(base)
base.send(:include, InstanceMethods)
base.extend ClassMethods
end
module ClassMethods
def new_from_file(config_file)
data = Data.new
data.parse(config_file)
data
end
def new_from_string(contents, filename)
data = Data.new
data.parse_string(contents, filename)
data
end
end
module InstanceMethods
# parses a configuration file which is a ruby script
def parse(config_file)
open(config_file) do |f|
parse_string(f.read, config_file)
end
end
def parse_string(contents, filename)
bind = binding
eval(contents, bind, filename)
ATTRIBUTES.each do |prop|
value = eval(prop.to_s, bind) rescue nil
self.send("#{prop}=", value) if value
end
if @added_import_paths
self.additional_import_paths ||= []
self.additional_import_paths += @added_import_paths
end
issue_deprecation_warnings
end
def serialize
contents = ""
required_libraries.each do |lib|
contents << %Q{require '#{lib}'\n}
end
contents << "# Require any additional compass plugins here.\n"
contents << "\n" if required_libraries.any?
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."
end
if respond_to?("comment_for_#{prop}")
contents << send("comment_for_#{prop}")
end
if block_given? && (to_emit = yield(prop, value))
contents << to_emit
else
contents << serialize_property(prop, value) unless value.nil?
end
end
contents
end
def serialize_property(prop, value)
%Q(#{prop} = #{value.inspect}\n)
end
def issue_deprecation_warnings
if http_images_path == :relative
$stderr.puts "DEPRECATION WARNING: Please set relative_assets = true to enable relative paths."
end
end
end
end
end
end

View File

@ -26,12 +26,16 @@ module Compass
define_method dir do define_method dir do
Compass.configuration.send(dir) Compass.configuration.send(dir)
end end
define_method "#{dir}_without_default" do
Compass.configuration.send("#{dir}_without_default")
end
end end
# Initializes the project to work with compass # Initializes the project to work with compass
def init def init
dirs = manifest.map do |entry| dirs = manifest.map do |entry|
File.dirname(send("install_location_for_#{entry.type}", entry.to, entry.options)) loc = send("install_location_for_#{entry.type}", entry.to, entry.options)
File.dirname(loc)
end end
if manifest.has_stylesheet? if manifest.has_stylesheet?

View File

@ -3,16 +3,38 @@ module Compass
class RailsInstaller < Base class RailsInstaller < Base
def configuration_defaults module ConfigurationDefaults
{ def default_images_dir
:sass_dir => (sass_dir || prompt_sass_dir), File.join("public", "images")
:css_dir => (css_dir || prompt_css_dir), end
:images_dir => default_images_dir,
:javascripts_dir => default_javascripts_dir, def default_javascripts_dir
:http_stylesheets_path => default_http_stylesheets_path, File.join("public", "javascripts")
:http_javascripts_path => default_http_javascripts_path, end
:http_images_path => default_http_images_path
} def default_http_images_path
"/images"
end
def default_http_javascripts_path
"/javascripts"
end
def default_http_stylesheets_path
"/stylesheets"
end
end
def default_configuration
Compass::Configuration::Data.new.extend(ConfigurationDefaults)
end
def completed_configuration
config = {}
config[:sass_dir] = prompt_sass_dir unless sass_dir_without_default
config[:css_dir] = prompt_css_dir unless css_dir_without_default
config unless config.empty?
end end
def write_configuration_files(config_file = nil) def write_configuration_files(config_file = nil)
@ -45,25 +67,6 @@ NEXTSTEPS
puts "\n(You are using haml, aren't you?)" puts "\n(You are using haml, aren't you?)"
end end
def default_images_dir
separate("public/images")
end
def default_javascripts_dir
separate("public/javascripts")
end
def default_http_images_path
"/images"
end
def default_http_javascripts_path
"/javascripts"
end
def default_http_stylesheets_path
"/stylesheets"
end
def install_location_for_html(to, options) def install_location_for_html(to, options)
separate("public/#{pattern_name_as_dir}#{to}") separate("public/#{pattern_name_as_dir}#{to}")

View File

@ -3,6 +3,24 @@ module Compass
class StandAloneInstaller < Base class StandAloneInstaller < Base
module ConfigurationDefaults
def sass_dir_without_default
"src"
end
def javascripts_dir_without_default
"javascripts"
end
def css_dir_without_default
"stylesheets"
end
def images_dir_without_default
"images"
end
end
def init def init
directory targetize("") directory targetize("")
super super
@ -28,9 +46,12 @@ module Compass
write_configuration_files unless config_files_exist? write_configuration_files unless config_files_exist?
end end
# We want to rely on the defaults provided by Configuration def default_configuration
def configuration_defaults Compass::Configuration::Data.new.extend(ConfigurationDefaults)
{} end
def completed_configuration
nil
end end
def finalize(options = {}) def finalize(options = {})

View File

@ -88,14 +88,6 @@ module Compass::CommandLineHelper
FileUtils.rm_rf(d) FileUtils.rm_rf(d)
end end
def capture_output
real_stdout, $stdout = $stdout, StringIO.new
yield
$stdout.string
ensure
$stdout = real_stdout
end
def execute(*arguments) def execute(*arguments)
Compass::Exec::Compass.new(arguments).run! Compass::Exec::Compass.new(arguments).run!
end end

View File

@ -7,9 +7,10 @@ require 'timeout'
class CommandLineTest < Test::Unit::TestCase class CommandLineTest < Test::Unit::TestCase
include Compass::TestCaseHelper include Compass::TestCaseHelper
include Compass::CommandLineHelper include Compass::CommandLineHelper
include Compass::IoHelper
def teardown def teardown
Compass.configuration.reset! Compass.reset_configuration!
end end
def test_print_version def test_print_version

View File

@ -5,7 +5,7 @@ require 'compass'
class CompassTest < Test::Unit::TestCase class CompassTest < Test::Unit::TestCase
include Compass::TestCaseHelper include Compass::TestCaseHelper
def setup def setup
Compass.configuration.reset! Compass.reset_configuration!
end end
def teardown def teardown
@ -90,7 +90,7 @@ private
def within_project(project_name) def within_project(project_name)
@current_project = project_name @current_project = project_name
Compass.configuration.parse(configuration_file(project_name)) if File.exists?(configuration_file(project_name)) Compass.add_configuration(configuration_file(project_name)) if File.exists?(configuration_file(project_name))
Compass.configuration.project_path = project_path(project_name) Compass.configuration.project_path = project_path(project_name)
args = Compass.configuration.to_compiler_arguments(:logger => Compass::NullLogger.new) args = Compass.configuration.to_compiler_arguments(:logger => Compass::NullLogger.new)
if Compass.configuration.sass_path && File.exists?(Compass.configuration.sass_path) if Compass.configuration.sass_path && File.exists?(Compass.configuration.sass_path)

View File

@ -1,71 +1,75 @@
require File.dirname(__FILE__)+'/test_helper' require File.dirname(__FILE__)+'/test_helper'
require 'compass' require 'compass'
require 'stringio'
class ConfigurationTest < Test::Unit::TestCase class ConfigurationTest < Test::Unit::TestCase
include Compass::IoHelper
def setup def setup
Compass.configuration.reset! Compass.reset_configuration!
end end
def test_parse_and_serialize def test_parse_and_serialize
contents = <<-CONFIG contents = StringIO.new(<<-CONFIG)
require 'compass' require 'compass'
# Require any additional compass plugins here. # Require any additional compass plugins here.
project_type = :stand_alone project_type = :stand_alone
# Set this to the root of your project when deployed:
http_path = "/"
css_dir = "css" css_dir = "css"
sass_dir = "sass" sass_dir = "sass"
images_dir = "img" images_dir = "img"
javascripts_dir = "js" javascripts_dir = "js"
# Set this to the root of your project when deployed:
http_path = "/"
output_style = :nested output_style = :nested
# To enable relative paths to assets via compass helper functions. Uncomment: # To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true # relative_assets = true
CONFIG CONFIG
Compass.configuration.parse_string(contents, "test_parse") Compass.add_configuration(contents, "test_parse")
assert_equal 'sass', Compass.configuration.sass_dir assert_equal 'sass', Compass.configuration.sass_dir
assert_equal 'css', Compass.configuration.css_dir assert_equal 'css', Compass.configuration.css_dir
assert_equal 'img', Compass.configuration.images_dir assert_equal 'img', Compass.configuration.images_dir
assert_equal 'js', Compass.configuration.javascripts_dir assert_equal 'js', Compass.configuration.javascripts_dir
expected_lines = contents.split("\n").map{|l|l.strip} expected_lines = contents.string.split("\n").map{|l|l.strip}
actual_lines = Compass.configuration.serialize.split("\n").map{|l|l.strip} actual_lines = Compass.configuration.serialize.split("\n").map{|l|l.strip}
assert_equal expected_lines, actual_lines assert_equal expected_lines, actual_lines
end end
def test_serialization_fails_with_asset_host_set def test_serialization_warns_with_asset_host_set
contents = <<-CONFIG contents = StringIO.new(<<-CONFIG)
asset_host do |path| asset_host do |path|
"http://example.com" "http://example.com"
end end
CONFIG CONFIG
Compass.configuration.parse_string(contents, "test_serialization_fails_with_asset_host_set") Compass.add_configuration(contents, "test_serialization_warns_with_asset_host_set")
assert_raise Compass::Error do warning = capture_warning do
Compass.configuration.serialize Compass.configuration.serialize
end end
assert_equal "WARNING: asset_host is code and cannot be written to a file. You'll need to copy it yourself.\n", warning
end end
def test_serialization_fails_with_asset_cache_buster_set def test_serialization_warns_with_asset_cache_buster_set
contents = <<-CONFIG contents = StringIO.new(<<-CONFIG)
asset_cache_buster do |path| asset_cache_buster do |path|
"http://example.com" "http://example.com"
end end
CONFIG CONFIG
Compass.configuration.parse_string(contents, "test_serialization_fails_with_asset_cache_buster_set") Compass.add_configuration(contents, "test_serialization_warns_with_asset_cache_buster_set")
assert_raise Compass::Error do warning = capture_warning do
Compass.configuration.serialize Compass.configuration.serialize
end 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 end
def test_additional_import_paths def test_additional_import_paths
contents = <<-CONFIG contents = StringIO.new(<<-CONFIG)
http_path = "/" http_path = "/"
project_path = "/home/chris/my_compass_project" project_path = "/home/chris/my_compass_project"
css_dir = "css" css_dir = "css"
@ -73,7 +77,7 @@ class ConfigurationTest < Test::Unit::TestCase
add_import_path "/path/to/my/framework" add_import_path "/path/to/my/framework"
CONFIG CONFIG
Compass.configuration.parse_string(contents, "test_additional_import_paths") Compass.add_configuration(contents, "test_additional_import_paths")
assert Compass.configuration.to_sass_engine_options[:load_paths].include?("/home/chris/my_compass_project/../foo") assert Compass.configuration.to_sass_engine_options[:load_paths].include?("/home/chris/my_compass_project/../foo")
assert Compass.configuration.to_sass_engine_options[:load_paths].include?("/path/to/my/framework"), Compass.configuration.to_sass_engine_options[:load_paths].inspect assert Compass.configuration.to_sass_engine_options[:load_paths].include?("/path/to/my/framework"), Compass.configuration.to_sass_engine_options[:load_paths].inspect
@ -83,30 +87,38 @@ class ConfigurationTest < Test::Unit::TestCase
expected_serialization = <<EXPECTED expected_serialization = <<EXPECTED
# Require any additional compass plugins here. # Require any additional compass plugins here.
project_path = "/home/chris/my_compass_project" project_path = "/home/chris/my_compass_project"
css_dir = "css"
sass_dir = "src"
images_dir = "images"
javascripts_dir = "javascripts"
# Set this to the root of your project when deployed: # Set this to the root of your project when deployed:
http_path = "/" http_path = "/"
css_dir = "css"
# To enable relative paths to assets via compass helper functions. Uncomment: # To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true # relative_assets = true
additional_import_paths = ["../foo", "/path/to/my/framework"] additional_import_paths = ["../foo", "/path/to/my/framework"]
EXPECTED EXPECTED
assert_equal "/", Compass.configuration.http_path assert_equal "/", Compass.configuration.http_path
assert_equal expected_serialization, Compass.configuration.serialize assert_equal expected_serialization.split("\n"), Compass.configuration.serialize.split("\n")
end end
def test_sass_options def test_sass_options
contents = <<-CONFIG contents = StringIO.new(<<-CONFIG)
sass_options = {:foo => 'bar'} sass_options = {:foo => 'bar'}
CONFIG CONFIG
Compass.configuration.parse_string(contents, "test_sass_options") Compass.add_configuration(contents, "test_sass_options")
assert_equal 'bar', Compass.configuration.to_sass_engine_options[:foo] assert_equal 'bar', Compass.configuration.to_sass_engine_options[:foo]
assert_equal 'bar', Compass.configuration.to_sass_plugin_options[:foo] assert_equal 'bar', Compass.configuration.to_sass_plugin_options[:foo]
expected_serialization = <<EXPECTED expected_serialization = <<EXPECTED
# Require any additional compass plugins here. # Require any additional compass plugins here.
css_dir = "stylesheets"
sass_dir = "src"
images_dir = "images"
javascripts_dir = "javascripts"
# Set this to the root of your project when deployed: # Set this to the root of your project when deployed:
http_path = "/"
# To enable relative paths to assets via compass helper functions. Uncomment: # To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true # relative_assets = true
sass_options = {:foo=>"bar"} sass_options = {:foo=>"bar"}

19
test/io_helper.rb Normal file
View File

@ -0,0 +1,19 @@
module Compass
module IoHelper
def capture_output
real_stdout, $stdout = $stdout, StringIO.new
yield
$stdout.string
ensure
$stdout = real_stdout
end
def capture_warning
real_stderr, $stderr = $stderr, StringIO.new
yield
$stderr.string
ensure
$stderr = real_stderr
end
end
end

View File

@ -7,9 +7,10 @@ require 'timeout'
class RailsIntegrationTest < Test::Unit::TestCase class RailsIntegrationTest < Test::Unit::TestCase
include Compass::TestCaseHelper include Compass::TestCaseHelper
include Compass::CommandLineHelper include Compass::CommandLineHelper
include Compass::IoHelper
def setup def setup
Compass.configuration.reset! Compass.reset_configuration!
end end
def test_rails_install def test_rails_install

View File

@ -18,4 +18,5 @@ require 'compass'
require 'test/unit' require 'test/unit'
require File.join(File.dirname(__FILE__), 'test_case_helper') require File.join(File.dirname(__FILE__), 'test_case_helper')
require File.join(File.dirname(__FILE__), 'io_helper')
require File.join(File.dirname(__FILE__), 'command_line_helper') require File.join(File.dirname(__FILE__), 'command_line_helper')