From df5ef38085baad851ecc9ad0bebef56554ac60fa Mon Sep 17 00:00:00 2001
From: Vikhyat Korrapati <vikhyatk@gmail.com>
Date: Tue, 1 Apr 2014 17:12:14 +0530
Subject: [PATCH] Use custom DiscourseSassImporter for site customization SCSS
 compilation.

---
 app/models/site_customization.rb       | 32 ++++--------
 lib/discourse_sass_importer.rb         | 71 ++++++++++++++++++++++++++
 spec/models/site_customization_spec.rb | 12 ++++-
 3 files changed, 90 insertions(+), 25 deletions(-)
 create mode 100644 lib/discourse_sass_importer.rb

diff --git a/app/models/site_customization.rb b/app/models/site_customization.rb
index ba9a98eb8..e345d7587 100644
--- a/app/models/site_customization.rb
+++ b/app/models/site_customization.rb
@@ -1,3 +1,5 @@
+require_dependency 'discourse_sass_importer'
+
 class SiteCustomization < ActiveRecord::Base
   ENABLED_KEY = '7e202ef2-56d7-47d5-98d8-a9c8d15e57dd'
   # placing this in uploads to ease deployment rules
@@ -12,29 +14,13 @@ class SiteCustomization < ActiveRecord::Base
   end
 
   def compile_stylesheet(scss)
-    stylesheets_path = Rails.root.join('app', 'assets', 'stylesheets')
-
-    # Get the sprockets environment. We need to do this because in production
-    # Rails.application.assets returns Sprockets::Index which does not compile
-    # assets.
-    sprockets = Rails.application.assets
-    if sprockets.is_a?(Sprockets::Index)
-      sprockets = sprockets.instance_variable_get('@environment')
-    end
-
-    file_path = stylesheets_path.join('custom_stylesheet.scss')
-
-    File.open(file_path, 'w') do |f|
-      f.write scss
-    end
-
-    begin
-      compiled = sprockets.find_asset('custom_stylesheet').body
-    ensure
-      FileUtils.rm file_path
-    end
-
-    compiled
+    ::Sass::Engine.new(scss, {
+      syntax: :scss,
+      cache: false,
+      read_cache: false,
+      style: :compressed,
+      filesystem_importer: DiscourseSassImporter
+    }).render
   end
 
   before_save do
diff --git a/lib/discourse_sass_importer.rb b/lib/discourse_sass_importer.rb
new file mode 100644
index 000000000..0ee01fef5
--- /dev/null
+++ b/lib/discourse_sass_importer.rb
@@ -0,0 +1,71 @@
+# This custom importer is used for site customizations. This is similar to the
+# Sprockets::SassImporter implementation provided in sass-rails since that is used
+# during asset precompilation.
+class DiscourseSassImporter < Sass::Importers::Filesystem
+  GLOB = /\*|\[.+\]/
+
+  def initialize(root)
+    @root = Rails.root.join('app', 'assets', 'stylesheets').to_s
+    @same_name_warnings = Set.new
+  end
+
+  def extensions
+    {
+      'css' => :scss,
+      'css.scss' => :scss,
+      'css.sass' => :sass,
+      'css.erb' => :scss,
+      'scss.erb' => :scss,
+      'sass.erb' => :sass,
+      'css.scss.erb' => :scss,
+      'css.sass.erb' => :sass
+    }.merge!(super)
+  end
+
+  def find_relative(name, base, options)
+    if name =~ GLOB
+      glob_imports(name, Pathname.new(base), options)
+    else
+      engine_from_path(name, File.dirname(base), options)
+    end
+  end
+
+  def find(name, options)
+    if name =~ GLOB
+      nil # globs must be relative
+    else
+      engine_from_path(name, root, options)
+    end
+  end
+
+  def each_globbed_file(glob, base_pathname, options)
+    Dir["#{base_pathname}/#{glob}"].sort.each do |filename|
+      next if filename == options[:filename]
+      yield filename # assume all matching files are requirable
+    end
+  end
+
+  def glob_imports(glob, base_pathname, options)
+    contents = ""
+    each_globbed_file(glob, base_pathname.dirname, options) do |filename|
+      unless File.directory?(filename)
+        contents << "@import #{Pathname.new(filename).relative_path_from(base_pathname.dirname).to_s.inspect};\n"
+      end
+    end
+    return nil if contents.empty?
+    Sass::Engine.new(contents, options.merge(
+      filename: base_pathname.to_s,
+      importer: self,
+      syntax: :scss
+    ))
+  end
+
+  private
+
+    def engine_from_path(name, dir, options)
+      full_filename, syntax = Sass::Util.destructure(find_real_file(dir, name, options))
+      return unless full_filename && File.readable?(full_filename)
+
+      Sass::Engine.for_file(full_filename, options)
+    end
+end
diff --git a/spec/models/site_customization_spec.rb b/spec/models/site_customization_spec.rb
index 8b6126260..3bfb89d37 100644
--- a/spec/models/site_customization_spec.rb
+++ b/spec/models/site_customization_spec.rb
@@ -155,12 +155,20 @@ describe SiteCustomization do
 
     it 'should compile scss' do
       c = SiteCustomization.create!(user_id: user.id, name: "test", stylesheet: '$black: #000; #a { color: $black; }', header: '')
-      c.stylesheet_baked.should == "#a {\n  color: black; }\n"
+      c.stylesheet_baked.should == "#a{color:#000}\n"
     end
 
     it 'should compile mobile scss' do
       c = SiteCustomization.create!(user_id: user.id, name: "test", stylesheet: '', header: '', mobile_stylesheet: '$black: #000; #a { color: $black; }', mobile_header: '')
-      c.mobile_stylesheet_baked.should == "#a {\n  color: black; }\n"
+      c.mobile_stylesheet_baked.should == "#a{color:#000}\n"
+    end
+
+    it 'should allow including discourse styles' do
+      c = SiteCustomization.create!(user_id: user.id, name: "test", stylesheet: '@import "desktop";', mobile_stylesheet: '@import "mobile";')
+      c.stylesheet_baked.should_not =~ /Syntax error/
+      c.stylesheet_baked.length.should > 1000
+      c.mobile_stylesheet_baked.should_not =~ /Syntax error/
+      c.mobile_stylesheet_baked.length.should > 1000
     end
 
     it 'should provide an awesome error on failure' do