asset-trip 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/History.txt +6 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.rdoc +27 -0
- data/Rakefile +12 -0
- data/Thorfile +110 -0
- data/asset-trip.gemspec +107 -0
- data/init.rb +3 -0
- data/lib/asset_trip/asset.rb +82 -0
- data/lib/asset_trip/compressor.rb +36 -0
- data/lib/asset_trip/config.rb +60 -0
- data/lib/asset_trip/file_writer.rb +17 -0
- data/lib/asset_trip/helper.rb +83 -0
- data/lib/asset_trip/javascript.rb +29 -0
- data/lib/asset_trip/load_path.rb +39 -0
- data/lib/asset_trip/manifest.rb +40 -0
- data/lib/asset_trip/manifest_writer.rb +35 -0
- data/lib/asset_trip/memoizable.rb +28 -0
- data/lib/asset_trip/middleware.rb +109 -0
- data/lib/asset_trip/ssl_stylesheet.rb +15 -0
- data/lib/asset_trip/stylesheet.rb +44 -0
- data/lib/asset_trip/url_rewriter.rb +82 -0
- data/lib/asset_trip.rb +66 -0
- data/spec/asset_trip/asset_spec.rb +47 -0
- data/spec/asset_trip/compressor_spec.rb +46 -0
- data/spec/asset_trip/config_spec.rb +61 -0
- data/spec/asset_trip/helper_spec.rb +221 -0
- data/spec/asset_trip/javascript_spec.rb +24 -0
- data/spec/asset_trip/load_path_spec.rb +61 -0
- data/spec/asset_trip/manifest_writer_spec.rb +32 -0
- data/spec/asset_trip/middleware_spec.rb +72 -0
- data/spec/asset_trip/stylesheet_spec.rb +51 -0
- data/spec/asset_trip/url_rewriter_spec.rb +166 -0
- data/spec/asset_trip_spec.rb +13 -0
- data/spec/fixtures/app/javascripts/main/new.js +1 -0
- data/spec/fixtures/app/javascripts/main.js +1 -0
- data/spec/fixtures/app/javascripts/signup.js +1 -0
- data/spec/fixtures/app/stylesheets/new.css +0 -0
- data/spec/fixtures/config/asset_trip/assets.rb +0 -0
- data/spec/integration/bundle_spec.rb +243 -0
- data/spec/integration/prune_spec.rb +53 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/core_extensions.rb +13 -0
- data/spec/support/helpers.rb +33 -0
- data/spec/support/matchers.rb +67 -0
- data/spec/support/path_utils.rb +45 -0
- data/spec/support/sandbox_helper.rb +19 -0
- data/tasks/asset_trip.rake +11 -0
- data/vendor/yuicompressor-2.4.2.jar +0 -0
- metadata +128 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
module AssetTrip
|
2
|
+
class ManifestWriter
|
3
|
+
|
4
|
+
def initialize(assets)
|
5
|
+
@assets = assets
|
6
|
+
end
|
7
|
+
|
8
|
+
def write!
|
9
|
+
FileWriter.new(path).write!(contents)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def contents
|
15
|
+
source = StringIO.new
|
16
|
+
|
17
|
+
source.puts "module AssetTrip"
|
18
|
+
source.puts " @manifest = Manifest.new"
|
19
|
+
|
20
|
+
@assets.each do |asset|
|
21
|
+
source.puts " @manifest[#{asset.name.inspect}] = #{asset.md5sum.first(11).inspect}"
|
22
|
+
end
|
23
|
+
|
24
|
+
source.puts "end"
|
25
|
+
|
26
|
+
return source.string
|
27
|
+
end
|
28
|
+
|
29
|
+
def path
|
30
|
+
AssetTrip.app_root.join("config", "asset_trip", "manifest.rb")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module AssetTrip
|
2
|
+
module Memoizable
|
3
|
+
|
4
|
+
def self.memoized_ivar_for(symbol)
|
5
|
+
"@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}".to_sym
|
6
|
+
end
|
7
|
+
|
8
|
+
def memoize(symbol)
|
9
|
+
original_method = :"_unmemoized_#{symbol}"
|
10
|
+
memoized_ivar = AssetTrip::Memoizable.memoized_ivar_for(symbol)
|
11
|
+
|
12
|
+
raise "Already memoized #{symbol}" if method_defined?(original_method)
|
13
|
+
alias_method original_method, symbol
|
14
|
+
|
15
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
16
|
+
def #{symbol}(*args) # def mime_type(*args)
|
17
|
+
if !defined?(#{memoized_ivar}) # if !defined?(@_memoized_mime_type)
|
18
|
+
#{memoized_ivar} = #{original_method}(*args) # @_memoized_mime_type = _unmemoized_mime_type(*args)
|
19
|
+
end # end
|
20
|
+
#{memoized_ivar} # @_memoized_mime_type
|
21
|
+
end # end
|
22
|
+
EOS
|
23
|
+
|
24
|
+
private symbol if private_method_defined?(original_method)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# This is based on Rack::Static and Rack::File
|
2
|
+
module AssetTrip
|
3
|
+
class Middleware
|
4
|
+
URL_PREFIX = "/__asset_trip__"
|
5
|
+
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
dup._call(env)
|
12
|
+
end
|
13
|
+
|
14
|
+
def _call(env)
|
15
|
+
@env = env
|
16
|
+
|
17
|
+
if matches_url_prefix?
|
18
|
+
process
|
19
|
+
else
|
20
|
+
pass
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def each
|
25
|
+
File.open(path, "rb") do |file|
|
26
|
+
while part = file.read(8192)
|
27
|
+
yield part
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def matches_url_prefix?
|
35
|
+
path_info.index(URL_PREFIX) == 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def process
|
39
|
+
return forbidden if path_info.include?("..")
|
40
|
+
|
41
|
+
if path.file? && path.readable?
|
42
|
+
serve_file
|
43
|
+
else
|
44
|
+
not_found
|
45
|
+
end
|
46
|
+
rescue UnknownAssetError
|
47
|
+
return not_found
|
48
|
+
end
|
49
|
+
|
50
|
+
def pass
|
51
|
+
@app.call(@env)
|
52
|
+
end
|
53
|
+
|
54
|
+
def path
|
55
|
+
@path ||= AssetTrip.config.resolve_file(asset_type.to_sym, filename)
|
56
|
+
end
|
57
|
+
|
58
|
+
def filename
|
59
|
+
@filename ||= path_info[("/#{URL_PREFIX}/#{asset_type}".size)..-1]
|
60
|
+
end
|
61
|
+
|
62
|
+
def path_info
|
63
|
+
@path_info ||= Rack::Utils.unescape(@env["PATH_INFO"])
|
64
|
+
end
|
65
|
+
|
66
|
+
def asset_type
|
67
|
+
path_parts[2]
|
68
|
+
end
|
69
|
+
|
70
|
+
def path_parts
|
71
|
+
path_info.split("/")
|
72
|
+
end
|
73
|
+
|
74
|
+
def serve_file
|
75
|
+
# NOTE:
|
76
|
+
# We check via File::size? whether this file provides size info
|
77
|
+
# via stat (e.g. /proc files often don't), otherwise we have to
|
78
|
+
# figure it out by reading the whole file into memory. And while
|
79
|
+
# we're at it we also use this as body then.
|
80
|
+
if size = File.size?(@path)
|
81
|
+
body = self
|
82
|
+
else
|
83
|
+
body = [File.read(@path)]
|
84
|
+
size = Rack::Utils.bytesize(body.first)
|
85
|
+
end
|
86
|
+
|
87
|
+
[200, {
|
88
|
+
"Last-Modified" => File.mtime(@path).httpdate,
|
89
|
+
"Content-Type" => Rack::Mime.mime_type(File.extname(@path), 'text/plain'),
|
90
|
+
"Content-Length" => size.to_s
|
91
|
+
}, body]
|
92
|
+
end
|
93
|
+
|
94
|
+
def forbidden
|
95
|
+
body = "Forbidden\n"
|
96
|
+
[403, {"Content-Type" => "text/plain",
|
97
|
+
"Content-Length" => body.size.to_s},
|
98
|
+
[body]]
|
99
|
+
end
|
100
|
+
|
101
|
+
def not_found
|
102
|
+
body = "File not found: #{@path_info}\n"
|
103
|
+
[404, {"Content-Type" => "text/plain",
|
104
|
+
"Content-Length" => body.size.to_s},
|
105
|
+
[body]]
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module AssetTrip
|
2
|
+
class Stylesheet < Asset
|
3
|
+
|
4
|
+
def name
|
5
|
+
"#{@name}.css"
|
6
|
+
end
|
7
|
+
|
8
|
+
def ssl_stylesheet
|
9
|
+
SSLStylesheet.new(@config, @name, @files)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def joined_contents
|
15
|
+
paths.map do |path|
|
16
|
+
url_rewriter(path).rewrite(File.read(path))
|
17
|
+
end.join("\n\n")
|
18
|
+
end
|
19
|
+
|
20
|
+
def compressor
|
21
|
+
Compressor.new("css")
|
22
|
+
end
|
23
|
+
|
24
|
+
def url_rewriter(filesystem_path)
|
25
|
+
public_path = AssetTrip.app_root.join("public")
|
26
|
+
|
27
|
+
if filesystem_path.to_s.starts_with?(public_path)
|
28
|
+
foo = Pathname.new("/").join(filesystem_path.relative_path_from(public_path))
|
29
|
+
UrlRewriter.new("http", foo)
|
30
|
+
else
|
31
|
+
UrlRewriter.new("http")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def asset_type
|
36
|
+
:stylesheets
|
37
|
+
end
|
38
|
+
|
39
|
+
def extension
|
40
|
+
".css"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "action_controller"
|
2
|
+
require "action_view"
|
3
|
+
require "ostruct"
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
module AssetTrip
|
7
|
+
class UrlRewriter
|
8
|
+
include ActionView::Helpers::AssetTagHelper
|
9
|
+
|
10
|
+
def initialize(scheme, stylesheet_path = nil)
|
11
|
+
@scheme = scheme
|
12
|
+
@stylesheet_path = stylesheet_path
|
13
|
+
|
14
|
+
setup_fake_controller_for_asset_host_computation
|
15
|
+
end
|
16
|
+
|
17
|
+
def rewrite(contents)
|
18
|
+
contents.gsub(/url *\(([^\)]+)\)/) do
|
19
|
+
"url(#{add_asset_host_to_path($1)})"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def setup_fake_controller_for_asset_host_computation
|
26
|
+
environment = {}
|
27
|
+
environment["HTTPS"] = "on" if @scheme == "https"
|
28
|
+
@controller = OpenStruct.new(:request => ActionController::Request.new(environment)) # Used by Rails compute_asset_host method from ActionView::Helpers::AssetTagHelper
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_asset_host_to_path(path)
|
32
|
+
strip_quotes!(path)
|
33
|
+
|
34
|
+
if prepend_asset_host?(path)
|
35
|
+
path = rewrite_relative_path(path) unless @stylesheet_path.blank?
|
36
|
+
URI::Generic.build(uri_components(path)).to_s
|
37
|
+
else
|
38
|
+
path
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def uri_components(path)
|
43
|
+
opts = { :path => path }
|
44
|
+
|
45
|
+
if (asset_id = rails_asset_id(path)).present?
|
46
|
+
opts[:query] = asset_id
|
47
|
+
end
|
48
|
+
|
49
|
+
if (host = compute_asset_host(path)).present?
|
50
|
+
opts[:host] = strip_scheme(host)
|
51
|
+
opts[:scheme] = @scheme
|
52
|
+
end
|
53
|
+
|
54
|
+
return opts
|
55
|
+
end
|
56
|
+
|
57
|
+
def rewrite_relative_path(relative_url)
|
58
|
+
if relative_url.starts_with?("/")
|
59
|
+
return relative_url
|
60
|
+
else
|
61
|
+
Pathname.new(File.join(@stylesheet_path.dirname, relative_url)).cleanpath
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def prepend_asset_host?(path)
|
66
|
+
uri = URI.parse(path)
|
67
|
+
|
68
|
+
uri.relative? &&
|
69
|
+
File.extname(uri.path) != '.htc'
|
70
|
+
end
|
71
|
+
|
72
|
+
def strip_scheme(host)
|
73
|
+
host.gsub(/^[a-z]+:\/\//, '')
|
74
|
+
end
|
75
|
+
|
76
|
+
def strip_quotes!(path)
|
77
|
+
path.gsub!(/^\s*['"]*/, "")
|
78
|
+
path.gsub!(/['"]*\s*$/, "")
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
data/lib/asset_trip.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require "active_support"
|
2
|
+
|
3
|
+
module AssetTrip
|
4
|
+
autoload :Asset, "asset_trip/asset"
|
5
|
+
autoload :Compressor, "asset_trip/compressor"
|
6
|
+
autoload :Config, "asset_trip/config"
|
7
|
+
autoload :FileWriter, "asset_trip/file_writer"
|
8
|
+
autoload :Helper, "asset_trip/helper"
|
9
|
+
autoload :Javascript, "asset_trip/javascript"
|
10
|
+
autoload :LoadPath, "asset_trip/load_path"
|
11
|
+
autoload :Manifest, "asset_trip/manifest"
|
12
|
+
autoload :ManifestWriter, "asset_trip/manifest_writer"
|
13
|
+
autoload :Middleware, "asset_trip/middleware"
|
14
|
+
autoload :Memoizable, "asset_trip/memoizable"
|
15
|
+
autoload :SSLStylesheet, "asset_trip/ssl_stylesheet"
|
16
|
+
autoload :Stylesheet, "asset_trip/stylesheet"
|
17
|
+
autoload :UrlRewriter, "asset_trip/url_rewriter"
|
18
|
+
|
19
|
+
class CompressorError < StandardError
|
20
|
+
end
|
21
|
+
|
22
|
+
class UnknownAssetError < StandardError
|
23
|
+
end
|
24
|
+
|
25
|
+
class NoManifestError < StandardError
|
26
|
+
end
|
27
|
+
|
28
|
+
VERSION = "0.1.0"
|
29
|
+
|
30
|
+
mattr_accessor :bundle
|
31
|
+
self.bundle = true
|
32
|
+
|
33
|
+
def self.bundle!
|
34
|
+
config.bundle!
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.prune!
|
38
|
+
manifest.prune!
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.config
|
42
|
+
@config ||= Config.from_file(config_path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.manifest
|
46
|
+
raise NoManifestError if @manifest.nil?
|
47
|
+
@manifest
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.app_root
|
51
|
+
Pathname.new(".").expand_path
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.root
|
55
|
+
Pathname.new(__FILE__).dirname.join("..").expand_path
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.assets_path
|
59
|
+
app_root.join("public", "assets")
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.config_path
|
63
|
+
app_root.join("config", "asset_trip")
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe AssetTrip::Asset do
|
4
|
+
before do
|
5
|
+
AssetTrip::Compressor.stub!(:new => DummyCompressor.new)
|
6
|
+
File.stub!(:read => "contents")
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#bundle!" do
|
10
|
+
it "splits the MD5 into two nested directories" do
|
11
|
+
path = AssetTrip.assets_path.join("d4", "1d8cd98f0", "asset.js")
|
12
|
+
AssetTrip::FileWriter.should_receive(:new).with(path).and_return(stub(:write! => nil))
|
13
|
+
asset = AssetTrip::Javascript.new(stub, "asset")
|
14
|
+
asset.bundle!
|
15
|
+
end
|
16
|
+
|
17
|
+
it "writes the contents to the file" do
|
18
|
+
writer = stub
|
19
|
+
writer.should_receive(:write!).with("contents")
|
20
|
+
AssetTrip::FileWriter.stub!(:new => writer)
|
21
|
+
asset = AssetTrip::Javascript.new(stub(:resolve_file => "foo.js"), "asset", [Pathname.new("foo.js")])
|
22
|
+
asset.bundle!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#md5sum" do
|
27
|
+
it "calculates the MD5 if there are no files" do
|
28
|
+
asset = AssetTrip::Javascript.new(stub, "asset")
|
29
|
+
asset.md5sum.should == "d41d8cd98f00b204e9800998ecf8427e"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "calculates the MD5 for one file" do
|
33
|
+
asset = AssetTrip::Javascript.new(stub(:resolve_file => "foo"), "asset") do
|
34
|
+
include "foo"
|
35
|
+
end
|
36
|
+
asset.md5sum.should == "98bf7d8c15784f0a3d63204441e1e2aa"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "calculates the MD5 for multiple file" do
|
40
|
+
asset = AssetTrip::Javascript.new(stub(:resolve_file => "foo"), "asset") do
|
41
|
+
include "foo"
|
42
|
+
include "bar"
|
43
|
+
end
|
44
|
+
asset.md5sum.should == "237c43254a0799392cac71f795d9250e"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe AssetTrip::Compressor do
|
4
|
+
let(:compressor) do
|
5
|
+
AssetTrip::Compressor.new(Pathname.new("css"))
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#compress" do
|
9
|
+
it "builds a command with the proper --type option" do
|
10
|
+
POpen4.should_receive(:popen4).with(/--type css/).and_return(stub(:success? => true))
|
11
|
+
compressor.compress("a { color: red }")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "builds a command to run the vendored jar file" do
|
15
|
+
POpen4.should_receive(:popen4).with(/java -jar .*vendor\/yuicompressor-2.4.2.jar/).and_return(stub(:success? => true))
|
16
|
+
compressor.compress("a { color: red }")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "sends the uncompressed contents via STDIN" do
|
20
|
+
stdin = stub(:close => nil)
|
21
|
+
|
22
|
+
POpen4.stub!(:popen4) do |command, block|
|
23
|
+
block.call(stub(:read => "STDOUT"), stub(:read => "STDERR"), stdin)
|
24
|
+
stub(:success? => true)
|
25
|
+
end
|
26
|
+
|
27
|
+
stdin.should_receive(:puts).with("a { color: red }")
|
28
|
+
compressor.compress("a { color: red }").should == "STDOUT"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "returns the STDOUT from the java process" do
|
32
|
+
POpen4.stub!(:popen4) do |command, block|
|
33
|
+
block.call(stub(:read => "STDOUT"), stub(:read => "STDERR"), stub(:puts => nil, :close => nil))
|
34
|
+
stub(:success? => true)
|
35
|
+
end
|
36
|
+
compressor.compress("a { color: red }").should == "STDOUT"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "raises a CompressorError if the java process is not successful" do
|
40
|
+
POpen4.stub!(:popen4 => stub(:success? => false))
|
41
|
+
lambda {
|
42
|
+
compressor.compress("a { color: red }")
|
43
|
+
}.should raise_error(AssetTrip::CompressorError)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe AssetTrip::Config do
|
4
|
+
# TODO: it would be great to avoid needing this for these specs
|
5
|
+
setup_sandbox_app!
|
6
|
+
|
7
|
+
it "can bundle files included with the file extension" do
|
8
|
+
config = AssetTrip::Config.new do
|
9
|
+
js_asset "signup" do
|
10
|
+
include "main.js"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
asset_config = config.assets.first
|
15
|
+
asset_config.paths.should == [app_javascript("main.js")]
|
16
|
+
end
|
17
|
+
|
18
|
+
it "can bundle files in subdirectories of the load path" do
|
19
|
+
config = AssetTrip::Config.new do
|
20
|
+
js_asset "signup" do
|
21
|
+
include "main/new"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
asset_config = config.assets.first
|
26
|
+
asset_config.paths.should == [app_javascript("main", "new.js")]
|
27
|
+
end
|
28
|
+
|
29
|
+
it "supports writing an asset to a subdirectory" do
|
30
|
+
config = AssetTrip::Config.new do
|
31
|
+
js_asset "signup/foo" do
|
32
|
+
include "main.js"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
asset_config = config.assets.first
|
37
|
+
asset_config.name.should == "signup/foo.js"
|
38
|
+
end
|
39
|
+
|
40
|
+
it "supports adding to the JS load paths" do
|
41
|
+
config = AssetTrip::Config.new do
|
42
|
+
load_paths[:javascripts] << "foo"
|
43
|
+
end
|
44
|
+
|
45
|
+
config.load_paths[:javascripts].should == AssetTrip::LoadPath.new([
|
46
|
+
Pathname.new("app/javascripts"),
|
47
|
+
Pathname.new("foo")
|
48
|
+
])
|
49
|
+
end
|
50
|
+
|
51
|
+
it "supports adding to the CSS load paths" do
|
52
|
+
config = AssetTrip::Config.new do
|
53
|
+
load_paths[:stylesheets] << "foo"
|
54
|
+
end
|
55
|
+
|
56
|
+
config.load_paths[:stylesheets].should == AssetTrip::LoadPath.new([
|
57
|
+
Pathname.new("app/stylesheets"),
|
58
|
+
Pathname.new("foo")
|
59
|
+
])
|
60
|
+
end
|
61
|
+
end
|