From 317f9bcd0852c140258c2a6bbd55b9c4a88523ed Mon Sep 17 00:00:00 2001 From: Vikhyat Korrapati 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