From 068a52b22d34021e2a44acbfa43608a1143cfca2 Mon Sep 17 00:00:00 2001 From: Chris Eppstein Date: Wed, 20 Aug 2014 16:50:46 -0700 Subject: [PATCH 1/9] Utility method to assert valid keys. --- core/lib/compass/util.rb | 7 +++++++ core/test/units/util_test.rb | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 core/test/units/util_test.rb diff --git a/core/lib/compass/util.rb b/core/lib/compass/util.rb index 64455743a5..9ef06b5095 100644 --- a/core/lib/compass/util.rb +++ b/core/lib/compass/util.rb @@ -16,4 +16,11 @@ def blank?(value) end end + def assert_valid_keys(hash, *keys) + 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 + end diff --git a/core/test/units/util_test.rb b/core/test/units/util_test.rb new file mode 100644 index 0000000000..030ac1a6ae --- /dev/null +++ b/core/test/units/util_test.rb @@ -0,0 +1,31 @@ +#! /usr/bin/env ruby +test_directory = File.expand_path(File.dirname(__FILE__)) +$: << test_directory unless $:.include? test_directory +require 'test_helper' + +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 + + private + + def options + @options ||= {} + end +end + From 5fea13d75270d265ddf9ad0ee4c95f42886018ef Mon Sep 17 00:00:00 2001 From: Chris Eppstein Date: Sun, 24 Aug 2014 13:22:32 -0700 Subject: [PATCH 2/9] Add basic asset collection configuration via ruby and use it for image urls. --- .../busted_image_urls/css/screen.css | 2 +- .../stylesheets/compass/css/images.css | 2 +- cli/test/units/configuration_test.rb | 36 +- core/lib/compass/configuration.rb | 5 +- .../compass/configuration/asset_collection.rb | 234 ++++++++++++ core/lib/compass/configuration/data.rb | 29 +- core/lib/compass/core.rb | 2 + core/lib/compass/core/asset_url_resolver.rb | 144 +++++++ core/lib/compass/core/http_util.rb | 41 ++ .../core/sass_extensions/functions/urls.rb | 60 +-- core/lib/compass/util.rb | 7 + .../projects/busted_image_urls/css/screen.css | 2 +- .../projects/compass/css/images.css | 2 +- core/test/units/asset_collection_test.rb | 356 ++++++++++++++++++ .../fancy_fonts/font1.ttf | 0 .../fancy_images/fancy_subdir/image2.png | 0 .../fancy_images/image1.jpg | 0 .../fancy_images/image3.svg | 0 .../more_fonts/morefont.ttf | 0 .../more_images/moreimage.jpg | 0 .../asset_resolver_tests/fonts/default.ttf | 0 .../asset_resolver_tests/images/default.jpg | 0 core/test/units/test_helper.rb | 13 + 23 files changed, 859 insertions(+), 76 deletions(-) create mode 100644 core/lib/compass/configuration/asset_collection.rb create mode 100644 core/lib/compass/core/asset_url_resolver.rb create mode 100644 core/lib/compass/core/http_util.rb create mode 100755 core/test/units/asset_collection_test.rb create mode 100644 core/test/units/fixtures/asset_resolver_tests/asset_collection_one/fancy_fonts/font1.ttf create mode 100644 core/test/units/fixtures/asset_resolver_tests/asset_collection_one/fancy_images/fancy_subdir/image2.png create mode 100644 core/test/units/fixtures/asset_resolver_tests/asset_collection_one/fancy_images/image1.jpg create mode 100644 core/test/units/fixtures/asset_resolver_tests/asset_collection_one/fancy_images/image3.svg create mode 100644 core/test/units/fixtures/asset_resolver_tests/asset_collection_two/more_fonts/morefont.ttf create mode 100644 core/test/units/fixtures/asset_resolver_tests/asset_collection_two/more_images/moreimage.jpg create mode 100644 core/test/units/fixtures/asset_resolver_tests/fonts/default.ttf create mode 100644 core/test/units/fixtures/asset_resolver_tests/images/default.jpg 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/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/core/lib/compass/configuration.rb b/core/lib/compass/configuration.rb index 0f31283b71..4ace707885 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 = { @@ -170,6 +171,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/asset_collection.rb b/core/lib/compass/configuration/asset_collection.rb new file mode 100644 index 0000000000..7d3c7de1ba --- /dev/null +++ b/core/lib/compass/configuration/asset_collection.rb @@ -0,0 +1,234 @@ +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 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) + symbolize_keys!(options) + 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) + 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 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 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..8224175ce2 --- /dev/null +++ b/core/lib/compass/core/asset_url_resolver.rb @@ -0,0 +1,144 @@ +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 + + 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/http_util.rb b/core/lib/compass/core/http_util.rb new file mode 100644 index 0000000000..216758cb90 --- /dev/null +++ b/core/lib/compass/core/http_util.rb @@ -0,0 +1,41 @@ +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 +end diff --git a/core/lib/compass/core/sass_extensions/functions/urls.rb b/core/lib/compass/core/sass_extensions/functions/urls.rb index 25dcc58252..dd15da4843 100644 --- a/core/lib/compass/core/sass_extensions/functions/urls.rb +++ b/core/lib/compass/core/sass_extensions/functions/urls.rb @@ -102,6 +102,7 @@ def font_url(path, only_path = bool(false), cache_buster = bool(true)) end module ImageUrl + include Compass::Core::HTTPUtil def self.included(base) if base.respond_to?(:declare) base.declare :image_url, [:path] @@ -111,62 +112,15 @@ def self.included(base) 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}" + css_file = nil + if Compass.configuration.relative_assets? && options[:css_filename] && options[:css_filename].start_with?("#{Compass.configuration.css_path}/") + css_file = url_join(Compass.configuration.http_stylesheets_path, options[:css_filename][(Compass.configuration.css_path.size + 1)..-1]) end - - # prepend the asset host if there is one. - path = "#{asset_host}#{'/' unless path[0..0] == "/"}#{path}" if asset_host - + url = Compass.configuration.url_resolver.compute_url(:image, path, css_file, cache_buster.to_bool) if only_path.to_bool - unquoted_string(clean_path(path)) + unquoted_string(url) else - clean_url(path) + unquoted_string("url('#{url}')") end end end diff --git a/core/lib/compass/util.rb b/core/lib/compass/util.rb index 9ef06b5095..f617334605 100644 --- a/core/lib/compass/util.rb +++ b/core/lib/compass/util.rb @@ -23,4 +23,11 @@ def assert_valid_keys(hash, *keys) 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_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/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 + From d3ad03b737aa28b31e4c018fdca5967f7bf46da0 Mon Sep 17 00:00:00 2001 From: Chris Eppstein Date: Sun, 24 Aug 2014 13:46:24 -0700 Subject: [PATCH 3/9] Use asset collections and asset resolver for fonts. --- .../busted_font_urls/css/screen.css | 4 +- .../stylesheets/compass/fonts/font1.eot | 0 .../stylesheets/compass/fonts/font1.woff | 0 cli/test/units/sass_extensions_test.rb | 16 ++-- .../core/sass_extensions/functions/urls.rb | 77 ++++--------------- .../projects/busted_font_urls/css/screen.css | 4 +- .../projects/compass/fonts/font1.eot | 0 .../projects/compass/fonts/font1.woff | 0 8 files changed, 28 insertions(+), 73 deletions(-) create mode 100644 cli/test/fixtures/stylesheets/compass/fonts/font1.eot create mode 100644 cli/test/fixtures/stylesheets/compass/fonts/font1.woff create mode 100644 core/test/integrations/projects/compass/fonts/font1.eot create mode 100644 core/test/integrations/projects/compass/fonts/font1.woff 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/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/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/core/lib/compass/core/sass_extensions/functions/urls.rb b/core/lib/compass/core/sass_extensions/functions/urls.rb index dd15da4843..56ebb7e057 100644 --- a/core/lib/compass/core/sass_extensions/functions/urls.rb +++ b/core/lib/compass/core/sass_extensions/functions/urls.rb @@ -48,56 +48,7 @@ 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 @@ -111,17 +62,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. - css_file = nil - if Compass.configuration.relative_assets? && options[:css_filename] && options[:css_filename].start_with?("#{Compass.configuration.css_path}/") - css_file = url_join(Compass.configuration.http_stylesheets_path, options[:css_filename][(Compass.configuration.css_path.size + 1)..-1]) - end - url = Compass.configuration.url_resolver.compute_url(:image, path, css_file, cache_buster.to_bool) - if only_path.to_bool - unquoted_string(url) - else - unquoted_string("url('#{url}')") - end + resolve_asset_url(:image, path, only_path, cache_buster) end end @@ -192,6 +133,20 @@ def generated_image_url(path, cache_buster = bool(false)) private + def resolve_asset_url(type, path, only_path, cache_buster) + path = path.value # get to the string value of the literal. + css_file = nil + if Compass.configuration.relative_assets? && options[:css_filename] && options[:css_filename].start_with?("#{Compass.configuration.css_path}/") + css_file = url_join(Compass.configuration.http_stylesheets_path, options[:css_filename][(Compass.configuration.css_path.size + 1)..-1]) + end + url = Compass.configuration.url_resolver.compute_url(type, path, css_file, cache_buster.to_bool) + if only_path.to_bool + unquoted_string(url) + else + unquoted_string("url('#{url}')") + end + end + # Emits a path, taking off any leading "./" def clean_path(url) url = url.to_s 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/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 From d39aa5630e76c2a1a65145f11334bef30620b311 Mon Sep 17 00:00:00 2001 From: Chris Eppstein Date: Sun, 24 Aug 2014 15:10:33 -0700 Subject: [PATCH 4/9] Use the asset resolver for generated images. --- .../compass/configuration/asset_collection.rb | 26 +++++++++ .../core/sass_extensions/functions/urls.rb | 55 +------------------ 2 files changed, 27 insertions(+), 54 deletions(-) diff --git a/core/lib/compass/configuration/asset_collection.rb b/core/lib/compass/configuration/asset_collection.rb index 7d3c7de1ba..714eb93057 100644 --- a/core/lib/compass/configuration/asset_collection.rb +++ b/core/lib/compass/configuration/asset_collection.rb @@ -41,6 +41,10 @@ def includes_font?(relative_path) end end + def includes_generated_image?(relative_path) + nil + end + def root_path Sass::Util.abstract! end @@ -196,6 +200,20 @@ def asset_cache_buster 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 @@ -216,6 +234,14 @@ 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 diff --git a/core/lib/compass/core/sass_extensions/functions/urls.rb b/core/lib/compass/core/sass_extensions/functions/urls.rb index 56ebb7e057..90e2dd7ed1 100644 --- a/core/lib/compass/core/sass_extensions/functions/urls.rb +++ b/core/lib/compass/core/sass_extensions/functions/urls.rb @@ -74,60 +74,7 @@ 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 From 821db8acf85db75e2bc39474a8fae899efa57710 Mon Sep 17 00:00:00 2001 From: Chris Eppstein Date: Sun, 24 Aug 2014 15:37:32 -0700 Subject: [PATCH 5/9] Refactor stylesheet_url with better tools and remove useless code. --- core/lib/compass/core/asset_url_resolver.rb | 14 --- core/lib/compass/core/http_util.rb | 16 +++ .../core/sass_extensions/functions/urls.rb | 100 +++--------------- core/test/units/urls_test.rb | 23 ---- 4 files changed, 28 insertions(+), 125 deletions(-) delete mode 100644 core/test/units/urls_test.rb diff --git a/core/lib/compass/core/asset_url_resolver.rb b/core/lib/compass/core/asset_url_resolver.rb index 8224175ce2..fd9a73bdf9 100644 --- a/core/lib/compass/core/asset_url_resolver.rb +++ b/core/lib/compass/core/asset_url_resolver.rb @@ -127,18 +127,4 @@ def default_cache_buster(path, real_path) end end - 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/http_util.rb b/core/lib/compass/core/http_util.rb index 216758cb90..3ea14f5fd3 100644 --- a/core/lib/compass/core/http_util.rb +++ b/core/lib/compass/core/http_util.rb @@ -38,4 +38,20 @@ def expand_url_path(url) 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/urls.rb b/core/lib/compass/core/sass_extensions/functions/urls.rb index 90e2dd7ed1..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 @@ -80,12 +74,15 @@ def generated_image_url(path, cache_buster = bool(false)) private + 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 resolve_asset_url(type, path, only_path, cache_buster) path = path.value # get to the string value of the literal. - css_file = nil - if Compass.configuration.relative_assets? && options[:css_filename] && options[:css_filename].start_with?("#{Compass.configuration.css_path}/") - css_file = url_join(Compass.configuration.http_stylesheets_path, options[:css_filename][(Compass.configuration.css_path.size + 1)..-1]) - end + 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) @@ -93,77 +90,4 @@ def resolve_asset_url(type, path, only_path, cache_buster) unquoted_string("url('#{url}')") end end - - # 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 - 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]}" - else - path - 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/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 From 9ecba96aa93ddb30ab510baccb8b8d6d7b40a861 Mon Sep 17 00:00:00 2001 From: Chris Eppstein Date: Mon, 25 Aug 2014 12:27:12 -0700 Subject: [PATCH 6/9] Allow sass-based configuration of asset collections. --- core/lib/compass/configuration.rb | 1 + .../compass/configuration/asset_collection.rb | 2 +- .../functions/configuration.rb | 42 +++++++++++++++++-- core/lib/compass/util.rb | 1 + .../projects/relative/css/screen.css | 6 ++- .../relative/sass/_project-setup.scss | 36 +++++++++++++++- .../projects/relative/sass/screen.sass | 7 ++++ .../asset-collection-1/fnt/font-one.woff | 0 .../asset-collection-1/img/image-one.png | 0 .../asset-collection-2/assets/font-two.ttf | 0 .../asset-collection-2/assets/image-two.jpg | 0 .../assets/subdir/image-three.gif | 0 core/test/units/util_test.rb | 12 ++++++ 13 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 core/test/integrations/projects/relative/vendor/asset-collection-1/fnt/font-one.woff create mode 100644 core/test/integrations/projects/relative/vendor/asset-collection-1/img/image-one.png create mode 100644 core/test/integrations/projects/relative/vendor/asset-collection-2/assets/font-two.ttf create mode 100644 core/test/integrations/projects/relative/vendor/asset-collection-2/assets/image-two.jpg create mode 100644 core/test/integrations/projects/relative/vendor/asset-collection-2/assets/subdir/image-three.gif diff --git a/core/lib/compass/configuration.rb b/core/lib/compass/configuration.rb index 4ace707885..8d97b03e42 100644 --- a/core/lib/compass/configuration.rb +++ b/core/lib/compass/configuration.rb @@ -94,6 +94,7 @@ def remove_configuration_property(name) } RUNTIME_READONLY_ATTRIBUTES = [ + :additional_import_paths, :cache, attributes_for_directory(:cache, nil), :chunky_png_options, diff --git a/core/lib/compass/configuration/asset_collection.rb b/core/lib/compass/configuration/asset_collection.rb index 714eb93057..4332612ced 100644 --- a/core/lib/compass/configuration/asset_collection.rb +++ b/core/lib/compass/configuration/asset_collection.rb @@ -115,11 +115,11 @@ class Compass::Configuration::AssetCollection < Compass::Configuration::Abstract # 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) - symbolize_keys!(options) 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 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/util.rb b/core/lib/compass/util.rb index f617334605..973f2d2e3f 100644 --- a/core/lib/compass/util.rb +++ b/core/lib/compass/util.rb @@ -17,6 +17,7 @@ def blank?(value) 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(", ")}" 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/util_test.rb b/core/test/units/util_test.rb index 030ac1a6ae..5324f7cf8e 100644 --- a/core/test/units/util_test.rb +++ b/core/test/units/util_test.rb @@ -2,6 +2,7 @@ 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 @@ -22,6 +23,17 @@ def test_assert_valid_keys 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 From 2ee03e85a84a659695a3c87b4294161e838411bd Mon Sep 17 00:00:00 2001 From: Chris Eppstein Date: Mon, 25 Aug 2014 15:03:28 -0700 Subject: [PATCH 7/9] Put sass files from an asset collection on the load path. --- cli/test/fixtures/stylesheets/image_urls/config.rb | 7 +++++++ cli/test/fixtures/stylesheets/image_urls/css/screen.css | 2 ++ .../image_urls/other/asset_collection/img/ac-img-1.jpg | 0 .../other/asset_collection/scss/_ac-stylesheet.scss | 1 + cli/test/fixtures/stylesheets/image_urls/sass/screen.sass | 2 ++ core/lib/compass/configuration/adapters.rb | 5 ++++- 6 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 cli/test/fixtures/stylesheets/image_urls/other/asset_collection/img/ac-img-1.jpg create mode 100644 cli/test/fixtures/stylesheets/image_urls/other/asset_collection/scss/_ac-stylesheet.scss 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/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) From 6aea71ee65222b3fac273284e1b50a15f982447f Mon Sep 17 00:00:00 2001 From: Chris Eppstein Date: Tue, 26 Aug 2014 01:06:57 -0700 Subject: [PATCH 8/9] CHANGELOG entry for asset collections. --- compass-style.org/content/CHANGELOG.markdown | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/compass-style.org/content/CHANGELOG.markdown b/compass-style.org/content/CHANGELOG.markdown index 4a7e2610ad..d1abb22f88 100644 --- a/compass-style.org/content/CHANGELOG.markdown +++ b/compass-style.org/content/CHANGELOG.markdown @@ -16,6 +16,22 @@ 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. + + 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. For full + documentation see [XXX](TODO). + + 1.0.1 (08/19/2014) ------------------ From 0d233201c17f22bf70f4c29153e0bacf9d58cc91 Mon Sep 17 00:00:00 2001 From: Chris Eppstein Date: Tue, 26 Aug 2014 11:20:08 -0700 Subject: [PATCH 9/9] Document asset collections. --- compass-style.org/content/CHANGELOG.markdown | 8 ++- .../configuration-reference.markdown | 62 +++++++++++++++++++ .../sass-based-configuration-options.markdown | 8 +++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/compass-style.org/content/CHANGELOG.markdown b/compass-style.org/content/CHANGELOG.markdown index d1abb22f88..e94598e1bb 100644 --- a/compass-style.org/content/CHANGELOG.markdown +++ b/compass-style.org/content/CHANGELOG.markdown @@ -24,12 +24,14 @@ Every release contains updated caniuse data unless otherwise noted. 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. + 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. For full - documentation see [XXX](TODO). + 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