Browse files

Add custom sass/css template

  • Loading branch information...
1 parent 669e261 commit 8341a4fb179bf1f794de9ed749f9215c33eadc49 @josh josh committed Dec 19, 2011
View
7 lib/sprockets.rb
@@ -23,6 +23,9 @@ module Sprockets
autoload :JstProcessor, "sprockets/jst_processor"
autoload :Processor, "sprockets/processor"
autoload :SafetyColons, "sprockets/safety_colons"
+ autoload :SassImporter, "sprockets/sass_importer"
+ autoload :SassTemplate, "sprockets/sass_template"
+ autoload :ScssTemplate, "sprockets/scss_template"
# Internal utilities
autoload :ArgumentError, "sprockets/errors"
@@ -55,8 +58,8 @@ module Cache
# CSS engines
register_engine '.less', Tilt::LessTemplate
- register_engine '.sass', Tilt::SassTemplate
- register_engine '.scss', Tilt::ScssTemplate
+ register_engine '.sass', SassTemplate
+ register_engine '.scss', ScssTemplate
# Other
register_engine '.erb', Tilt::ERBTemplate
View
2 lib/sprockets/context.rb
@@ -101,7 +101,7 @@ def resolve(path, options = {}, &block)
raise FileNotFound, "couldn't find file '#{path}'"
else
- environment.resolve(path, :base_path => self.pathname.dirname, &block)
+ environment.resolve(path, {:base_path => self.pathname.dirname}.merge(options), &block)
end
end
View
80 lib/sprockets/sass_importer.rb
@@ -0,0 +1,80 @@
+module Sprockets
+ class SassImporter
+ attr_reader :context
+
+ def initialize(context)
+ @context = context
+ end
+
+ def evaluate(pathname, options)
+ context.depend_on_asset(pathname)
+
+ options = options.merge(:filename => pathname.to_s, :syntax => :scss)
+ syntax = pathname.extname[/\w+$/].to_sym
+
+ case syntax
+ when :sass, :scss
+ ::Sass::Engine.new(pathname.read, options.merge(:syntax => syntax))
+ else
+ ::Sass::Engine.new(context.evaluate(pathname), options)
+ end
+ end
+
+ def resolve_relative(path, basepath)
+ dirname, basename = File.split(path)
+
+ resolve("#{dirname}/_#{basename}", :base_path => basepath) ||
+ resolve("#{dirname}/#{basename}", :base_path => basepath)
+ end
+
+ def resolve_loadpath(path)
+ return if path.to_s =~ /^\.\.?\//
+ dirname, basename = File.split(path)
+
+ if dirname == '.'
+ resolve("_#{basename}") || resolve("#{basename}")
+ else
+ resolve("#{dirname}/_#{basename}") || resolve("#{dirname}/#{basename}")
+ end
+ end
+
+ def resolve(path, options = {})
+ context.resolve(path, {:content_type => :self}.merge(options))
+ rescue Sprockets::FileNotFound
+ nil
+ end
+
+ def find_relative(path, base, options)
+ if pathname = resolve_relative(path, :base_path => File.dirname(base))
+ evaluate(pathname, options)
+ end
+ end
+
+ def find(path, options)
+ if pathname = resolve_loadpath(path)
+ evaluate(pathname, options)
+ end
+ end
+
+ def mtime(path, options)
+ if pathname = resolve_loadpath(path)
+ pathname.mtime
+ end
+ end
+
+ def key(name, options)
+ ["Sprockets:" + File.dirname(File.expand_path(name)), File.basename(name)]
+ end
+
+ # Pretty inspect
+ def inspect
+ "#<#{self.class}:0x#{object_id.to_s(16)} " +
+ "logical_path=#{context.logical_path.to_s.inspect}" +
+ ">"
+ end
+
+ def to_s
+ inspect
+ end
+ end
+end
View
42 lib/sprockets/sass_template.rb
@@ -0,0 +1,42 @@
+require 'tilt'
+require 'sprockets/sass_importer'
+
+module Sprockets
+ class SassTemplate < Tilt::Template
+ self.default_mime_type = 'text/css'
+
+ def self.engine_initialized?
+ defined? ::Sass::Engine
+ end
+
+ def initialize_engine
+ require_template_library 'sass'
+ end
+
+ def prepare
+ end
+
+ def syntax
+ :sass
+ end
+
+ def evaluate(context, locals, &block)
+ importer = SassImporter.new(context)
+
+ options = {
+ :filename => eval_file,
+ :line => line,
+ :syntax => syntax,
+ :cache => false,
+ :read_cache => false,
+ :importer => importer,
+ :load_paths => [importer]
+ }
+
+ ::Sass::Engine.new(data, options).render
+ rescue ::Sass::SyntaxError => e
+ context.__LINE__ = e.sass_backtrace.first[:line]
+ raise e
+ end
+ end
+end
View
11 lib/sprockets/scss_template.rb
@@ -0,0 +1,11 @@
+require 'sprockets/sass_template'
+
+module Sprockets
+ class ScssTemplate < SassTemplate
+ self.default_mime_type = 'text/css'
+
+ def syntax
+ :scss
+ end
+ end
+end
View
1 sprockets.gemspec
@@ -22,6 +22,7 @@ Gem::Specification.new do |s|
s.add_development_dependency "json"
s.add_development_dependency "rack-test"
s.add_development_dependency "rake"
+ s.add_development_dependency "sass", "~> 3.1"
s.authors = ["Sam Stephenson", "Joshua Peek"]
s.email = ["sstephenson@gmail.com", "josh@joshpeek.com"]
View
1 test/fixtures/compass/_compass.scss
@@ -0,0 +1 @@
+@import "compass/css3";
View
6 test/fixtures/compass/compass/css3.scss
@@ -0,0 +1,6 @@
+@mixin box-sizing($bs) {
+ $bs: unquote($bs);
+ @include experimental(box-sizing, $bs,
+ -moz, -webkit, not -o, -ms, not -khtml, official
+ );
+}
View
5 test/fixtures/sass/_rounded.scss
@@ -0,0 +1,5 @@
+@mixin rounded($side, $radius: 10px) {
+ border-#{$side}-radius: $radius;
+ -moz-border-radius-#{$side}: $radius;
+ -webkit-border-#{$side}-radius: $radius;
+}
View
1 test/fixtures/sass/import_css.scss
@@ -0,0 +1 @@
+@import "reset";
View
1 test/fixtures/sass/import_load_path.scss
@@ -0,0 +1 @@
+@import "compass";
View
1 test/fixtures/sass/import_nonpartial.scss
@@ -0,0 +1 @@
+@import "variables";
View
10 test/fixtures/sass/import_partial.sass
@@ -0,0 +1,10 @@
+@import "rounded"
+
+#navbar li
+ @include rounded(top)
+
+#footer
+ @include rounded(top, 5px)
+
+#sidebar
+ @include rounded(left, 8px)
View
5 test/fixtures/sass/import_partial.scss
@@ -0,0 +1,5 @@
+@import "rounded";
+
+#navbar li { @include rounded(top); }
+#footer { @include rounded(top, 5px); }
+#sidebar { @include rounded(left, 8px); }
View
14 test/fixtures/sass/nesting.scss
@@ -0,0 +1,14 @@
+table.hl {
+ margin: 2em 0;
+ td.ln {
+ text-align: right;
+ }
+}
+
+li {
+ font: {
+ family: serif;
+ weight: bold;
+ size: 1.2em;
+ }
+}
View
3 test/fixtures/sass/reset.css
@@ -0,0 +1,3 @@
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block; }
View
5 test/fixtures/sass/shared/relative.scss
@@ -0,0 +1,5 @@
+@import "../rounded";
+
+#navbar li { @include rounded(top); }
+#footer { @include rounded(top, 5px); }
+#sidebar { @include rounded(left, 8px); }
View
11 test/fixtures/sass/variables.sass
@@ -0,0 +1,11 @@
+$blue: #3bbfce
+$margin: 16px
+
+.content-navigation
+ border-color: $blue
+ color: darken($blue, 9%)
+
+.border
+ padding: $margin / 2
+ margin: $margin / 2
+ border-color: $blue
View
6 test/sprockets_test.rb
@@ -24,7 +24,11 @@ def fixture(path)
end
def fixture_path(path)
- File.join(FIXTURE_ROOT, path)
+ if path.match(FIXTURE_ROOT)
+ path
+ else
+ File.join(FIXTURE_ROOT, path)
+ end
end
def sandbox(*paths)
View
232 test/test_sass.rb
@@ -0,0 +1,232 @@
+require 'sprockets_test'
+
+class TestTiltSass < Sprockets::TestCase
+ CACHE_PATH = File.expand_path("../../.sass-cache", __FILE__)
+ COMPASS_PATH = File.join(FIXTURE_ROOT, 'compass')
+
+ class SassTemplate < Tilt::SassTemplate
+ def sass_options
+ options.merge({:filename => eval_file, :line => line, :syntax => :sass, :load_paths => [COMPASS_PATH]})
+ end
+ end
+
+ class ScssTemplate < Tilt::ScssTemplate
+ def sass_options
+ options.merge({:filename => eval_file, :line => line, :syntax => :scss, :load_paths => [COMPASS_PATH]})
+ end
+ end
+
+ def setup
+ # Sass is a whiny little bitch
+ silence_warnings do
+ require 'sass'
+ end
+ end
+
+ def teardown
+ FileUtils.rm_r(CACHE_PATH)
+ assert !File.exist?(CACHE_PATH)
+ end
+
+ def render(path)
+ path = fixture_path(path)
+ silence_warnings do
+ case File.extname(path)
+ when '.sass'
+ SassTemplate.new(path).render
+ when '.scss', '.css'
+ ScssTemplate.new(path).render
+ end
+ end
+ end
+
+ test "process variables" do
+ assert_equal <<-EOS, render('sass/variables.sass')
+.content-navigation {
+ border-color: #3bbfce;
+ color: #2ca2af; }
+
+.border {
+ padding: 8px;
+ margin: 8px;
+ border-color: #3bbfce; }
+ EOS
+ end
+
+ test "process nesting" do
+ assert_equal <<-EOS, render('sass/nesting.scss')
+table.hl {
+ margin: 2em 0; }
+ table.hl td.ln {
+ text-align: right; }
+
+li {
+ font-family: serif;
+ font-weight: bold;
+ font-size: 1.2em; }
+ EOS
+ end
+
+ test "@import scss partial from scss" do
+ assert_equal <<-EOS, render('sass/import_partial.scss')
+#navbar li {
+ border-top-radius: 10px;
+ -moz-border-radius-top: 10px;
+ -webkit-border-top-radius: 10px; }
+
+#footer {
+ border-top-radius: 5px;
+ -moz-border-radius-top: 5px;
+ -webkit-border-top-radius: 5px; }
+
+#sidebar {
+ border-left-radius: 8px;
+ -moz-border-radius-left: 8px;
+ -webkit-border-left-radius: 8px; }
+ EOS
+ end
+
+ test "@import scss partial from sass" do
+ assert_equal <<-EOS, render('sass/import_partial.sass')
+#navbar li {
+ border-top-radius: 10px;
+ -moz-border-radius-top: 10px;
+ -webkit-border-top-radius: 10px; }
+
+#footer {
+ border-top-radius: 5px;
+ -moz-border-radius-top: 5px;
+ -webkit-border-top-radius: 5px; }
+
+#sidebar {
+ border-left-radius: 8px;
+ -moz-border-radius-left: 8px;
+ -webkit-border-left-radius: 8px; }
+ EOS
+ end
+
+ test "@import prefers partial over fullname" do
+ filename = fixture_path('sass/test.scss')
+ partial, other = fixture_path('sass/_partial.scss'), fixture_path('sass/partial.scss')
+
+ sandbox filename, partial, other do
+ File.open(filename, 'w') { |f| f.write "@import 'partial';" }
+ File.open(partial, 'w') { |f| f.write ".partial { background: red; };" }
+ File.open(other, 'w') { |f| f.write ".partial { background: blue; };" }
+ assert_equal ".partial {\n background: red; }\n", render(filename)
+ end
+ end
+
+ test "@import sass non-partial from scss" do
+ assert_equal <<-EOS, render('sass/import_nonpartial.scss')
+.content-navigation {
+ border-color: #3bbfce;
+ color: #2ca2af; }
+
+.border {
+ padding: 8px;
+ margin: 8px;
+ border-color: #3bbfce; }
+ EOS
+ end
+
+ test "@import css file from load path" do
+ assert_equal <<-EOS, render('sass/import_load_path.scss')
+ EOS
+ end
+
+ test "process css file" do
+ assert_equal <<-EOS, render('sass/reset.css')
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block; }
+ EOS
+ end
+
+ test "@import relative file" do
+ assert_equal <<-EOS, render('sass/shared/relative.scss')
+#navbar li {
+ border-top-radius: 10px;
+ -moz-border-radius-top: 10px;
+ -webkit-border-top-radius: 10px; }
+
+#footer {
+ border-top-radius: 5px;
+ -moz-border-radius-top: 5px;
+ -webkit-border-top-radius: 5px; }
+
+#sidebar {
+ border-left-radius: 8px;
+ -moz-border-radius-left: 8px;
+ -webkit-border-left-radius: 8px; }
+ EOS
+ end
+
+ test "modify file causes it to recompile" do
+ filename = fixture_path('sass/test.scss')
+
+ sandbox filename do
+ File.open(filename, 'w') { |f| f.write "body { background: red; };" }
+ assert_equal "body {\n background: red; }\n", render(filename)
+
+ File.open(filename, 'w') { |f| f.write "body { background: blue; };" }
+ mtime = Time.now + 1
+ File.utime(mtime, mtime, filename)
+
+ assert_equal "body {\n background: blue; }\n", render(filename)
+ end
+ end
+
+ test "modify partial causes it to recompile" do
+ filename, partial = fixture_path('sass/test.scss'), fixture_path('sass/_partial.scss')
+
+ sandbox filename, partial do
+ File.open(filename, 'w') { |f| f.write "@import 'partial';" }
+ File.open(partial, 'w') { |f| f.write "body { background: red; };" }
+ assert_equal "body {\n background: red; }\n", render(filename)
+
+ File.open(partial, 'w') { |f| f.write "body { background: blue; };" }
+ mtime = Time.now + 1
+ File.utime(mtime, mtime, partial)
+
+ assert_equal "body {\n background: blue; }\n", render(filename)
+ end
+ end
+
+ def silence_warnings
+ old_verbose, $VERBOSE = $VERBOSE, false
+ yield
+ ensure
+ $VERBOSE = old_verbose
+ end
+end
+
+class TestSprocketsSass < TestTiltSass
+ def setup
+ super
+
+ @env = Sprockets::Environment.new(".") do |env|
+ env.append_path(fixture_path('sass'))
+ env.append_path(fixture_path('compass'))
+ end
+ end
+
+ def teardown
+ assert !File.exist?(CACHE_PATH)
+ end
+
+ def render(path)
+ path = fixture_path(path)
+ silence_warnings do
+ @env[path].to_s
+ end
+ end
+
+ # For some reason Sass doesn't like importing other css files
+ test "@import css from scss" do
+ assert_equal <<-EOS, render('sass/import_css.scss')
+article, aside, details, figcaption, figure,\nfooter, header, hgroup, menu, nav, section {
+ display: block; }
+ EOS
+ end
+end

0 comments on commit 8341a4f

Please sign in to comment.