diff --git a/cli/test/fixtures/stylesheets/busted_font_urls/css/screen.css b/cli/test/fixtures/stylesheets/busted_font_urls/css/screen.css
index fe08b96758..303caf1a89 100644
--- a/cli/test/fixtures/stylesheets/busted_font_urls/css/screen.css
+++ b/cli/test/fixtures/stylesheets/busted_font_urls/css/screen.css
@@ -1,8 +1,8 @@
-.showgrid { font-family: url('http://assets3.example.com/fonts/grid-BUSTED.ttf'); }
+.showgrid { font-family: url('http://assets2.example.com/fonts/grid-BUSTED.ttf'); }
.no-buster { font-family: url('http://assets3.example.com/fonts/grid.ttf'); }
-.buster-by-default { font-family: url('http://assets3.example.com/fonts/grid-BUSTED.ttf'); }
+.buster-by-default { font-family: url('http://assets2.example.com/fonts/grid-BUSTED.ttf'); }
.feed { font-family: url('http://assets3.example.com/fonts/feed.ttf?query_string'); }
diff --git a/cli/test/fixtures/stylesheets/busted_image_urls/css/screen.css b/cli/test/fixtures/stylesheets/busted_image_urls/css/screen.css
index ab36dbc7dd..3baea04937 100644
--- a/cli/test/fixtures/stylesheets/busted_image_urls/css/screen.css
+++ b/cli/test/fixtures/stylesheets/busted_image_urls/css/screen.css
@@ -1,4 +1,4 @@
-.showgrid { background-image: url('http://assets0.example.com/images/grid-BUSTED.png'); }
+.showgrid { background-image: url('http://assets3.example.com/images/grid-BUSTED.png'); }
.inlinegrid { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAUEAYAAACv1qP4AAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAAZ0lEQVRYw+3QwQ2AIBAFUTEUwI3+uzN7gDscsIgxEuO8An52J11X73OudfxMraXkzHfO3Y98nQEhA0IGhAwIGRAyIGRAyICQASEDQgaEDAgZEDIgZEDIgJABoZzSGK3tPuN9ERFP7Nw4fg+c5g8V1wAAAABJRU5ErkJggg=='); }
diff --git a/cli/test/fixtures/stylesheets/compass/css/images.css b/cli/test/fixtures/stylesheets/compass/css/images.css
index e0c4fdae3f..9d05bfd48a 100644
--- a/cli/test/fixtures/stylesheets/compass/css/images.css
+++ b/cli/test/fixtures/stylesheets/compass/css/images.css
@@ -5,4 +5,4 @@
background-image: url('/images/4x6.png?busted=true'); }
.absolute {
- background-image: url(http://example.com/images/4x6.png); }
+ background-image: url('http://example.com/images/4x6.png'); }
diff --git a/cli/test/fixtures/stylesheets/compass/fonts/font1.eot b/cli/test/fixtures/stylesheets/compass/fonts/font1.eot
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/cli/test/fixtures/stylesheets/compass/fonts/font1.woff b/cli/test/fixtures/stylesheets/compass/fonts/font1.woff
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/cli/test/fixtures/stylesheets/image_urls/config.rb b/cli/test/fixtures/stylesheets/image_urls/config.rb
index bcdae437e3..a14b89f388 100644
--- a/cli/test/fixtures/stylesheets/image_urls/config.rb
+++ b/cli/test/fixtures/stylesheets/image_urls/config.rb
@@ -17,3 +17,10 @@
asset_host do |path|
"http://assets%d.example.com" % (path.size % 4)
end
+
+add_asset_collection(
+ :root_dir => "other/asset_collection",
+ :http_dir => "ext-assets",
+ :images_dir => "img",
+ :sass_dir => "scss"
+)
diff --git a/cli/test/fixtures/stylesheets/image_urls/css/screen.css b/cli/test/fixtures/stylesheets/image_urls/css/screen.css
index 0c01a39183..aa17280efe 100644
--- a/cli/test/fixtures/stylesheets/image_urls/css/screen.css
+++ b/cli/test/fixtures/stylesheets/image_urls/css/screen.css
@@ -1,3 +1,5 @@
+.imported-from-asset-collection { color: red; }
+
.showgrid { background-image: url('http://assets0.example.com/images/grid.png?busted=true'); }
.inlinegrid { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAUEAYAAACv1qP4AAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAAZ0lEQVRYw+3QwQ2AIBAFUTEUwI3+uzN7gDscsIgxEuO8An52J11X73OudfxMraXkzHfO3Y98nQEhA0IGhAwIGRAyIGRAyICQASEDQgaEDAgZEDIgZEDIgJABoZzSGK3tPuN9ERFP7Nw4fg+c5g8V1wAAAABJRU5ErkJggg=='); }
diff --git a/cli/test/fixtures/stylesheets/image_urls/other/asset_collection/img/ac-img-1.jpg b/cli/test/fixtures/stylesheets/image_urls/other/asset_collection/img/ac-img-1.jpg
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/cli/test/fixtures/stylesheets/image_urls/other/asset_collection/scss/_ac-stylesheet.scss b/cli/test/fixtures/stylesheets/image_urls/other/asset_collection/scss/_ac-stylesheet.scss
new file mode 100644
index 0000000000..2110040da1
--- /dev/null
+++ b/cli/test/fixtures/stylesheets/image_urls/other/asset_collection/scss/_ac-stylesheet.scss
@@ -0,0 +1 @@
+.imported-from-asset-collection { color: red; }
\ No newline at end of file
diff --git a/cli/test/fixtures/stylesheets/image_urls/sass/screen.sass b/cli/test/fixtures/stylesheets/image_urls/sass/screen.sass
index e16697bcbf..69ee919174 100644
--- a/cli/test/fixtures/stylesheets/image_urls/sass/screen.sass
+++ b/cli/test/fixtures/stylesheets/image_urls/sass/screen.sass
@@ -1,3 +1,5 @@
+@import "ac-stylesheet"
+
.showgrid
background-image: image-url(unquote("grid.png"))
diff --git a/cli/test/units/configuration_test.rb b/cli/test/units/configuration_test.rb
index 7192b26ed7..37a156f79e 100644
--- a/cli/test/units/configuration_test.rb
+++ b/cli/test/units/configuration_test.rb
@@ -206,24 +206,30 @@ def test_serialization_warns_with_asset_cache_buster_set
end
def test_cache_buster_file_not_passed_when_the_file_does_not_exist
- config = Compass::Configuration::Data.new("test_cache_buster_file_not_passed_when_the_file_does_not_exist")
- the_file = nil
- was_called = nil
- config.asset_cache_buster do |path, file|
- was_called = true
- the_file = file
- "busted=true"
- end
+ FileUtils.mkdir_p("images")
+ begin
+ config = Compass::Configuration::Data.new("test_cache_buster_file_not_passed_when_the_file_does_not_exist")
+ open("images/asdf.gif", "w") {|f| }
+ the_file = nil
+ was_called = nil
+ config.asset_cache_buster do |path, file|
+ was_called = true
+ the_file = file
+ "busted=true"
+ end
- Compass.add_configuration(config)
+ Compass.add_configuration(config)
- sass = Sass::Engine.new(<<-SCSS, Compass.configuration.to_sass_engine_options.merge(:syntax => :scss))
- .foo { background: image-url("asdf.gif") }
- SCSS
- sass.render
- assert was_called
- assert_nil the_file
+ sass = Sass::Engine.new(<<-SCSS, Compass.configuration.to_sass_engine_options.merge(:syntax => :scss))
+ .foo { background: image-url("asdf.gif") }
+ SCSS
+ sass.render
+ assert was_called
+ assert the_file.closed?
+ ensure
+ FileUtils.rm_r("images")
+ end
end
def test_cache_buster_file_is_closed
diff --git a/cli/test/units/sass_extensions_test.rb b/cli/test/units/sass_extensions_test.rb
index e6ae94ae8c..cba1a7a00b 100644
--- a/cli/test/units/sass_extensions_test.rb
+++ b/cli/test/units/sass_extensions_test.rb
@@ -136,18 +136,18 @@ def test_css2_fallback
def test_font_files
assert_equal '', evaluate('font_files()')
- assert_equal "url(/font/name.woff) format('woff'), url(/font/name.woff2) format('woff2'), url(/fonts/name.ttf) format('truetype'), url(/fonts/name.svg#fontpath) format('svg')", evaluate("font-files('/font/name.woff', woff, '/font/name.woff2', woff2, '/fonts/name.ttf', truetype, '/fonts/name.svg#fontpath', svg)")
+ assert_equal "url('/font/name.woff') format('woff'), url('/font/name.woff2') format('woff2'), url('/fonts/name.ttf') format('truetype'), url('/fonts/name.svg#fontpath') format('svg')", evaluate("font-files('/font/name.woff', woff, '/font/name.woff2', woff2, '/fonts/name.ttf', truetype, '/fonts/name.svg#fontpath', svg)")
- assert_equal "url(/font/with/right_ext.woff) format('woff')", evaluate("font_files('/font/with/right_ext.woff')")
- assert_equal "url(/font/with/wrong_ext.woff) format('svg')", evaluate("font_files('/font/with/wrong_ext.woff', 'svg')")
- assert_equal "url(/font/with/no_ext) format('opentype')", evaluate("font_files('/font/with/no_ext', 'otf')")
- assert_equal "url(/font/with/weird.ext) format('truetype')", evaluate("font_files('/font/with/weird.ext', 'ttf')")
+ assert_equal "url('/font/with/right_ext.woff') format('woff')", evaluate("font_files('/font/with/right_ext.woff')")
+ assert_equal "url('/font/with/wrong_ext.woff') format('svg')", evaluate("font_files('/font/with/wrong_ext.woff', 'svg')")
+ assert_equal "url('/font/with/no_ext') format('opentype')", evaluate("font_files('/font/with/no_ext', 'otf')")
+ assert_equal "url('/font/with/weird.ext') format('truetype')", evaluate("font_files('/font/with/weird.ext', 'ttf')")
# unquoted path strings used to break because of a regex test
- assert_equal "url(/font/with/right_ext.woff) format('woff')", evaluate("font_files(unquote('/font/with/right_ext.woff'))")
+ assert_equal "url('/font/with/right_ext.woff') format('woff')", evaluate("font_files(unquote('/font/with/right_ext.woff'))")
- assert_equal "url(/font/with/right_ext.woff) format('woff'), url(/font/with/right_ext_also.otf) format('opentype')", evaluate("font_files('/font/with/right_ext.woff', '/font/with/right_ext_also.otf')")
- assert_equal "url(/font/with/wrong_ext.woff) format('truetype'), url(/font/with/right_ext.otf) format('opentype')", evaluate("font_files('/font/with/wrong_ext.woff', 'ttf', '/font/with/right_ext.otf')")
+ assert_equal "url('/font/with/right_ext.woff') format('woff'), url('/font/with/right_ext_also.otf') format('opentype')", evaluate("font_files('/font/with/right_ext.woff', '/font/with/right_ext_also.otf')")
+ assert_equal "url('/font/with/wrong_ext.woff') format('truetype'), url('/font/with/right_ext.otf') format('opentype')", evaluate("font_files('/font/with/wrong_ext.woff', 'ttf', '/font/with/right_ext.otf')")
assert_nothing_raised Sass::SyntaxError do
evaluate("font-files('/font/name.woff')")
diff --git a/compass-style.org/content/CHANGELOG.markdown b/compass-style.org/content/CHANGELOG.markdown
index 4a7e2610ad..e94598e1bb 100644
--- a/compass-style.org/content/CHANGELOG.markdown
+++ b/compass-style.org/content/CHANGELOG.markdown
@@ -16,6 +16,24 @@ The Documentation for the [latest preview release](http://beta.compass-style.org
Every release contains updated caniuse data unless otherwise noted.
+1.1.0 (UNRELEASED)
+------------------
+
+* Asset Collections - Each asset collection is a bundle of sass stylesheets,
+ images, and fonts that potentially have their own URL location, cache
+ busting, and host requirements. Unlike compass extensions, asset
+ collections don't require the publisher to package their assets
+ in any particular way and the image and fonts don't need to be bundled
+ or delivered as part of your projects's assets. This makes asset
+ collections ideal for integrating with drupal extensions, bower, and
+ other front-end packagers.
+
+ To add an asset collection to your project, call `add_asset_collection`
+ and pass the asset collection configuration options to describe where
+ to find the assets and how the urls for them are constructed. [Asset
+ Collection Documentation](/help/documentation/configuration-reference/#asset-collections).
+
+
1.0.1 (08/19/2014)
------------------
diff --git a/compass-style.org/content/help/documentation/configuration-reference.markdown b/compass-style.org/content/help/documentation/configuration-reference.markdown
index 3b83f688b6..f4fe540e9c 100644
--- a/compass-style.org/content/help/documentation/configuration-reference.markdown
+++ b/compass-style.org/content/help/documentation/configuration-reference.markdown
@@ -389,6 +389,68 @@ To disable the asset cache buster:
---
+
+**`add_asset_collection`** - Each asset collection is a bundle of sass stylesheets,
+images, and fonts that potentially have their own URL location, cache
+busting, and host requirements. Unlike compass extensions, asset
+collections don't require the publisher to package their assets
+in any particular way and the image and fonts don't need to be bundled
+or delivered as part of your projects's assets. This makes asset
+collections ideal for integrating with drupal extensions, bower, and
+other front-end packagers.
+
+To add an asset collection to your project, call `add_asset_collection`
+and pass the asset collection configuration options to describe where
+to find the assets and how the urls for them are constructed. The
+following options are allowed:
+
+
+* `:root_path` - The absolute path to the asset collection.
+* `:root_dir` - A relative path to the asset collection from the project path.
+* `:http_path` - Web root location of assets in this collection.
+ Starts with '/'.
+* `:http_dir` - Web root location of assets in this collection.
+ Relative to the project's `http_path`.
+* `:sass_dir` - Sass directory to be added to the Sass import paths.
+ Relative to the `:root_path` or `:root_dir.`
+* `:fonts_dir` - Directory of fonts to be added to the font look up path.
+ Relative to the `:root_path` or `:root_dir.`
+* `:http_fonts_dir` - Where to find fonts on the webserver relative to
+ the `http_path` or `http_dir.` Defaults to `/`.
+ Can be overridden by setting `:http_fonts_path`.
+* `:http_fonts_path` - Where to find fonts on the webserver.
+* `:images_dir` - Directory of images to be added to the image look up path.
+ Relative to the `:root_path` or `:root_dir.`
+* `:http_images_dir` - Where to find images on the webserver relative to
+ the `http_path` or `http_dir.` Defaults to `/`.
+ Can be overridden by setting `:http_images_path`.
+* `:http_images_path` - Where to find images on the webserver.
+* `:asset_host` - A string starting with `'http://'` for a single host,
+ or a lambda or proc that will compute the asset host for assets in this collection.
+ If `:http_dir` is set instead of `http_path,` this defaults to the project's `asset_host`.
+* `:asset_cache_buster` - A string, `:none`, or
+ or a lambda or proc that will compute the `cache_buster` for assets in this collection.
+ If `:http_dir` is set instead of `http_path,` this defaults to the project's `asset_cache_buster`.
+
+It is required to pass either `:root_path` or `:root_dir` and
+`:http_path` or `:http_dir`.
+
+Example:
+
+ add_asset_collection(
+ :root_dir => "other/asset_collection",
+ :http_dir => "ext-assets",
+ :images_dir => "img",
+ :sass_dir => "scss"
+ :asset_cache_buster => proc {|url_path, file|
+ {:path => "/#{Digest::MD5.file(file)}#{File.extname(url_path)}"} },
+ :asset_host => proc {|url_path|
+ "http://assets#{url_path.hash % 4 + 1}.other-asset-server.com" }
+ )
+
+
+---
+
**`watch`** -- React to changes to arbitrary files within your project. Can be invoked
more than once. Example:
diff --git a/compass-style.org/content/help/documentation/sass-based-configuration-options.markdown b/compass-style.org/content/help/documentation/sass-based-configuration-options.markdown
index e56bf67212..09ca7e825c 100644
--- a/compass-style.org/content/help/documentation/sass-based-configuration-options.markdown
+++ b/compass-style.org/content/help/documentation/sass-based-configuration-options.markdown
@@ -22,6 +22,14 @@ The options that can be set via Sass configuration are:
containing the keys query and/or path mapped to a string. The string
is a simple value to set as the query parameter on all urls, when
`null`, the cache busting feature is disabled.
+* `asset-collections` - List of Maps. Each map is a set of keys
+ that describe an asset collection. The keys are basically the same
+ as for the [ruby configuration of asset
+ collections](/help/documentation/configuration-reference/#asset-collections)
+ with the following exceptions: 1. keys can be dashed instead of
+ underscored. 2. Procs should be function references as described here
+ for `asset-cache-buster` and `asset-host`. 3. The `sass-dir` option
+ doesn't work because it must be set up external to the sass file itself.
* `asset-host` - Function reference, or `null`. When `null` this feature
is disabled (default). A referenced function must accept a single
argument (the root relative url) and return a full url (starting with
diff --git a/core/lib/compass/configuration.rb b/core/lib/compass/configuration.rb
index 0f31283b71..8d97b03e42 100644
--- a/core/lib/compass/configuration.rb
+++ b/core/lib/compass/configuration.rb
@@ -85,7 +85,8 @@ def remove_configuration_property(name)
:sprite_load_path,
:required_libraries,
:loaded_frameworks,
- :framework_path
+ :framework_path,
+ :asset_collections
]
ARRAY_ATTRIBUTE_OPTIONS = {
@@ -93,6 +94,7 @@ def remove_configuration_property(name)
}
RUNTIME_READONLY_ATTRIBUTES = [
+ :additional_import_paths,
:cache,
attributes_for_directory(:cache, nil),
:chunky_png_options,
@@ -170,6 +172,6 @@ def deprojectize(path, project_path = nil)
end
end
-%w(defaults inheritance paths data watch adapters).each do |lib|
+%w(defaults inheritance paths data watch adapters asset_collection).each do |lib|
require "compass/configuration/#{lib}"
end
diff --git a/core/lib/compass/configuration/adapters.rb b/core/lib/compass/configuration/adapters.rb
index 301fc4a965..af8ae6bef8 100644
--- a/core/lib/compass/configuration/adapters.rb
+++ b/core/lib/compass/configuration/adapters.rb
@@ -51,7 +51,10 @@ def resolve_additional_import_paths
else
path
end
- end
+ end +
+ (asset_collections || []).map do |asset_collection|
+ asset_collection.sass_path
+ end.compact
end
def absolute_path?(path)
diff --git a/core/lib/compass/configuration/asset_collection.rb b/core/lib/compass/configuration/asset_collection.rb
new file mode 100644
index 0000000000..4332612ced
--- /dev/null
+++ b/core/lib/compass/configuration/asset_collection.rb
@@ -0,0 +1,260 @@
+class Compass::Configuration::AbstractAssetCollection
+ include Compass::Core::HTTPUtil
+
+ attr_writer :configuration
+
+ def configuration
+ @configuration || Compass.configuration
+ end
+
+ def as_filesystem_path(url_path)
+ if File::SEPARATOR == '/'
+ url_path
+ else
+ relative_path = url_path.gsub(%r{/}, File::SEPARATOR)
+ end
+ end
+
+ def includes_image?(relative_path)
+ # Treat root relative urls (without a protocol) like normal if they start with the images path.
+ if relative_path.start_with?("#{http_images_path}/")
+ relative_path = relative_path[(http_images_path.size + 1)..-1]
+ end
+ fs_relative_path = as_filesystem_path(relative_path)
+ absolute_path = File.join(images_path, fs_relative_path)
+ if File.exists?(absolute_path)
+ [relative_path, absolute_path]
+ else
+ nil
+ end
+ end
+
+ def includes_font?(relative_path)
+ # Treat root relative urls (without a protocol) like normal if they start with the fonts path.
+ if relative_path.start_with?("#{http_fonts_path}/")
+ relative_path = relative_path[(http_fonts_path.size + 1)..-1]
+ end
+ fs_relative_path = as_filesystem_path(relative_path)
+ absolute_path = File.join(fonts_path, fs_relative_path)
+ if File.exists?(absolute_path)
+ [relative_path, absolute_path]
+ end
+ end
+
+ def includes_generated_image?(relative_path)
+ nil
+ end
+
+ def root_path
+ Sass::Util.abstract!
+ end
+
+ def http_path
+ Sass::Util.abstract!
+ end
+
+ def sass_path
+ Sass::Util.abstract!
+ end
+
+ def images_path
+ Sass::Util.abstract!
+ end
+
+ def http_images_path
+ Sass::Util.abstract!
+ end
+
+ def fonts_path
+ Sass::Util.abstract!
+ end
+
+ def http_fonts_path
+ Sass::Util.abstract!
+ end
+
+ def asset_host
+ Sass::Util.abstract!
+ end
+
+ def asset_cache_buster
+ Sass::Util.abstract!
+ end
+end
+
+class Compass::Configuration::AssetCollection < Compass::Configuration::AbstractAssetCollection
+ include Compass::Util
+
+ attr_reader :options
+
+ # @param options The paths to the asset collection.
+ # @option :root_path The absolute path to the asset collection
+ # @option :root_dir A relative path to the asset collection from the project path.
+ # @option :http_path Web root location of assets in this collection.
+ # Starts with '/'.
+ # @option :http_dir Web root location of assets in this collection.
+ # Relative to the project's `http_path`.
+ # @option :sass_dir Sass directory to be added to the Sass import paths.
+ # Relative to the :root_path or :root_dir. Defaults to `sass`.
+ # @option :fonts_dir Directory of fonts to be added to the font look up path.
+ # Relative to the :root_path or :root_dir. Defaults to `fonts`.
+ # @option :http_fonts_dir Where to find fonts on the webserver relative to
+ # the http_path or http_dir. Defaults to /.
+ # Can be overridden by setting :http_fonts_path.
+ # @option :http_fonts_path Where to find fonts on the webserver.
+ # @option :images_dir Directory of images to be added to the image look up path.
+ # Relative to the :root_path or :root_dir. Defaults to `images`.
+ # @option :http_images_dir Where to find images on the webserver relative to
+ # the http_path or http_dir. Defaults to /.
+ # Can be overridden by setting :http_images_path.
+ # @option :http_images_path Where to find images on the webserver.
+ # @option :asset_host A string starting with 'http://' for a single host,
+ # or a lambda or proc that will compute the asset host for assets in this collection.
+ # If :http_dir is set instead of http_path, this defaults to the project's asset_host.
+ # @option :asset_cache_buster A string, :none, or
+ # or a lambda or proc that will compute the cache_buster for assets in this collection.
+ # If :http_dir is set instead of http_path, this defaults to the project's asset_cache_buster.
+ def initialize(options, configuration = nil)
+ assert_valid_keys(options, :root_path, :root_dir, :http_path, :http_dir, :sass_dir,
+ :fonts_dir, :http_fonts_dir, :http_fonts_path,
+ :images_dir, :http_images_dir, :http_images_path,
+ :asset_host, :asset_cache_buster)
+ symbolize_keys!(options)
+ unless options.has_key?(:root_path) || options.has_key?(:root_dir)
+ raise ArgumentError, "Either :root_path or :root_dir must be specified."
+ end
+ unless options.has_key?(:http_path) || options.has_key?(:http_dir)
+ raise ArgumentError, "Either :http_path or :http_dir must be specified."
+ end
+ @options = options
+ @configuration = configuration
+ end
+
+ def root_path
+ return @root_path if defined?(@root_path)
+ @root_path = @options[:root_path] || File.join(configuration.project_path, @options[:root_dir])
+ end
+
+ def http_path
+ return @http_path if defined?(@http_path)
+ @http_path = @options[:http_path] || url_join(configuration.http_path, @options[:http_dir])
+ end
+
+ def sass_path
+ return @sass_path if defined?(@sass_path)
+ @sass_path = if options[:sass_dir]
+ File.join(root_path, options[:sass_dir])
+ end
+ end
+
+ def images_path
+ return @images_path if defined?(@images_path)
+ @images_path = if options[:images_dir]
+ File.join(root_path, options[:images_dir])
+ end
+ end
+
+ def http_images_path
+ return @http_images_path if defined?(@http_images_path)
+ @http_images_path = if options[:http_images_path]
+ options[:http_images_path]
+ elsif options[:http_images_dir]
+ url_join(http_path, options[:http_images_dir])
+ elsif options[:images_dir]
+ url_join(http_path, options[:images_dir])
+ end
+ end
+
+
+ def fonts_path
+ return @fonts_path if defined?(@fonts_path)
+ @fonts_path = if options[:fonts_dir]
+ File.join(root_path, options[:fonts_dir])
+ end
+ end
+
+ def http_fonts_path
+ return @http_fonts_path if defined?(@http_fonts_path)
+ @http_fonts_path = if options[:http_fonts_path]
+ options[:http_fonts_path]
+ elsif options[:http_fonts_dir]
+ url_join(http_path, options[:http_fonts_dir])
+ elsif options[:fonts_dir]
+ url_join(http_path, options[:fonts_dir])
+ end
+ end
+
+ def asset_host
+ return options[:asset_host] if options.has_key?(:asset_host)
+ if options[:http_dir]
+ configuration.asset_host
+ end
+ end
+
+ def asset_cache_buster
+ return options[:asset_cache_buster] if options.has_key?(:asset_cache_buster)
+ if options[:http_dir]
+ configuration.asset_cache_buster
+ end
+ end
+end
+
+class Compass::Configuration::DefaultAssetCollection < Compass::Configuration::AbstractAssetCollection
+ def includes_generated_image?(relative_path)
+ # Treat root relative urls (without a protocol) like normal if they start with the images path.
+ if relative_path.start_with?("#{http_generated_images_path}/")
+ relative_path = relative_path[(http_generated_images_path.size + 1)..-1]
+ end
+ fs_relative_path = as_filesystem_path(relative_path)
+ absolute_path = File.join(generated_images_path, fs_relative_path)
+ if File.exists?(absolute_path)
+ [relative_path, absolute_path]
+ else
+ nil
+ end
+ end
+
+ def root_path
+ configuration.project_path
+ end
+
+ def http_path
+ configuration.http_path
+ end
+
+ def sass_path
+ configuration.sass_path
+ end
+
+ def images_path
+ configuration.images_path
+ end
+
+ def http_images_path
+ configuration.http_images_path
+ end
+
+ def generated_images_path
+ configuration.generated_images_path
+ end
+
+ def http_generated_images_path
+ configuration.http_generated_images_path
+ end
+
+ def fonts_path
+ configuration.fonts_path
+ end
+
+ def http_fonts_path
+ configuration.http_fonts_path
+ end
+
+ def asset_host
+ configuration.asset_host
+ end
+
+ def asset_cache_buster
+ configuration.asset_cache_buster
+ end
+end
diff --git a/core/lib/compass/configuration/data.rb b/core/lib/compass/configuration/data.rb
index 54125cb9ee..de94af0b5e 100644
--- a/core/lib/compass/configuration/data.rb
+++ b/core/lib/compass/configuration/data.rb
@@ -81,7 +81,7 @@ class Data
end
def initialize(name, attr_hash = nil)
- raise "I need a name!" unless name
+ raise "I need a name!" unless name && (name.is_a?(String) || name.is_a?(Symbol))
@name = name
set_all(attr_hash) if attr_hash
self.top_level = self
@@ -95,6 +95,14 @@ def set_all(attr_hash)
end
end
+ alias http_path_without_error= http_path=
+ def http_path=(path)
+ if path == :relative
+ raise ArgumentError, ":relative is no longer a valid value for http_path. Please set relative_assets = true instead."
+ end
+ self.http_path_without_error = path
+ end
+
def add_import_path(*paths)
paths.map!{|p| defined?(Pathname) && Pathname === p ? p.to_s : p}
# The @added_import_paths variable works around an issue where
@@ -106,6 +114,15 @@ def add_import_path(*paths)
end
end
+
+ # Add a location where sass, image, and font assets can be found.
+ # @see AssetCollection#initialize for options
+ def add_asset_collection(options)
+ @url_resolver = nil
+ @asset_collections ||= []
+ @asset_collections << AssetCollection.new(options)
+ 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.
@@ -192,7 +209,15 @@ def discover(frameworks_dir)
def relative_assets?
# the http_images_path is deprecated, but here for backwards compatibility.
- relative_assets || http_images_path == :relative
+ relative_assets
+ end
+
+ def url_resolver
+ if top_level == self
+ @url_resolver = Compass::Core::AssetUrlResolver.new(asset_collections.to_a, self)
+ else
+ top_level.url_resolver
+ end
end
end
end
diff --git a/core/lib/compass/core.rb b/core/lib/compass/core.rb
index 242c0b6771..f5ad900df0 100644
--- a/core/lib/compass/core.rb
+++ b/core/lib/compass/core.rb
@@ -65,6 +65,8 @@ def shared_extension_paths
require 'compass/util'
require "compass/frameworks"
require "compass/core/caniuse"
+require "compass/core/http_util"
+require "compass/core/asset_url_resolver"
require 'compass/core/sass_extensions'
require 'compass/error'
require 'compass/browser_support'
diff --git a/core/lib/compass/core/asset_url_resolver.rb b/core/lib/compass/core/asset_url_resolver.rb
new file mode 100644
index 0000000000..fd9a73bdf9
--- /dev/null
+++ b/core/lib/compass/core/asset_url_resolver.rb
@@ -0,0 +1,130 @@
+class Compass::Core::AssetUrlResolver
+ include Compass::Core::HTTPUtil
+
+ class AssetNotFound < Sass::SyntaxError
+ def initialize(type, path, asset_collections)
+ asset_search_paths = asset_collections.map{|ac| ac.send(:"#{type}s_path") }
+ super("Could not find #{path} in #{ asset_search_paths.join(", ")}")
+ end
+ end
+
+ attr_accessor :asset_collections
+ attr_accessor :configuration
+
+ def initialize(asset_collections, configuration = nil)
+ @configuration = configuration || Compass.configuration
+ @asset_collections = asset_collections.dup
+ unless @asset_collections.find {|ac| Compass::Configuration::DefaultAssetCollection === ac }
+ @asset_collections.unshift(Compass::Configuration::DefaultAssetCollection.new)
+ end
+ if configuration
+ @asset_collections.each {|ac| ac.configuration = configuration }
+ end
+ end
+
+ # Compute a url for a given asset type (:image or :font)
+ def compute_url(type, relative_path, relative_to_css_url = nil, use_cache_buster = true)
+ # pass through fully specified urls
+ return relative_path if relative_path.start_with?("http://")
+
+ # If the image has an target reference, remove it (Common with SVG)
+ clean_relative_path, target = relative_path.split("#", 2)
+
+ # If the image has a query, remove it
+ clean_relative_path, query = clean_relative_path.split("?", 2)
+
+ # Get rid of silliness in the url
+ clean_relative_path = expand_url_path(clean_relative_path)
+
+ # Find the asset collection that includes this asset
+ asset_collection, clean_relative_path, real_path = find_collection(type, clean_relative_path)
+
+ # Didn't find the asset, but it's a full url so just return it.
+ return relative_path if asset_collection.nil? && relative_path.start_with?("/")
+
+ # Raise an error for relative paths if we didn't find it on the search path.
+ raise AssetNotFound.new(type, relative_path, @asset_collections) unless asset_collection
+
+ # Make a root-relative url (starting with /)
+ asset_url = url_join(asset_collection.send(:"http_#{type}s_path"), clean_relative_path)
+
+ # Compute asset cache buster
+ busted_path, busted_query = cache_buster(asset_collection, asset_url, real_path) if use_cache_buster
+ asset_url = busted_path if busted_path
+ query = [query, busted_query].compact.join("&") if busted_query
+
+ # Convert path to a relative url if a css file is specified.
+ relative_url = compute_relative_path(relative_to_css_url, asset_url) if relative_to_css_url
+
+ # Compute asset host when not relative and one is provided
+ asset_host = if asset_collection.asset_host && relative_url.nil?
+ asset_collection.asset_host.call(asset_url)
+ end
+
+ # build the full url
+ url = url_join(asset_host || "", relative_url || asset_url)
+ url << "?" if query
+ url << query if query
+ url << "#" if target
+ url << target if target
+ return url
+ end
+
+ protected
+
+ def find_collection(type, relative_path)
+ asset_collection = nil
+ clean_relative_path = nil
+ absolute_path = nil
+ @asset_collections.each do |ac|
+ cp, ap = ac.send(:"includes_#{type}?", relative_path)
+ if ap
+ asset_collection = ac
+ absolute_path = ap
+ clean_relative_path = cp
+ break
+ end
+ end
+ [asset_collection, clean_relative_path, absolute_path]
+ end
+
+ def absolute_path?(path)
+ path[0..0] == "/" || path[0..3] == "http"
+ end
+
+ def cache_buster(asset_collection, path, real_path)
+ cache_buster = compute_cache_buster(asset_collection, path, real_path)
+ return [path, nil] if cache_buster.nil?
+ cache_buster = {:query => cache_buster} if cache_buster.is_a?(String)
+ [cache_buster[:path] || path, cache_buster[:query]]
+ end
+
+
+ def compute_cache_buster(asset_collection, path, real_path)
+ file = nil
+ if asset_collection.asset_cache_buster == :none
+ nil
+ elsif asset_collection.asset_cache_buster
+ args = [path]
+ if asset_collection.asset_cache_buster.arity > 1
+ file = File.new(real_path)
+ args << file
+ end
+ asset_collection.asset_cache_buster.call(*args)
+ else
+ default_cache_buster(path, real_path)
+ end
+ ensure
+ file.close if file
+ end
+
+ def default_cache_buster(path, real_path)
+ if File.readable?(real_path)
+ File.mtime(real_path).to_i.to_s
+ else
+ Compass::Util.compass_warn("WARNING: '#{real_path}' cannot be read.")
+ nil
+ end
+ end
+
+end
diff --git a/core/lib/compass/core/http_util.rb b/core/lib/compass/core/http_util.rb
new file mode 100644
index 0000000000..3ea14f5fd3
--- /dev/null
+++ b/core/lib/compass/core/http_util.rb
@@ -0,0 +1,57 @@
+module Compass::Core::HTTPUtil
+ # like File#join, but always uses '/' instead of File::SEPARATOR
+ def url_join(*args)
+ args.inject("") do |m, a|
+ m << "/" unless m.end_with?('/') || a.start_with?('/') if m.length > 0
+ m.gsub!(%r{/+$}, '') if a.start_with?('/')
+ m << a
+ end
+ end
+
+ # Eliminates parent directory references (E.g. "..") and
+ # self directory references references (E.g. ".") from urls.
+ # Removes any duplicated separators (E.g. //)
+ # path should not include the protol, host, query param or target.
+ def expand_url_path(url)
+ # We remove the leading path otherwise we think there's an extra segment that can be removed
+ prefix = "/" if url.start_with?("/")
+ segments = url.gsub(%r{//+}, '/').split("/")
+ segments.shift if prefix
+ segments.push("") if url.end_with?("/")
+ segments.reverse!
+ result_segments = []
+ parent_count = 0
+ segments.each do |segment|
+ if segment == ".."
+ parent_count += 1
+ elsif segment == "."
+ # skip it
+ elsif parent_count > 0
+ parent_count -= 1
+ else
+ result_segments << segment
+ end
+ end
+ if parent_count > 0
+ raise ArgumentError, "Invalid URL: #{url} (not enough parent directories)"
+ end
+ result_segments.reverse!
+ prefix.to_s + result_segments.join("/")
+ end
+
+ # Compute a relative path from one url to another
+ # the urls should be only the path component (no host, query, or target)
+ def compute_relative_path(from_url, to_url)
+ from_components = expand_url_path(from_url).split("/")
+ from_components.pop # drop the filename from the source
+ to_components = expand_url_path(to_url).split("/")
+ to_base = to_components.pop
+ while from_components.first == to_components.first
+ break if from_components.empty?
+ from_components.shift
+ to_components.shift
+ end
+
+ ([".."] * from_components.size + to_components + [to_base]).join("/")
+ end
+end
diff --git a/core/lib/compass/core/sass_extensions/functions/configuration.rb b/core/lib/compass/core/sass_extensions/functions/configuration.rb
index d57e33ce9a..c85d750465 100644
--- a/core/lib/compass/core/sass_extensions/functions/configuration.rb
+++ b/core/lib/compass/core/sass_extensions/functions/configuration.rb
@@ -83,8 +83,7 @@ def add_sass_configuration(project_path)
end
declare :add_sass_configuration, [:project_path]
- OPTION_TRANSFORMER = Hash.new() {|h, k| proc {|v, ctx| v.value } }
- OPTION_TRANSFORMER[:asset_cache_buster] = proc do |v, ctx|
+ def self.make_cache_buster_proc(v, ctx)
proc do |url, file|
if ctx.environment.function(v.value) || Sass::Script::Functions.callable?(v.value.tr('-', '_'))
result = ctx.call(v, ctx.quoted_string(url),
@@ -108,7 +107,8 @@ def add_sass_configuration(project_path)
end
end
end
- OPTION_TRANSFORMER[:asset_host] = proc do |v, ctx|
+
+ def self.make_asset_host_proc(v, ctx)
proc do |file|
if ctx.environment.function(v.value) || Sass::Script::Functions.callable?(v.value.tr('-', '_'))
result = ctx.call(v, ctx.quoted_string(file))
@@ -122,6 +122,39 @@ def add_sass_configuration(project_path)
raise ArgumentError, "#{v.value} is not a function."
end
end
+
+ end
+
+ OPTION_TRANSFORMER = Hash.new() {|h, k| proc {|v, ctx| v.value } }
+ OPTION_TRANSFORMER[:asset_cache_buster] = proc {|v, ctx| make_cache_buster_proc(v, ctx) }
+ OPTION_TRANSFORMER[:asset_host] = proc {|v, ctx| make_asset_host_proc(v, ctx) }
+
+ OPTION_TRANSFORMER[:asset_collections] = proc do |v, ctx|
+ v = list([v], :comma) if v.is_a?(Sass::Script::Value::Map)
+ ctx.assert_type(v, :List)
+
+ asset_collections = []
+
+ v.value.each do |map|
+ ctx.assert_type(map, :Map)
+ asset_collection = {}
+ map.value.keys.each do |key|
+ ctx.assert_type key, :String
+ ctx.assert_type map.value[key], :String unless map.value[key].value.nil?
+ underscored = key.value.tr("-", "_")
+ case underscored
+ when "asset_host"
+ asset_collection[underscored] = make_asset_host_proc(map.value[key], ctx)
+ when "asset_cache_buster"
+ asset_collection[underscored] = make_cache_buster_proc(map.value[key], ctx)
+ else
+ asset_collection[underscored] = map.value[key].value
+ end
+ end
+ asset_collections << Compass::Configuration::AssetCollection.new(asset_collection)
+ end
+
+ asset_collections
end
def add_configuration(options)
@@ -144,7 +177,8 @@ def add_configuration(options)
private
def runtime_writable_attributes
- Compass::Configuration::ATTRIBUTES - Compass::Configuration::RUNTIME_READONLY_ATTRIBUTES
+ (Compass::Configuration::ATTRIBUTES + Compass::Configuration::ARRAY_ATTRIBUTES) -
+ Compass::Configuration::RUNTIME_READONLY_ATTRIBUTES
end
def common_parent_directory(directory1, directory2)
diff --git a/core/lib/compass/core/sass_extensions/functions/urls.rb b/core/lib/compass/core/sass_extensions/functions/urls.rb
index 25dcc58252..0533098ce9 100644
--- a/core/lib/compass/core/sass_extensions/functions/urls.rb
+++ b/core/lib/compass/core/sass_extensions/functions/urls.rb
@@ -20,21 +20,15 @@ def self.included(base)
end
end
def stylesheet_url(path, only_path = bool(false))
- # Compute the path to the stylesheet, either root relative or stylesheet relative
- # or nil if the http_images_path is not set in the configuration.
- http_stylesheets_path = if relative?
- compute_relative_path(Compass.configuration.css_path)
- elsif Compass.configuration.http_stylesheets_path
- Compass.configuration.http_stylesheets_path
- else
- Compass.configuration.http_root_relative(Compass.configuration.css_dir)
+ url = url_join(Compass.configuration.http_stylesheets_path, path.value)
+ if Compass.configuration.relative_assets?
+ url = compute_relative_path(current_css_url_path, url)
end
- path = "#{http_stylesheets_path}/#{path.value}"
if only_path.to_bool
- unquoted_string(clean_path(path))
+ unquoted_string(expand_url_path(url))
else
- clean_url(path)
+ unquoted_string("url('#{expand_url_path(url)}')")
end
end
end
@@ -48,60 +42,12 @@ def self.included(base)
end
end
def font_url(path, only_path = bool(false), cache_buster = bool(true))
- path = path.value # get to the string value of the literal.
-
- # Short curcuit if they have provided an absolute url.
- if absolute_path?(path)
- return unquoted_string("url(#{path})")
- end
-
- # Compute the path to the font file, either root relative or stylesheet relative
- # or nil if the http_fonts_path cannot be determined from the configuration.
- http_fonts_path = if relative?
- compute_relative_path(Compass.configuration.fonts_path)
- else
- Compass.configuration.http_fonts_path
- end
-
- # Compute the real path to the font on the file system if the fonts_dir is set.
- real_path = if Compass.configuration.fonts_dir
- File.join(Compass.configuration.fonts_path, path.gsub(/[?#].*$/,""))
- end
-
- # prepend the path to the font if there's one
- if http_fonts_path
- http_fonts_path = "#{http_fonts_path}/" unless http_fonts_path[-1..-1] == "/"
- path = "#{http_fonts_path}#{path}"
- end
-
- # Compute the asset host unless in relative mode.
- asset_host = if !relative? && Compass.configuration.asset_host
- Compass.configuration.asset_host.call(path)
- end
-
- # Compute and append the cache buster if there is one.
- if cache_buster.to_bool
- path, anchor = path.split("#", 2)
- if cache_buster.is_a?(Sass::Script::Value::String)
- path += "#{path["?"] ? "&" : "?"}#{cache_buster.value}"
- else
- path = cache_busted_path(path, real_path)
- end
- path = "#{path}#{"#" if anchor}#{anchor}"
- end
-
- # prepend the asset host if there is one.
- path = "#{asset_host}#{'/' unless path[0..0] == "/"}#{path}" if asset_host
-
- if only_path.to_bool
- unquoted_string(clean_path(path))
- else
- clean_url(path)
- end
+ resolve_asset_url(:font, path, only_path, cache_buster)
end
end
module ImageUrl
+ include Compass::Core::HTTPUtil
def self.included(base)
if base.respond_to?(:declare)
base.declare :image_url, [:path]
@@ -110,64 +56,7 @@ def self.included(base)
end
end
def image_url(path, only_path = bool(false), cache_buster = bool(true))
- path = path.value # get to the string value of the literal.
-
- if path =~ %r{^#{Regexp.escape(Compass.configuration.http_images_path)}/(.*)}
- # Treat root relative urls (without a protocol) like normal if they start with
- # the images path.
- path = $1
- elsif absolute_path?(path)
- # Short curcuit if they have provided an absolute url.
- return unquoted_string("url(#{path})")
- end
-
- # Compute the path to the image, either root relative or stylesheet relative
- # or nil if the http_images_path is not set in the configuration.
- http_images_path = if relative?
- compute_relative_path(Compass.configuration.images_path)
- elsif Compass.configuration.http_images_path
- Compass.configuration.http_images_path
- else
- Compass.configuration.http_root_relative(Compass.configuration.images_dir)
- end
-
- # Compute the real path to the image on the file stystem if the images_dir is set.
- real_path = if Compass.configuration.images_path
- File.join(Compass.configuration.images_path, path.gsub(/#.*$/,""))
- else
- File.join(Compass.configuration.project_path, path.gsub(/#.*$/,""))
- end
-
- # prepend the path to the image if there's one
- if http_images_path
- http_images_path = "#{http_images_path}/" unless http_images_path[-1..-1] == "/"
- path = "#{http_images_path}#{path}"
- end
-
- # Compute the asset host unless in relative mode.
- asset_host = if !relative? && Compass.configuration.asset_host
- Compass.configuration.asset_host.call(path)
- end
-
- # Compute and append the cache buster if there is one.
- if cache_buster.to_bool
- path, anchor = path.split("#", 2)
- if cache_buster.is_a?(Sass::Script::Value::String)
- path += "#{path["?"] ? "&" : "?"}#{cache_buster.value}"
- else
- path = cache_busted_path(path, real_path)
- end
- path = "#{path}#{"#" if anchor}#{anchor}"
- end
-
- # prepend the asset host if there is one.
- path = "#{asset_host}#{'/' unless path[0..0] == "/"}#{path}" if asset_host
-
- if only_path.to_bool
- unquoted_string(clean_path(path))
- else
- clean_url(path)
- end
+ resolve_asset_url(:image, path, only_path, cache_buster)
end
end
@@ -179,135 +68,26 @@ def self.included(base)
end
end
def generated_image_url(path, cache_buster = bool(false))
- path = path.value # get to the string value of the literal.
-
- if path =~ %r{^#{Regexp.escape(Compass.configuration.http_generated_images_path)}/(.*)}
- # Treat root relative urls (without a protocol) like normal if they start with
- # the generated_images path.
- path = $1
- elsif absolute_path?(path)
- # Short curcuit if they have provided an absolute url.
- return unquoted_string("url(#{path})")
- end
-
- # Compute the path to the image, either root relative or stylesheet relative
- # or nil if the http_generated_images_path is not set in the configuration.
- http_generated_images_path = if relative?
- compute_relative_path(Compass.configuration.generated_images_path)
- elsif Compass.configuration.http_generated_images_path
- Compass.configuration.http_generated_images_path
- else
- Compass.configuration.http_root_relative(Compass.configuration.generated_images_dir)
- end
-
- # Compute the real path to the image on the file stystem if the generated_images_dir is set.
- real_path = if Compass.configuration.generated_images_path
- File.join(Compass.configuration.generated_images_path, path.gsub(/#.*$/,""))
- else
- File.join(Compass.configuration.project_path, path.gsub(/#.*$/,""))
- end
-
- # prepend the path to the image if there's one
- if http_generated_images_path
- http_generated_images_path = "#{http_generated_images_path}/" unless http_generated_images_path[-1..-1] == "/"
- path = "#{http_generated_images_path}#{path}"
- end
-
- # Compute the asset host unless in relative mode.
- asset_host = if !relative? && Compass.configuration.asset_host
- Compass.configuration.asset_host.call(path)
- end
-
- # Compute and append the cache buster if there is one.
- if cache_buster.to_bool
- path, anchor = path.split("#", 2)
- if cache_buster.is_a?(Sass::Script::Value::String)
- path += "#{path["?"] ? "&" : "?"}#{cache_buster.value}"
- else
- path = cache_busted_path(path, real_path)
- end
- path = "#{path}#{"#" if anchor}#{anchor}"
- end
-
- # prepend the asset host if there is one.
- path = "#{asset_host}#{'/' unless path[0..0] == "/"}#{path}" if asset_host
-
- clean_url(path)
+ resolve_asset_url(:generated_image, path, bool(false), cache_buster)
end
end
private
- # Emits a path, taking off any leading "./"
- def clean_path(url)
- url = url.to_s
- url = url[0..1] == "./" ? url[2..-1] : url
- end
-
- # Emits a url, taking off any leading "./"
- def clean_url(url)
- unquoted_string("url('#{clean_path(url)}')")
- end
-
- def relative?
- Compass.configuration.relative_assets?
- end
-
- def absolute_path?(path)
- path[0..0] == "/" || path[0..3] == "http"
- end
-
- def compute_relative_path(path)
- if (target_css_file = options[:css_filename])
- target_path = Pathname.new(File.expand_path(path))
- source_path = Pathname.new(File.dirname(File.expand_path(target_css_file)))
- target_path.relative_path_from(source_path).to_s
+ def current_css_url_path
+ if options[:css_filename] && options[:css_filename].start_with?("#{Compass.configuration.css_path}/")
+ url_join(Compass.configuration.http_stylesheets_path, options[:css_filename][(Compass.configuration.css_path.size + 1)..-1])
end
end
- def cache_busted_path(path, real_path)
- cache_buster = compute_cache_buster(path, real_path)
- if cache_buster.nil?
- return path
- elsif cache_buster.is_a?(String)
- cache_buster = {:query => cache_buster}
- else
- path = cache_buster[:path] if cache_buster[:path]
- end
-
- if cache_buster[:query]
- "#{path}#{path["?"] ? "&" : "?"}#{cache_buster[:query]}"
+ def resolve_asset_url(type, path, only_path, cache_buster)
+ path = path.value # get to the string value of the literal.
+ css_file = current_css_url_path if Compass.configuration.relative_assets?
+ url = Compass.configuration.url_resolver.compute_url(type, path, css_file, cache_buster.to_bool)
+ if only_path.to_bool
+ unquoted_string(url)
else
- path
+ unquoted_string("url('#{url}')")
end
end
-
- def compute_cache_buster(path, real_path)
- file = nil
- if Compass.configuration.asset_cache_buster
- args = [path]
- if Compass.configuration.asset_cache_buster.arity > 1
- begin
- file = File.new(real_path) if real_path
- rescue Errno::ENOENT
- # pass
- end
- args << file
- end
- Compass.configuration.asset_cache_buster.call(*args)
- elsif real_path
- default_cache_buster(path, real_path)
- end
- ensure
- file.close if file
- end
-
- def default_cache_buster(path, real_path)
- if File.readable?(real_path)
- File.mtime(real_path).to_i.to_s
- else
- $stderr.puts "WARNING: '#{File.basename(path)}' was not found (or cannot be read) in #{File.dirname(real_path)}"
- end
- end
-
end
diff --git a/core/lib/compass/util.rb b/core/lib/compass/util.rb
index 64455743a5..973f2d2e3f 100644
--- a/core/lib/compass/util.rb
+++ b/core/lib/compass/util.rb
@@ -16,4 +16,19 @@ def blank?(value)
end
end
+ def assert_valid_keys(hash, *keys)
+ keys = keys.inject([]) {|m, k| m << k; m << k.to_s if k.is_a?(Symbol); m}
+ invalid_keys = hash.keys - keys
+ if invalid_keys.any?
+ raise ArgumentError, "Invalid key#{'s' if invalid_keys.size > 1} found: #{invalid_keys.map{|k| k.inspect}.join(", ")}"
+ end
+ end
+
+ def symbolize_keys!(hash)
+ hash.keys.select {|k| k.is_a?(String)}.each do |k|
+ hash[k.to_sym] = hash.delete(k)
+ end
+ nil
+ end
+
end
diff --git a/core/test/integrations/projects/busted_font_urls/css/screen.css b/core/test/integrations/projects/busted_font_urls/css/screen.css
index fe08b96758..303caf1a89 100644
--- a/core/test/integrations/projects/busted_font_urls/css/screen.css
+++ b/core/test/integrations/projects/busted_font_urls/css/screen.css
@@ -1,8 +1,8 @@
-.showgrid { font-family: url('http://assets3.example.com/fonts/grid-BUSTED.ttf'); }
+.showgrid { font-family: url('http://assets2.example.com/fonts/grid-BUSTED.ttf'); }
.no-buster { font-family: url('http://assets3.example.com/fonts/grid.ttf'); }
-.buster-by-default { font-family: url('http://assets3.example.com/fonts/grid-BUSTED.ttf'); }
+.buster-by-default { font-family: url('http://assets2.example.com/fonts/grid-BUSTED.ttf'); }
.feed { font-family: url('http://assets3.example.com/fonts/feed.ttf?query_string'); }
diff --git a/core/test/integrations/projects/busted_image_urls/css/screen.css b/core/test/integrations/projects/busted_image_urls/css/screen.css
index 12567c02b3..301144a9b8 100644
--- a/core/test/integrations/projects/busted_image_urls/css/screen.css
+++ b/core/test/integrations/projects/busted_image_urls/css/screen.css
@@ -1,4 +1,4 @@
-.showgrid { background-image: url('http://assets0.example.com/images/grid-BUSTED.png'); }
+.showgrid { background-image: url('http://assets3.example.com/images/grid-BUSTED.png'); }
.inlinegrid { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAUEAYAAACv1qP4AAAABmJLR0T///////8JWPfcAAAACXBIWXMAAABIAAAASABGyWs+AAAAZ0lEQVRYw+3QwQ2AIBAFUTEUwI3+uzN7gDscsIgxEuO8An52J11X73OudfxMraXkzHfO3Y98nQEhA0IGhAwIGRAyIGRAyICQASEDQgaEDAgZEDIgZEDIgJABoZzSGK3tPuN9ERFP7Nw4fg+c5g8V1wAAAABJRU5ErkJggg=='); }
diff --git a/core/test/integrations/projects/compass/css/images.css b/core/test/integrations/projects/compass/css/images.css
index e0c4fdae3f..9d05bfd48a 100644
--- a/core/test/integrations/projects/compass/css/images.css
+++ b/core/test/integrations/projects/compass/css/images.css
@@ -5,4 +5,4 @@
background-image: url('/images/4x6.png?busted=true'); }
.absolute {
- background-image: url(http://example.com/images/4x6.png); }
+ background-image: url('http://example.com/images/4x6.png'); }
diff --git a/core/test/integrations/projects/compass/fonts/font1.eot b/core/test/integrations/projects/compass/fonts/font1.eot
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/integrations/projects/compass/fonts/font1.woff b/core/test/integrations/projects/compass/fonts/font1.woff
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/integrations/projects/relative/css/screen.css b/core/test/integrations/projects/relative/css/screen.css
index a076f52a2b..2fa1b9899c 100644
--- a/core/test/integrations/projects/relative/css/screen.css
+++ b/core/test/integrations/projects/relative/css/screen.css
@@ -1 +1,5 @@
-test { background: url('../assets/images/testing.png?<%= File.mtime(File.join(Compass.configuration.project_path, 'assets', 'images', 'testing.png')).strftime("%s") %>'); }
+test { background: url('../assets/images/testing-d41d8cd98f00b204e9800998ecf8427e.png'); }
+
+.assets-one { font-url: url('../assets-1/fnt/font-one-d41d8cd98f00b204e9800998ecf8427e.woff'); image-url: url('../assets-1/img/image-one-d41d8cd98f00b204e9800998ecf8427e.png'); }
+
+.assets-two { font-url: url('../assets/d41d8cd98f00b204e9800998ecf8427e.ttf'); image-url: url('../assets/d41d8cd98f00b204e9800998ecf8427e.jpg'); subdir-image-url: url('../assets/d41d8cd98f00b204e9800998ecf8427e.gif'); }
diff --git a/core/test/integrations/projects/relative/sass/_project-setup.scss b/core/test/integrations/projects/relative/sass/_project-setup.scss
index 52e5e3106d..bb403103f6 100644
--- a/core/test/integrations/projects/relative/sass/_project-setup.scss
+++ b/core/test/integrations/projects/relative/sass/_project-setup.scss
@@ -2,5 +2,37 @@ $project-path: absolute-path(join-file-segments(".."));
@import "compass/configuration";
-@include compass-configuration($images-dir: join-file-segments("assets", "images"),
- $relative-assets: true);
+@function different-cache-buster($url, $filename) {
+ $parsed-file: split-filename($url);
+ $directory: nth($parsed-file, 1);
+ $base: nth($parsed-file, 2);
+ $ext: nth($parsed-file, 3);
+ @return (path: "/assets/#{md5sum($filename)}#{$ext}");
+}
+
+@function my-cache-buster($url, $filename) {
+ $parsed-file: split-filename($url);
+ $directory: nth($parsed-file, 1);
+ $base: nth($parsed-file, 2);
+ $ext: nth($parsed-file, 3);
+ @return (path: "#{$directory}/#{$base}-#{md5sum($filename)}#{$ext}");
+}
+
+$compass-config: (
+ relative-assets: true,
+ images-dir: join-file-segments("assets", "images"),
+ asset-cache-buster: my-cache-buster,
+ asset-collections: (
+ (root-dir: join-file-segments("vendor", "asset-collection-1"),
+ http-dir: "assets-1",
+ images-dir: img,
+ fonts-dir: fnt),
+ (root-dir: join-file-segments("vendor", "asset-collection-2"),
+ http-dir: "assets-2",
+ images-dir: assets,
+ fonts-dir: assets,
+ asset-cache-buster: different-cache-buster),
+ ),
+);
+
+@include compass-configuration($compass-config);
diff --git a/core/test/integrations/projects/relative/sass/screen.sass b/core/test/integrations/projects/relative/sass/screen.sass
index 68078ba1f7..44cc611534 100644
--- a/core/test/integrations/projects/relative/sass/screen.sass
+++ b/core/test/integrations/projects/relative/sass/screen.sass
@@ -1,3 +1,10 @@
@import "project-setup"
test
background: image-url("testing.png")
+.assets-one
+ font-url: font-url("font-one.woff")
+ image-url: image-url("image-one.png")
+.assets-two
+ font-url: font-url("font-two.ttf")
+ image-url: image-url("image-two.jpg")
+ subdir-image-url: image-url("subdir/image-three.gif")
diff --git a/core/test/integrations/projects/relative/vendor/asset-collection-1/fnt/font-one.woff b/core/test/integrations/projects/relative/vendor/asset-collection-1/fnt/font-one.woff
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/integrations/projects/relative/vendor/asset-collection-1/img/image-one.png b/core/test/integrations/projects/relative/vendor/asset-collection-1/img/image-one.png
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/integrations/projects/relative/vendor/asset-collection-2/assets/font-two.ttf b/core/test/integrations/projects/relative/vendor/asset-collection-2/assets/font-two.ttf
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/integrations/projects/relative/vendor/asset-collection-2/assets/image-two.jpg b/core/test/integrations/projects/relative/vendor/asset-collection-2/assets/image-two.jpg
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/integrations/projects/relative/vendor/asset-collection-2/assets/subdir/image-three.gif b/core/test/integrations/projects/relative/vendor/asset-collection-2/assets/subdir/image-three.gif
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/units/asset_collection_test.rb b/core/test/units/asset_collection_test.rb
new file mode 100755
index 0000000000..40b183b092
--- /dev/null
+++ b/core/test/units/asset_collection_test.rb
@@ -0,0 +1,356 @@
+#! /usr/bin/env ruby
+test_directory = File.expand_path(File.dirname(__FILE__))
+$: << test_directory unless $:.include? test_directory
+require 'test_helper'
+
+class AssetCollectionTest < Test::Unit::TestCase
+
+ include Compass::Configuration
+ include Compass::Core::HTTPUtil
+
+ ABSOLUTE_COLLECTION_OPTS = {
+ :root_path => "/tmp/some_assets",
+ :sass_dir => "scss",
+ :fonts_dir => "fnts",
+ :images_dir => "imgs",
+ :http_path => "/some-assets",
+ :asset_host => nil,
+ :asset_cache_buster => :none
+ }
+
+ RELATIVE_COLLECTION_OPTS = {
+ :root_dir => "some_assets",
+ :sass_dir => "scss",
+ :fonts_dir => "fnts",
+ :images_dir => "imgs",
+ :http_dir => "some-assets",
+ :asset_host => nil,
+ :asset_cache_buster => :none
+ }
+
+ HTTP_RELATIVE_COLLECTION_OPTS = {
+ :root_dir => "some_assets",
+ :sass_dir => "scss",
+ :fonts_dir => "fnts",
+ :images_dir => "imgs",
+ :http_fonts_dir => "hfnts",
+ :http_images_dir => "himgs",
+ :http_dir => "some-assets",
+ :asset_host => nil,
+ :asset_cache_buster => :none
+ }
+
+ HTTP_ABSOLUTE_COLLECTION_OPTS = {
+ :root_dir => "some_assets",
+ :sass_dir => "scss",
+ :fonts_dir => "fnts",
+ :images_dir => "imgs",
+ :http_fonts_path => "/font-assets",
+ :http_images_path => "/image-assets",
+ :http_dir => "some-assets",
+ :asset_host => nil,
+ :asset_cache_buster => :none
+ }
+
+ def test_asset_collection_keys
+ valid_sets = [ABSOLUTE_COLLECTION_OPTS, RELATIVE_COLLECTION_OPTS,
+ HTTP_RELATIVE_COLLECTION_OPTS, HTTP_ABSOLUTE_COLLECTION_OPTS]
+
+ valid_sets.each do |v|
+ AssetCollection.new(v)
+ end
+ end
+
+ def test_invalid_collection_keys
+ assert_raise_message(ArgumentError, "Either :root_path or :root_dir must be specified.") do
+ AssetCollection.new({})
+ end
+ end
+
+ def test_invalid_collection_keys
+ assert_raise_message(ArgumentError, "Either :http_path or :http_dir must be specified.") do
+ AssetCollection.new({:root_path => "/tmp"})
+ end
+ end
+
+
+ def test_resolved_attributes_absolute
+ collection = AssetCollection.new(ABSOLUTE_COLLECTION_OPTS)
+ assert_equal "/tmp/some_assets", collection.root_path
+ assert_equal "/some-assets", collection.http_path
+ assert_equal "/tmp/some_assets/scss", collection.sass_path
+ assert_equal "/tmp/some_assets/fnts", collection.fonts_path
+ assert_equal "/tmp/some_assets/imgs", collection.images_path
+ assert_equal "/some-assets/fnts", collection.http_fonts_path
+ assert_equal "/some-assets/imgs", collection.http_images_path
+ assert_equal nil, collection.asset_host
+ assert_equal :none, collection.asset_cache_buster
+ end
+
+ def test_resolved_attributes_relative
+ collection = AssetCollection.new(RELATIVE_COLLECTION_OPTS)
+ assert_equal "./some_assets", collection.root_path
+ assert_equal "/some-assets", collection.http_path
+ assert_equal "./some_assets/scss", collection.sass_path
+ assert_equal "./some_assets/fnts", collection.fonts_path
+ assert_equal "./some_assets/imgs", collection.images_path
+ assert_equal "/some-assets/fnts", collection.http_fonts_path
+ assert_equal "/some-assets/imgs", collection.http_images_path
+ assert_equal nil, collection.asset_host
+ assert_equal :none, collection.asset_cache_buster
+ end
+
+ def test_resolved_attributes_http_absolute
+ collection = AssetCollection.new(HTTP_ABSOLUTE_COLLECTION_OPTS)
+ assert_equal "./some_assets", collection.root_path
+ assert_equal "/some-assets", collection.http_path
+ assert_equal "./some_assets/scss", collection.sass_path
+ assert_equal "./some_assets/fnts", collection.fonts_path
+ assert_equal "./some_assets/imgs", collection.images_path
+ assert_equal "/font-assets", collection.http_fonts_path
+ assert_equal "/image-assets", collection.http_images_path
+ assert_equal nil, collection.asset_host
+ assert_equal :none, collection.asset_cache_buster
+ end
+
+ def test_resolved_attributes_http_relative
+ collection = AssetCollection.new(HTTP_RELATIVE_COLLECTION_OPTS)
+ assert_equal "./some_assets", collection.root_path
+ assert_equal "/some-assets", collection.http_path
+ assert_equal "./some_assets/scss", collection.sass_path
+ assert_equal "./some_assets/fnts", collection.fonts_path
+ assert_equal "./some_assets/imgs", collection.images_path
+ assert_equal "/some-assets/hfnts", collection.http_fonts_path
+ assert_equal "/some-assets/himgs", collection.http_images_path
+ assert_equal nil, collection.asset_host
+ assert_equal :none, collection.asset_cache_buster
+ end
+
+ def test_resolved_attributes_absolute_default
+ asset_host_proc = proc {|url| "http://something.com" }
+ asset_cache_buster_proc = proc {|url, file| nil }
+ Compass.configuration.asset_host(&asset_host_proc)
+ Compass.configuration.asset_cache_buster(&asset_cache_buster_proc)
+ collection = AssetCollection.new(:root_path => ABSOLUTE_COLLECTION_OPTS[:root_path],
+ :http_path => ABSOLUTE_COLLECTION_OPTS[:http_path])
+ assert_equal "/tmp/some_assets", collection.root_path
+ assert_equal "/some-assets", collection.http_path
+ assert_equal nil, collection.sass_path
+ assert_equal nil, collection.fonts_path
+ assert_equal nil, collection.images_path
+ assert_equal nil, collection.asset_host
+ assert_equal nil, collection.asset_cache_buster
+ end
+
+ def test_resolved_attributes_relative_default
+ asset_host_proc = proc {|url| "http://something.com" }
+ asset_cache_buster_proc = proc {|url, file| nil }
+ Compass.configuration.asset_host(&asset_host_proc)
+ Compass.configuration.asset_cache_buster(&asset_cache_buster_proc)
+ collection = AssetCollection.new(:root_dir => RELATIVE_COLLECTION_OPTS[:root_dir],
+ :http_dir => RELATIVE_COLLECTION_OPTS[:http_dir])
+ assert_equal "./some_assets", collection.root_path
+ assert_equal "/some-assets", collection.http_path
+ assert_equal nil, collection.sass_path
+ assert_equal nil, collection.fonts_path
+ assert_equal nil, collection.images_path
+ assert_equal asset_host_proc, collection.asset_host
+ assert_equal asset_cache_buster_proc, collection.asset_cache_buster
+ end
+
+ def test_url_join
+ assert_equal "foo/bar", url_join("foo", "bar")
+ assert_equal "foo/bar/baz", url_join("foo", "bar", "baz")
+ assert_equal "foo/bar", url_join("foo/", "bar")
+ assert_equal "foo/bar/baz", url_join("foo/", "bar/", "baz")
+ end
+
+ def test_expand_url_path
+ assert_equal "/", expand_url_path("/")
+ assert_equal "/foo", expand_url_path("/foo")
+ assert_equal "/foo/", expand_url_path("/foo/")
+ assert_equal "/", expand_url_path("/foo/..")
+ assert_equal "", expand_url_path("foo/..")
+ assert_equal "d", expand_url_path("a/../b/../c/../d")
+ assert_equal "d", expand_url_path("a/../b/c/../../d")
+ assert_equal "a/b", expand_url_path("a//b")
+ assert_equal "a/b", expand_url_path("a/./b")
+ assert_raise_message(ArgumentError, "Invalid URL: .. (not enough parent directories)") do
+ expand_url_path("..")
+ end
+ assert_raise_message(ArgumentError, "Invalid URL: a/../b/c/../../../d (not enough parent directories)") do
+ expand_url_path("a/../b/c/../../../d")
+ end
+ end
+
+ def test_asset_url_resolver
+ fixtures_dir = File.expand_path("fixtures/asset_resolver_tests", File.dirname(__FILE__))
+ configuration = Compass::Configuration::Data.new("test", :project_path => fixtures_dir, :asset_cache_buster => :none)
+ configuration.extend(Compass::Configuration::Defaults)
+ collection1 = Compass::Configuration::AssetCollection.new(
+ {:root_dir => "asset_collection_one",
+ :http_dir => "asset-collection-one",
+ :fonts_dir => "fancy_fonts",
+ :images_dir => "fancy_images",
+ :asset_cache_buster => :none}
+ )
+ collection2 = Compass::Configuration::AssetCollection.new(
+ {:root_dir => "asset_collection_two",
+ :http_dir => "asset-collection-two",
+ :fonts_dir => "more_fonts",
+ :images_dir => "more_images",
+ :asset_cache_buster => :none}
+ )
+ resolver = Compass::Core::AssetUrlResolver.new([collection1, collection2], configuration)
+ assert_equal "/asset-collection-two/more_images/moreimage.jpg",
+ resolver.compute_url(:image, "moreimage.jpg")
+ assert_equal "/asset-collection-one/fancy_images/image1.jpg",
+ resolver.compute_url(:image, "image1.jpg")
+ assert_equal "/images/default.jpg",
+ resolver.compute_url(:image, "default.jpg")
+ assert_equal "/asset-collection-two/more_fonts/morefont.ttf",
+ resolver.compute_url(:font, "morefont.ttf")
+ assert_equal "/asset-collection-one/fancy_fonts/font1.ttf",
+ resolver.compute_url(:font, "font1.ttf")
+ assert_equal "/fonts/default.ttf",
+ resolver.compute_url(:font, "default.ttf")
+ assert_equal "/asset-collection-one/fancy_images/fancy_subdir/image2.png",
+ resolver.compute_url(:image, "fancy_subdir/image2.png")
+ assert_equal "/asset-collection-one/fancy_images/image1.jpg",
+ resolver.compute_url(:image, "fancy_subdir/../image1.jpg")
+ assert_equal "/asset-collection-one/fancy_images/image3.svg#something",
+ resolver.compute_url(:image, "image3.svg#something")
+ assert_equal "/asset-collection-one/fancy_images/image3.svg?q=1234&s=4321#something",
+ resolver.compute_url(:image, "image3.svg?q=1234&s=4321#something")
+ assert_equal "/does-not-exist/thing.jpg",
+ resolver.compute_url(:image, "/does-not-exist/thing.jpg")
+ assert_equal "http://google.com/logo.png",
+ resolver.compute_url(:image, "http://google.com/logo.png")
+ error_message = "Could not find doesnotexist.jpg in " +
+ "#{resolver.asset_collections.map{|ac| ac.images_path}.join(", ")}"
+ assert_raise_message(Compass::Core::AssetUrlResolver::AssetNotFound, error_message) do
+ resolver.compute_url(:image, "doesnotexist.jpg")
+ end
+ end
+
+ def test_asset_url_resolver_with_asset_hosts_and_busters
+ fixtures_dir = File.expand_path("fixtures/asset_resolver_tests", File.dirname(__FILE__))
+ configuration = Compass::Configuration::Data.new("test", :project_path => fixtures_dir)
+ configuration.extend(Compass::Configuration::Defaults)
+ configuration.asset_host do |url|
+ "http://default-server.org/"
+ end
+ configuration.asset_cache_buster do |url, file|
+ "md5somthing"
+ end
+ collection1 = Compass::Configuration::AssetCollection.new(
+ {:root_dir => "asset_collection_one",
+ :http_dir => "asset-collection-one",
+ :fonts_dir => "fancy_fonts",
+ :images_dir => "fancy_images"}
+ )
+ collection2 = Compass::Configuration::AssetCollection.new(
+ {:root_dir => "asset_collection_two",
+ :http_dir => "asset-collection-two",
+ :fonts_dir => "more_fonts",
+ :images_dir => "more_images",
+ :asset_host => proc {|url| "http://more-assets-server.com/" },
+ :asset_cache_buster => method(:simple_path_buster)}
+ )
+ resolver = Compass::Core::AssetUrlResolver.new([collection1, collection2], configuration)
+ assert_equal "http://more-assets-server.com/asset-collection-two/more_images/moreimage-md5something.jpg",
+ resolver.compute_url(:image, "moreimage.jpg")
+ assert_equal "http://default-server.org/asset-collection-one/fancy_images/image1.jpg?md5somthing",
+ resolver.compute_url(:image, "image1.jpg")
+ assert_equal "http://default-server.org/images/default.jpg?md5somthing",
+ resolver.compute_url(:image, "default.jpg")
+ assert_equal "http://more-assets-server.com/asset-collection-two/more_fonts/morefont-md5something.ttf",
+ resolver.compute_url(:font, "morefont.ttf")
+ assert_equal "http://default-server.org/asset-collection-one/fancy_fonts/font1.ttf?md5somthing",
+ resolver.compute_url(:font, "font1.ttf")
+ assert_equal "http://default-server.org/fonts/default.ttf?md5somthing",
+ resolver.compute_url(:font, "default.ttf")
+ assert_equal "http://default-server.org/asset-collection-one/fancy_images/fancy_subdir/image2.png?md5somthing",
+ resolver.compute_url(:image, "fancy_subdir/image2.png")
+ assert_equal "http://default-server.org/asset-collection-one/fancy_images/image1.jpg?md5somthing",
+ resolver.compute_url(:image, "fancy_subdir/../image1.jpg")
+ assert_equal "http://default-server.org/asset-collection-one/fancy_images/image3.svg?md5somthing#something",
+ resolver.compute_url(:image, "image3.svg#something")
+ assert_equal "http://default-server.org/asset-collection-one/fancy_images/image3.svg?q=1234&s=4321&md5somthing#something",
+ resolver.compute_url(:image, "image3.svg?q=1234&s=4321#something")
+ assert_equal "/does-not-exist/thing.jpg",
+ resolver.compute_url(:image, "/does-not-exist/thing.jpg")
+ assert_equal "http://google.com/logo.png",
+ resolver.compute_url(:image, "http://google.com/logo.png")
+ error_message = "Could not find doesnotexist.jpg in " +
+ "#{resolver.asset_collections.map{|ac| ac.images_path}.join(", ")}"
+ assert_raise_message(Compass::Core::AssetUrlResolver::AssetNotFound, error_message) do
+ resolver.compute_url(:image, "doesnotexist.jpg")
+ end
+ end
+
+ def test_asset_url_resolver_with_asset_hosts_and_busters_when_relative
+ fixtures_dir = File.expand_path("fixtures/asset_resolver_tests", File.dirname(__FILE__))
+ configuration = Compass::Configuration::Data.new("test", :project_path => fixtures_dir)
+ configuration.extend(Compass::Configuration::Defaults)
+ configuration.asset_host do |url|
+ "http://default-server.org/"
+ end
+ configuration.asset_cache_buster do |url, file|
+ "md5somthing"
+ end
+ configuration.relative_assets = true
+ collection1 = Compass::Configuration::AssetCollection.new(
+ {:root_dir => "asset_collection_one",
+ :http_dir => "asset-collection-one",
+ :fonts_dir => "fancy_fonts",
+ :images_dir => "fancy_images"}
+ )
+ collection2 = Compass::Configuration::AssetCollection.new(
+ {:root_dir => "asset_collection_two",
+ :http_dir => "asset-collection-two",
+ :fonts_dir => "more_fonts",
+ :images_dir => "more_images",
+ :asset_host => proc {|url| "http://more-assets-server.com/" },
+ :asset_cache_buster => method(:simple_path_buster)}
+ )
+ resolver = Compass::Core::AssetUrlResolver.new([collection1, collection2], configuration)
+ assert_equal "../asset-collection-two/more_images/moreimage-md5something.jpg",
+ resolver.compute_url(:image, "moreimage.jpg", "/css/some-css-file.css")
+ assert_equal "../asset-collection-one/fancy_images/image1.jpg?md5somthing",
+ resolver.compute_url(:image, "image1.jpg", "/css/some-css-file.css")
+ assert_equal "../images/default.jpg?md5somthing",
+ resolver.compute_url(:image, "default.jpg", "/css/some-css-file.css")
+ assert_equal "../asset-collection-two/more_fonts/morefont-md5something.ttf",
+ resolver.compute_url(:font, "morefont.ttf", "/css/some-css-file.css")
+ assert_equal "../asset-collection-one/fancy_fonts/font1.ttf?md5somthing",
+ resolver.compute_url(:font, "font1.ttf", "/css/some-css-file.css")
+ assert_equal "../fonts/default.ttf?md5somthing",
+ resolver.compute_url(:font, "default.ttf", "/css/some-css-file.css")
+ assert_equal "../asset-collection-one/fancy_images/fancy_subdir/image2.png?md5somthing",
+ resolver.compute_url(:image, "fancy_subdir/image2.png", "/css/some-css-file.css")
+ assert_equal "../asset-collection-one/fancy_images/image1.jpg?md5somthing",
+ resolver.compute_url(:image, "fancy_subdir/../image1.jpg", "/css/some-css-file.css")
+ assert_equal "../asset-collection-one/fancy_images/image3.svg?md5somthing#something",
+ resolver.compute_url(:image, "image3.svg#something", "/css/some-css-file.css")
+ assert_equal "../asset-collection-one/fancy_images/image3.svg?q=1234&s=4321&md5somthing#something",
+ resolver.compute_url(:image, "image3.svg?q=1234&s=4321#something", "/css/some-css-file.css")
+ assert_equal "/does-not-exist/thing.jpg",
+ resolver.compute_url(:image, "/does-not-exist/thing.jpg", "/css/some-css-file.css")
+ assert_equal "http://google.com/logo.png",
+ resolver.compute_url(:image, "http://google.com/logo.png", "/css/some-css-file.css")
+ error_message = "Could not find doesnotexist.jpg in " +
+ "#{resolver.asset_collections.map{|ac| ac.images_path}.join(", ")}"
+ assert_raise_message(Compass::Core::AssetUrlResolver::AssetNotFound, error_message) do
+ resolver.compute_url(:image, "doesnotexist.jpg")
+ end
+ end
+
+ def simple_path_buster(url_path, file)
+ segments = url_path.split('/')
+ base = segments.pop
+ base, ext = base.split(".")
+ {:path => segments.join("/") + "/" + base + "-md5something" + "." + ext }
+ end
+
+end
diff --git a/core/test/units/fixtures/asset_resolver_tests/asset_collection_one/fancy_fonts/font1.ttf b/core/test/units/fixtures/asset_resolver_tests/asset_collection_one/fancy_fonts/font1.ttf
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/units/fixtures/asset_resolver_tests/asset_collection_one/fancy_images/fancy_subdir/image2.png b/core/test/units/fixtures/asset_resolver_tests/asset_collection_one/fancy_images/fancy_subdir/image2.png
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/units/fixtures/asset_resolver_tests/asset_collection_one/fancy_images/image1.jpg b/core/test/units/fixtures/asset_resolver_tests/asset_collection_one/fancy_images/image1.jpg
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/units/fixtures/asset_resolver_tests/asset_collection_one/fancy_images/image3.svg b/core/test/units/fixtures/asset_resolver_tests/asset_collection_one/fancy_images/image3.svg
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/units/fixtures/asset_resolver_tests/asset_collection_two/more_fonts/morefont.ttf b/core/test/units/fixtures/asset_resolver_tests/asset_collection_two/more_fonts/morefont.ttf
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/units/fixtures/asset_resolver_tests/asset_collection_two/more_images/moreimage.jpg b/core/test/units/fixtures/asset_resolver_tests/asset_collection_two/more_images/moreimage.jpg
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/units/fixtures/asset_resolver_tests/fonts/default.ttf b/core/test/units/fixtures/asset_resolver_tests/fonts/default.ttf
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/units/fixtures/asset_resolver_tests/images/default.jpg b/core/test/units/fixtures/asset_resolver_tests/images/default.jpg
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/core/test/units/test_helper.rb b/core/test/units/test_helper.rb
index 2aa0865489..2e4d9dd6d5 100644
--- a/core/test/units/test_helper.rb
+++ b/core/test/units/test_helper.rb
@@ -1,3 +1,5 @@
+lib_directory = File.expand_path("../../lib", File.dirname(__FILE__))
+$: << lib_directory unless $:.include? lib_directory
require 'fileutils'
require 'compass/core'
@@ -6,3 +8,14 @@
include Compass::Diff
+class Test::Unit::TestCase
+ def assert_raise_message(klass, message)
+ begin
+ yield
+ fail "Exception not raised."
+ rescue klass => e
+ assert_equal message, e.message
+ end
+ end
+end
+
diff --git a/core/test/units/urls_test.rb b/core/test/units/urls_test.rb
deleted file mode 100644
index 4e3d92f3fb..0000000000
--- a/core/test/units/urls_test.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-#! /usr/bin/env ruby
-test_directory = File.expand_path(File.dirname(__FILE__))
-$: << test_directory unless $:.include? test_directory
-require 'test_helper'
-
-class UrlsTest < Test::Unit::TestCase
- include Compass::Core::SassExtensions::Functions::Urls
-
- def test_compute_relative_path
- options[:css_filename] = File.expand_path("./test.css")
- assert_equal ".", compute_relative_path(".")
- assert_equal ".", compute_relative_path(File.expand_path("."))
- options[:css_filename] = "./test.css"
- assert_equal ".", compute_relative_path(".")
- assert_equal ".", compute_relative_path(File.expand_path("."))
- end
-
- private
-
- def options
- @options ||= {}
- end
-end
diff --git a/core/test/units/util_test.rb b/core/test/units/util_test.rb
new file mode 100644
index 0000000000..5324f7cf8e
--- /dev/null
+++ b/core/test/units/util_test.rb
@@ -0,0 +1,43 @@
+#! /usr/bin/env ruby
+test_directory = File.expand_path(File.dirname(__FILE__))
+$: << test_directory unless $:.include? test_directory
+require 'test_helper'
+require 'compass/util'
+
+class UtilTest < Test::Unit::TestCase
+
+ def test_assert_valid_keys
+ Compass::Util.assert_valid_keys({:key1 => true}, :key1, :key2, :key3)
+ begin
+ Compass::Util.assert_valid_keys({:key1 => true, :invalid => true}, :key1, :key2, :key3)
+ fail "Did not raise"
+ rescue ArgumentError => e
+ assert_equal "Invalid key found: :invalid", e.message
+ end
+ begin
+ Compass::Util.assert_valid_keys({:key1 => true, :invalid => true, :another_invalid => true},
+ :key1, :key2, :key3)
+ fail "Did not raise"
+ rescue ArgumentError => e
+ assert_equal "Invalid keys found: :invalid, :another_invalid", e.message
+ end
+ end
+
+ def test_assert_valid_keys_with_symbols_as_strings
+ Compass::Util.assert_valid_keys({"key1" => true}, :key1, :key2, :key3)
+ begin
+ Compass::Util.assert_valid_keys({"key1" => true, "invalid" => true}, :key1, :key2, :key3)
+ fail "Did not raise"
+ rescue ArgumentError => e
+ assert_equal %q{Invalid key found: "invalid"}, e.message
+ end
+ end
+
+
+ private
+
+ def options
+ @options ||= {}
+ end
+end
+