From e13e05f972301396d5c02c355f108e9df7802cad Mon Sep 17 00:00:00 2001 From: johnnyshields Date: Fri, 21 Oct 2022 23:49:59 +0900 Subject: [PATCH 01/10] Combine Natsuki's sassc-embedded-shim-ruby with sassc-ruby - Replaces Libsass dependency with sass-embedded --- .gitignore | 4 - .gitmodules | 3 - .travis.yml | 16 - Gemfile | 2 + LICENSE.txt | 1 + README.md | 86 ++++- Rakefile | 55 +--- ext/depend | 4 - ext/extconf.rb | 92 ------ ext/libsass | 1 - lib/sassc.rb | 26 +- lib/sassc/engine.rb | 154 ++++++--- lib/sassc/error.rb | 2 - lib/sassc/functions_handler.rb | 62 ++-- lib/sassc/import_handler.rb | 216 +++++++++++-- lib/sassc/native.rb | 64 ---- lib/sassc/native/native_context_api.rb | 147 --------- lib/sassc/native/native_functions_api.rb | 159 --------- lib/sassc/native/sass2scss_api.rb | 10 - lib/sassc/native/sass_input_style.rb | 13 - lib/sassc/native/sass_output_style.rb | 12 - lib/sassc/native/sass_value.rb | 97 ------ lib/sassc/native/string_list.rb | 10 - lib/sassc/protocol.rb | 11 + lib/sassc/sass_2_scss.rb | 9 - lib/sassc/script.rb | 1 - lib/sassc/script/value_conversion.rb | 158 +++++---- lib/sassc/script/value_conversion/base.rb | 13 - lib/sassc/script/value_conversion/bool.rb | 13 - lib/sassc/script/value_conversion/color.rb | 18 -- lib/sassc/script/value_conversion/list.rb | 25 -- lib/sassc/script/value_conversion/map.rb | 21 -- lib/sassc/script/value_conversion/number.rb | 13 - lib/sassc/script/value_conversion/string.rb | 17 - lib/sassc/url.rb | 40 +++ lib/sassc/util/normalized_map.rb | 117 ------- lib/sassc/version.rb | 2 +- sassc.gemspec | 87 ++--- test/custom_importer_test.rb | 138 ++++---- test/engine_test.rb | 341 ++++++++++---------- test/error_test.rb | 22 +- test/functions_test.rb | 142 ++++---- test/native_test.rb | 213 ------------ test/output_style_test.rb | 118 +++---- test/sass_2_scss_test.rb | 14 - test/test_helper.rb | 33 +- test/vendor_test.rb | 23 ++ 47 files changed, 1039 insertions(+), 1786 deletions(-) delete mode 100644 .gitmodules delete mode 100644 .travis.yml delete mode 100644 ext/depend delete mode 100644 ext/extconf.rb delete mode 160000 ext/libsass delete mode 100644 lib/sassc/native.rb delete mode 100644 lib/sassc/native/native_context_api.rb delete mode 100644 lib/sassc/native/native_functions_api.rb delete mode 100644 lib/sassc/native/sass2scss_api.rb delete mode 100644 lib/sassc/native/sass_input_style.rb delete mode 100644 lib/sassc/native/sass_output_style.rb delete mode 100644 lib/sassc/native/sass_value.rb delete mode 100644 lib/sassc/native/string_list.rb create mode 100644 lib/sassc/protocol.rb delete mode 100644 lib/sassc/sass_2_scss.rb delete mode 100644 lib/sassc/script/value_conversion/base.rb delete mode 100644 lib/sassc/script/value_conversion/bool.rb delete mode 100644 lib/sassc/script/value_conversion/color.rb delete mode 100644 lib/sassc/script/value_conversion/list.rb delete mode 100644 lib/sassc/script/value_conversion/map.rb delete mode 100644 lib/sassc/script/value_conversion/number.rb delete mode 100644 lib/sassc/script/value_conversion/string.rb create mode 100644 lib/sassc/url.rb delete mode 100644 lib/sassc/util/normalized_map.rb delete mode 100644 test/native_test.rb delete mode 100644 test/sass_2_scss_test.rb create mode 100644 test/vendor_test.rb diff --git a/.gitignore b/.gitignore index 74855a98..9f0eb6ce 100644 --- a/.gitignore +++ b/.gitignore @@ -9,10 +9,6 @@ /spec/reports/ /tmp/ *.bundle -*.so -*.o -*.a *.gem -mkmf.log vendor/bundle /ext/Makefile diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e762dbbd..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "ext/libsass"] - path = ext/libsass - url = https://github.com/sass/libsass.git diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index cb636042..00000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: ruby - -bundler_args: "--binstubs --standalone --without documentation --path ../bundle" -script: "bundle exec rake test" -rvm: - - 2.0.0 - - 2.1.10 - - 2.2.10 - - 2.3.8 - - 2.4.6 - - 2.5.5 - - 2.6.3 - - 2.7.0 - - truffleruby -notifications: - email: false diff --git a/Gemfile b/Gemfile index 3be9c3cd..bb94df82 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,4 @@ +# frozen_string_literal: true + source "https://rubygems.org" gemspec diff --git a/LICENSE.txt b/LICENSE.txt index 0a58909a..56a4fe78 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,5 @@ Copyright (c) Ryan Boland & Contributors +Copyright (c) 2022 なつき MIT License diff --git a/README.md b/README.md index ad87e176..1afd3313 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,97 @@ # SassC [![Build Status](https://travis-ci.org/sass/sassc-ruby.svg?branch=master)](https://travis-ci.org/sass/sassc-ruby) [![Gem Version](https://badge.fury.io/rb/sassc.svg)](http://badge.fury.io/rb/sassc) -Use `libsass` with Ruby! +Use `sassc-embedded` with SassC Ruby! -This gem combines the speed of `libsass`, the [Sass C implementation](https://github.com/sass/libsass), with the ease of use of the original [Ruby Sass](https://github.com/sass/ruby-sass) library. +This fork removes the deprecated [`libsass`](https://github.com/sass/libsass) and replace it with [`sassc-embedded`](https://github.com/ntkme/sassc-embedded-polyfill-ruby), providing latest sass features and fast gem installation. -### libsass Version +This should essentially be a drop in alternative to [sass/sassc-ruby](https://github.com/sass/sassc-ruby). -[3.6.1](https://github.com/sass/libsass/releases/3.6.1) -## Installation -Add this line to your application's Gemfile: -```ruby -gem 'sassc' + + + + + + + + +# Embedded Sass Shim for SassC Ruby + +[![build](https://github.com/ntkme/sassc-embedded-shim-ruby/actions/workflows/build.yml/badge.svg)](https://github.com/ntkme/sassc-embedded-shim-ruby/actions/workflows/build.yml) +[![gem](https://badge.fury.io/rb/sassc-embedded.svg)](https://rubygems.org/gems/sassc-embedded) + +Use `sass-embedded` with SassC Ruby! + +This library shims [`sassc`](https://github.com/sass/sassc-ruby) with the [`sass-embedded`](https://github.com/ntkme/sass-embedded-host-ruby) implementation. + +It has been tested with: + +- [`sassc`](https://github.com/sass/sassc-ruby) +- [`sassc-rails`](https://github.com/sass/sassc-rails) +- [`sprockets`](https://github.com/rails/sprockets) +- [`sprockets-rails`](https://github.com/rails/sprockets-rails) + +## Install + +Add these lines to your application's Gemfile: + +``` ruby +gem 'sassc', github: 'sass/sassc-ruby', ref: 'refs/pull/233/head' +gem 'sassc-embedded' ``` And then execute: -```bash +``` sh bundle ``` Or install it yourself as: +``` sh +gem install sassc-embedded +``` + +## Usage + +This shim utilizes `sass-embedded` to allow you to compile SCSS or SASS syntax to CSS. To compile, use a `SassC::Engine`, e.g.: + +``` ruby +require 'sassc-embedded' + +SassC::Engine.new(sass, style: :compressed).render +``` + +See [rubydoc.info/gems/sassc](https://rubydoc.info/gems/sassc) for full API documentation. + +## Behavioral Differences from SassC Ruby + +1. Option `:style => :nested` and `:style => :compact` behave as `:style => :expanded`. + +2. Option `:precision` is ignored. + +3. Option `:line_comments` is ignored. + +See [the dart-sass documentation](https://github.com/sass/dart-sass#behavioral-differences-from-ruby-sass) for other differences. + + + + + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'rails-sass-embedded' +``` + +And then execute: + ```bash -gem install sassc +bundle ``` ## Usage diff --git a/Rakefile b/Rakefile index d75e653d..8c09a903 100644 --- a/Rakefile +++ b/Rakefile @@ -1,51 +1,14 @@ -require 'bundler/gem_tasks' - -task default: :test - -require 'rake/extensiontask' -gem_spec = Gem::Specification.load("sassc.gemspec") - -# HACK: Prevent rake-compiler from overriding required_ruby_version, -# because the shared library here is Ruby-agnostic. -# See https://github.com/rake-compiler/rake-compiler/issues/153 -module FixRequiredRubyVersion - def required_ruby_version=(*); end -end -Gem::Specification.send(:prepend, FixRequiredRubyVersion) - -Rake::ExtensionTask.new('libsass', gem_spec) do |ext| - ext.name = 'libsass' - ext.ext_dir = 'ext' - ext.lib_dir = 'lib/sassc' - ext.cross_compile = true - ext.cross_platform = %w[x86-mingw32 x64-mingw32] +# frozen_string_literal: true - # Link C++ stdlib statically when building binary gems. - ext.cross_config_options << '--enable-static-stdlib' - - ext.cross_config_options << '--disable-march-tune-native' - - ext.cross_compiling do |spec| - spec.files.reject! { |path| File.fnmatch?('ext/*', path) } - end -end +require 'bundler/gem_tasks' +require 'rake/testtask' +require 'rubocop/rake_task' -desc 'Compile all native gems via rake-compiler-dock (Docker)' -task 'gem:native' do - require 'rake_compiler_dock' +task default: %i[rubocop test] - # The RUBY_CC_VERSION here doesn't matter for the final package. - # Only one version should be specified, as the shared library is Ruby-agnostic. - RakeCompilerDock.sh "gem i rake bundler --no-document && bundle && "\ - "rake clean && rake cross native gem MAKE='nice make -j`nproc`' "\ - "RUBY_CC_VERSION=2.6.0 CLEAN=1" +Rake::TestTask.new do |t| + t.libs << 'test' + t.test_files = FileList['test/**/*_test.rb'] end -CLEAN.include 'tmp', 'pkg', 'lib/sassc/libsass.{so,bundle}', 'ext/libsass/VERSION', - 'ext/*.{o,so,bundle}', 'ext/Makefile' - -desc "Run all tests" -task test: 'compile:libsass' do - $LOAD_PATH.unshift('lib', 'test') - Dir.glob('./test/**/*_test.rb') { |f| require f } -end +RuboCop::RakeTask.new diff --git a/ext/depend b/ext/depend deleted file mode 100644 index 940eda67..00000000 --- a/ext/depend +++ /dev/null @@ -1,4 +0,0 @@ -# Replaces default mkmf dependencies. Default mkmf dependencies include all libruby headers. -# We don't need libruby and some of these headers are missing on JRuby (breaking compilation there). -$(OBJS): $(HDRS) - diff --git a/ext/extconf.rb b/ext/extconf.rb deleted file mode 100644 index 6d925dab..00000000 --- a/ext/extconf.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -gem_root = File.expand_path('..', __dir__) -libsass_dir = File.join(gem_root, 'ext', 'libsass') - -if !File.directory?(libsass_dir) || - # '.', '..', and possibly '.git' from a failed checkout: - Dir.entries(libsass_dir).size <= 3 - Dir.chdir(gem_root) { system('git submodule update --init') } or - fail 'Could not fetch libsass' -end - -require 'mkmf' - -$CXXFLAGS << ' -std=c++11' - -# Set to true when building binary gems -if enable_config('static-stdlib', false) - $LDFLAGS << ' -static-libgcc -static-libstdc++' -end - -if enable_config('march-tune-native', false) - $CFLAGS << ' -march=native -mtune=native' - $CXXFLAGS << ' -march=native -mtune=native' -end - -# darwin nix clang doesn't support lto -# disable -lto flag for darwin + nix -# see: https://github.com/sass/sassc-ruby/issues/148 -enable_lto_by_default = (Gem::Platform.local.os == "darwin" && ENV['NIX_CC'].nil?) - -if enable_config('lto', enable_lto_by_default) - $CFLAGS << ' -flto' - $CXXFLAGS << ' -flto' - $LDFLAGS << ' -flto' -end - -# Disable noisy compilation warnings. -$warnflags = '' -$CFLAGS.gsub!(/[\s+](-ansi|-std=[^\s]+)/, '') - -dir_config 'libsass' - -libsass_version = Dir.chdir(libsass_dir) do - if File.exist?('.git') - ver = %x[git describe --abbrev=4 --dirty --always --tags].chomp - File.write('VERSION', ver) - ver - end - File.read('VERSION').chomp if File.exist?('VERSION') -end - -if libsass_version - libsass_version_def = %Q{ -DLIBSASS_VERSION='"#{libsass_version}"'} - $CFLAGS << libsass_version_def - $CXXFLAGS << libsass_version_def -end - -$INCFLAGS << " -I$(srcdir)/libsass/include" -$VPATH << "$(srcdir)/libsass/src" -Dir.chdir(__dir__) do - $VPATH += Dir['libsass/src/*/'].map { |p| "$(srcdir)/#{p}" } - $srcs = Dir['libsass/src/**/*.{c,cpp}'].sort -end - -# libsass.bundle malformed object (unknown load command 7) on Mac OS X -# See https://github.com/sass/sassc-ruby/pull/174 -if enable_config('strip', RbConfig::CONFIG['host_os'].downcase !~ /darwin/) - MakeMakefile::LINK_SO << "\nstrip -x $@" -end - -# Don't link libruby. -$LIBRUBYARG = nil - -# Disable .def file generation for mingw, as it defines an -# `Init_libsass` export which we don't have. -MakeMakefile.send(:remove_const, :EXPORT_PREFIX) -MakeMakefile::EXPORT_PREFIX = nil - -if RUBY_ENGINE == 'jruby' && - Gem::Version.new(RUBY_ENGINE_VERSION) < Gem::Version.new('9.2.8.0') - # COUTFLAG is not set correctly on jruby<9.2.8.0 - # See https://github.com/jruby/jruby/issues/5749 - MakeMakefile.send(:remove_const, :COUTFLAG) - MakeMakefile::COUTFLAG = '-o $(empty)' - - # CCDLFLAGS is not set correctly on jruby<9.2.8.0 - # See https://github.com/jruby/jruby/issues/5751 - $CXXFLAGS << ' -fPIC' -end - -create_makefile 'sassc/libsass' diff --git a/ext/libsass b/ext/libsass deleted file mode 160000 index 8d312a1c..00000000 --- a/ext/libsass +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8d312a1c91bb7dd22883ebdfc829003f75a82396 diff --git a/lib/sassc.rb b/lib/sassc.rb index c3143020..8cc9d5b7 100644 --- a/lib/sassc.rb +++ b/lib/sassc.rb @@ -28,11 +28,10 @@ def self.load_paths end require_relative "sassc/version" -require_relative "sassc/native" require_relative "sassc/import_handler" require_relative "sassc/importer" require_relative "sassc/util" -require_relative "sassc/util/normalized_map" +# require_relative "sassc/util/normalized_map" require_relative "sassc/script" require_relative "sassc/script/value" require_relative "sassc/script/value/bool" @@ -43,15 +42,22 @@ def self.load_paths require_relative "sassc/script/value/map" require_relative "sassc/script/functions" require_relative "sassc/script/value_conversion" -require_relative "sassc/script/value_conversion/base" -require_relative "sassc/script/value_conversion/string" -require_relative "sassc/script/value_conversion/number" -require_relative "sassc/script/value_conversion/color" -require_relative "sassc/script/value_conversion/map" -require_relative "sassc/script/value_conversion/list" -require_relative "sassc/script/value_conversion/bool" require_relative "sassc/functions_handler" require_relative "sassc/dependency" require_relative "sassc/error" +require_relative "sassc/url" +require_relative "sassc/protocol" require_relative "sassc/engine" -require_relative "sassc/sass_2_scss" + +# ####### +# require 'sassc' +require 'sass-embedded' + +require 'base64' +require 'json' +require 'uri' + +# require_relative 'embedded/version' +# require_relative 'sassc/embedded' +############# + diff --git a/lib/sassc/engine.rb b/lib/sassc/engine.rb index 13955039..3d1d1483 100644 --- a/lib/sassc/engine.rb +++ b/lib/sassc/engine.rb @@ -22,45 +22,40 @@ def initialize(template, options = {}) def render return @template.dup if @template.empty? - data_context = Native.make_data_context(@template) - context = Native.data_context_get_context(data_context) - native_options = Native.context_get_options(context) - - Native.option_set_is_indented_syntax_src(native_options, true) if sass? - Native.option_set_input_path(native_options, filename) if filename - Native.option_set_precision(native_options, precision) if precision - Native.option_set_include_path(native_options, load_paths) - Native.option_set_output_style(native_options, output_style_enum) - Native.option_set_source_comments(native_options, true) if line_comments? - Native.option_set_source_map_file(native_options, source_map_file) if source_map_file - Native.option_set_source_map_embed(native_options, true) if source_map_embed? - Native.option_set_source_map_contents(native_options, true) if source_map_contents? - Native.option_set_omit_source_map_url(native_options, true) if omit_source_map_url? - - import_handler.setup(native_options) - functions_handler.setup(native_options, functions: @functions) - - status = Native.compile_data_context(data_context) - - if status != 0 - message = Native.context_get_error_message(context) - filename = Native.context_get_error_file(context) - line = Native.context_get_error_line(context) - - raise SyntaxError.new(message, filename: filename, line: line) - end - - css = Native.context_get_output_string(context) - - @dependencies = Native.context_get_included_files(context) - @source_map = Native.context_get_source_map_string(context) - - css.force_encoding(@template.encoding) - @source_map.force_encoding(@template.encoding) if @source_map.is_a?(String) - - return css unless quiet? - ensure - Native.delete_data_context(data_context) if data_context + result = ::Sass.compile_string( + @template, + importer: import_handler.setup(nil), + load_paths: load_paths, + syntax: syntax, + url: file_url, + + charset: @options.fetch(:charset, true), + source_map: source_map_embed? || !source_map_file.nil?, + source_map_include_sources: source_map_contents?, + style: output_style, + + functions: functions_handler.setup(nil, functions: @functions), + importers: @options.fetch(:importers, []), + + alert_ascii: @options.fetch(:alert_ascii, false), + alert_color: @options.fetch(:alert_color, nil), + logger: @options.fetch(:logger, nil), + quiet_deps: @options.fetch(:quiet_deps, false), + verbose: @options.fetch(:verbose, false) + ) + + @dependencies = result.loaded_urls + .filter { |url| url.start_with?(Protocol::FILE) && url != file_url } + .map { |url| URL.file_url_to_path(url) } + @source_map = post_process_source_map(result.source_map) + + return post_process_css(result.css) unless quiet? + rescue ::Sass::CompileError => e + line = e.span&.start&.line + line += 1 unless line.nil? + url = e.span&.url + path = (URL.parse(url).route_from(URL.path_to_file_url("#{Dir.pwd}/")) if url&.start_with?(Protocol::FILE)) + raise SyntaxError.new(e.full_message, filename: path, line: line) end def dependencies @@ -119,23 +114,88 @@ def functions_handler @functions_handler = FunctionsHandler.new(@options) end + def file_url + @file_url ||= URL.path_to_file_url(File.absolute_path(filename || 'stdin')) + end + + def output_path + @output_path ||= @options.fetch( + :output_path, + ("#{filename.delete_suffix(File.extname(filename))}.css" if filename) + ) + end + + def output_url + @output_url ||= (URL.path_to_file_url(File.absolute_path(output_path)) if output_path) + end + + def source_map_file_url + @source_map_file_url ||= (URL.path_to_file_url(File.absolute_path(source_map_file)) if source_map_file) + end + def output_style_enum @output_style_enum ||= Native::SassOutputStyle[output_style] end def output_style @output_style ||= begin - style = @options.fetch(:style, :sass_style_nested).to_s - style = "sass_style_#{style}" unless style.include?("sass_style_") - style = style.to_sym - raise InvalidStyleError unless Native::SassOutputStyle.symbols.include?(style) - style - end + style = @options.fetch(:style, :sass_style_nested).to_s + style = "sass_style_#{style}" unless style.include?('sass_style_') + raise InvalidStyleError unless OUTPUT_STYLES.include?(style.to_sym) + + style = style.delete_prefix('sass_style_').to_sym + case style + when :nested, :compact + :expanded + else + style + end + end + end + + def syntax + syntax = @options.fetch(:syntax, :scss) + syntax = :indented if syntax.to_sym == :sass + syntax end def load_paths - paths = (@options[:load_paths] || []) + SassC.load_paths - paths.join(File::PATH_SEPARATOR) unless paths.empty? + @load_paths ||= if @options[:importer].nil? + (@options[:load_paths] || []) + SassC.load_paths + else + [] + end + end + + def post_process_source_map(source_map) + return unless source_map + + url = URL.parse(source_map_file_url || file_url) + data = JSON.parse(source_map) + data['file'] = URL.parse(output_url).route_from(url).to_s if output_url + data['sources'].map! do |source| + if source.start_with?(Protocol::FILE) + URL.parse(source).route_from(url).to_s + else + source + end + end + + JSON.generate(data) + end + + def post_process_css(css) + css += "\n" unless css.empty? + unless @source_map.nil? || omit_source_map_url? + url = URL.parse(output_url || file_url) + source_mapping_url = if source_map_embed? + "data:application/json;base64,#{Base64.strict_encode64(@source_map)}" + else + URL.parse(source_map_file_url).route_from(url).to_s + end + css += "\n/*# sourceMappingURL=#{source_mapping_url} */" + end + css end end end diff --git a/lib/sassc/error.rb b/lib/sassc/error.rb index 10f53553..0cf8616d 100644 --- a/lib/sassc/error.rb +++ b/lib/sassc/error.rb @@ -31,7 +31,5 @@ def sass_backtrace return [] unless @filename && @line ["#{@filename}:#{@line}"] end - end - end diff --git a/lib/sassc/functions_handler.rb b/lib/sassc/functions_handler.rb index 8a3c3495..914786dd 100644 --- a/lib/sassc/functions_handler.rb +++ b/lib/sassc/functions_handler.rb @@ -6,53 +6,40 @@ def initialize(options) @options = options end - def setup(native_options, functions: Script::Functions) + def setup(_native_options, functions: Script::Functions) @callbacks = {} - @function_names = {} - list = Native.make_function_list(Script.custom_functions(functions: functions).count) - - # use an anonymous class wrapper to avoid mutations in a threaded environment functions_wrapper = Class.new do attr_accessor :options + include functions end.new functions_wrapper.options = @options - Script.custom_functions(functions: functions).each_with_index do |custom_function, i| - @callbacks[custom_function] = FFI::Function.new(:pointer, [:pointer, :pointer]) do |native_argument_list, cookie| + Script.custom_functions(functions: functions).each do |custom_function| + callback = lambda do |native_argument_list| + function_arguments = arguments_from_native_list(native_argument_list) begin - function_arguments = arguments_from_native_list(native_argument_list) result = functions_wrapper.send(custom_function, *function_arguments) - to_native_value(result) - rescue StandardError => exception - # This rescues any exceptions that occur either in value conversion - # or during the execution of a custom function. - error(exception.message) + rescue StandardError + raise ::Sass::ScriptError, "Error: error in C function #{custom_function}" end + to_native_value(result) + rescue StandardError => e + warn "[SassC::FunctionsHandler] #{e.cause.message}" + raise e end - @function_names[custom_function] = Script.formatted_function_name(custom_function, functions: functions) - - callback = Native.make_function( - @function_names[custom_function], - @callbacks[custom_function], - nil - ) - - Native::function_set_list_entry(list, i, callback) + @callbacks[Script.formatted_function_name(custom_function, functions: functions)] = callback end - Native::option_set_c_functions(native_options, list) + @callbacks end private def arguments_from_native_list(native_argument_list) - native_argument_list_length = Native.list_get_length(native_argument_list) - - (0...native_argument_list_length).map do |i| - native_value = Native.list_get_value(native_argument_list, i) + native_argument_list.map do |native_value| Script::ValueConversion.from_native(native_value, @options) end.compact end @@ -69,5 +56,26 @@ def error(message) $stderr.puts "[SassC::FunctionsHandler] #{message}" Native.make_error(message) end + + begin + begin + raise RuntimeError + rescue StandardError + raise ::Sass::ScriptError + end + rescue StandardError => e + unless e.full_message.include?(e.cause.full_message) + ::Sass::ScriptError.class_eval do + def full_message(*args, **kwargs) + full_message = super(*args, **kwargs) + if cause + "#{full_message}\n#{cause.full_message(*args, **kwargs)}" + else + full_message + end + end + end + end + end end end diff --git a/lib/sassc/import_handler.rb b/lib/sassc/import_handler.rb index 498df7c2..76c06f25 100644 --- a/lib/sassc/import_handler.rb +++ b/lib/sassc/import_handler.rb @@ -10,41 +10,213 @@ def initialize(options) end end - def setup(native_options) - return unless @importer + def setup(_native_options) + Importer.new(@importer) if @importer + end - importer_callback = Native.make_importer(import_function, nil) + class FileImporter + class << self + def resolve_path(path, from_import) + ext = File.extname(path) + if ['.sass', '.scss', '.css'].include?(ext) + if from_import + result = exactly_one(try_path("#{without_ext(path)}.import#{ext}")) + return result unless result.nil? + end + return exactly_one(try_path(path)) + end - list = Native.make_function_list(1) - Native::function_set_list_entry(list, 0, importer_callback) + unless ext.empty? + if from_import + result = exactly_one(try_path("#{without_ext(path)}.import#{ext}")) + return result unless result.nil? + end + result = exactly_one(try_path(path)) + return result unless result.nil? + end - Native.option_set_c_importers(native_options, list) - end + if from_import + result = exactly_one(try_path_with_ext("#{path}.import")) + return result unless result.nil? + end + + result = exactly_one(try_path_with_ext(path)) + return result unless result.nil? + + try_path_as_dir(path, from_import) + end + + private + + def try_path_with_ext(path) + result = try_path("#{path}.sass") + try_path("#{path}.scss") + result.empty? ? try_path("#{path}.css") : result + end + + def try_path(path) + partial = File.join(File.dirname(path), "_#{File.basename(path)}") + result = [] + result.push(partial) if file_exist?(partial) + result.push(path) if file_exist?(path) + result + end + + def try_path_as_dir(path, from_import) + return unless dir_exist? path - private + if from_import + result = exactly_one(try_path_with_ext(File.join(path, 'index.import'))) + return result unless result.nil? + end - def import_function - @import_function ||= FFI::Function.new(:pointer, [:string, :pointer, :pointer]) do |path, importer_entry, compiler| - last_import = Native::compiler_get_last_import(compiler) - parent_path = Native::import_get_abs_path(last_import) + exactly_one(try_path_with_ext(File.join(path, 'index'))) + end - imports = [*@importer.imports(path, parent_path)] - imports_to_native(imports) + def exactly_one(paths) + return if paths.empty? + return paths.first if paths.length == 1 + + raise "It's not clear which file to import. Found:\n#{paths.map { |path| " #{path}" }.join("\n")}" + end + + def file_exist?(path) + File.exist?(path) && File.file?(path) + end + + def dir_exist?(path) + File.exist?(path) && File.directory?(path) + end + + def without_ext(path) + ext = File.extname(path) + path.delete_suffix(ext) + end end end - def imports_to_native(imports) - import_list = Native.make_import_list(imports.size) + private_constant :FileImporter + + class Importer + def initialize(importer) + @importer = importer + + @canonical_urls = {} + @id = 0 + @importer_results = {} + @parent_urls = [URL.path_to_file_url(File.absolute_path(@importer.options[:filename] || 'stdin'))] + end + + def canonicalize(url, from_import:) + if url.start_with?(Protocol::IMPORT) + canonical_url = @canonical_urls.delete(url.delete_prefix(Protocol::IMPORT)) + unless @importer_results.key?(canonical_url) + canonical_url = resolve_file_url(canonical_url, @parent_urls.last, from_import) + end + @parent_urls.push(canonical_url) + canonical_url + elsif url.start_with?(Protocol::FILE) + path = URL.parse(url).route_from(@parent_urls.last).to_s + parent_path = URL.file_url_to_path(@parent_urls.last) + + imports = @importer.imports(path, parent_path) + imports = [SassC::Importer::Import.new(path)] if imports.nil? + imports = [imports] unless imports.is_a?(Array) + imports.each do |import| + import.path = File.absolute_path(import.path, File.dirname(parent_path)) + end + + canonical_url = "#{Protocol::IMPORT}#{next_id}" + @importer_results[canonical_url] = imports_to_native(imports) + canonical_url + elsif url.start_with?(Protocol::LOADED) + canonical_url = Protocol::LOADED + @parent_urls.pop + canonical_url + end + end + + def load(canonical_url) + if @importer_results.key?(canonical_url) + @importer_results.delete(canonical_url) + elsif canonical_url.start_with?(Protocol::FILE) + path = URL.file_url_to_path(canonical_url) + { + contents: File.read(path), + syntax: syntax(path), + source_map_url: canonical_url + } + elsif canonical_url.start_with?(Protocol::LOADED) + { + contents: '', + syntax: :scss + } + end + end - imports.each_with_index do |import, i| - source = import.source ? Native.native_string(import.source) : nil - source_map_path = nil + private - entry = Native.make_import_entry(import.path, source, source_map_path) - Native.import_set_list_entry(import_list, i, entry) + def load_paths + @load_paths ||= (@importer.options[:load_paths] || []) + SassC.load_paths end - import_list + def resolve_file_url(url, parent_url, from_import) + path = URL.parse(url).route_from(parent_url).to_s + parent_path = URL.file_url_to_path(parent_url) + [File.dirname(parent_path)].concat(load_paths).each do |load_path| + resolved = FileImporter.resolve_path(File.absolute_path(path, load_path), from_import) + return URL.path_to_file_url(resolved) unless resolved.nil? + end + nil + end + + def syntax(path) + case File.extname(path) + when '.sass' + :indented + when '.css' + :css + else + :scss + end + end + + def imports_to_native(imports) + { + contents: imports.flat_map do |import| + id = next_id + canonical_url = URL.path_to_file_url(import.path) + @canonical_urls[id] = canonical_url + if import.source + @importer_results[canonical_url] = if import.source.is_a?(Hash) + { + contents: import.source[:contents], + syntax: import.source[:syntax], + source_map_url: canonical_url + } + else + { + contents: import.source, + syntax: syntax(import.path), + source_map_url: canonical_url + } + end + end + [ + "@import \"#{Protocol::IMPORT}#{id}\";", + "@import \"#{Protocol::LOADED}#{id}\";" + ] + end.join("\n"), + syntax: :scss + } + end + + def next_id + id = @id + @id = id.next + id.to_s + end end + + private_constant :Importer end end diff --git a/lib/sassc/native.rb b/lib/sassc/native.rb deleted file mode 100644 index 3b9f515f..00000000 --- a/lib/sassc/native.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -require "ffi" - -module SassC - module Native - extend FFI::Library - - dl_ext = RbConfig::MAKEFILE_CONFIG['DLEXT'] - begin - ffi_lib File.expand_path("libsass.#{dl_ext}", __dir__) - rescue LoadError # Some non-rvm environments don't copy a shared object over to lib/sassc - ffi_lib File.expand_path("libsass.#{dl_ext}", "#{__dir__}/../../ext") - end - - require_relative "native/sass_value" - - typedef :pointer, :sass_options_ptr - typedef :pointer, :sass_context_ptr - typedef :pointer, :sass_file_context_ptr - typedef :pointer, :sass_data_context_ptr - - typedef :pointer, :sass_c_function_list_ptr - typedef :pointer, :sass_c_function_callback_ptr - typedef :pointer, :sass_value_ptr - - typedef :pointer, :sass_import_list_ptr - typedef :pointer, :sass_importer - typedef :pointer, :sass_import_ptr - - callback :sass_c_function, [:pointer, :pointer], :pointer - callback :sass_c_import_function, [:pointer, :pointer, :pointer], :pointer - - require_relative "native/sass_input_style" - require_relative "native/sass_output_style" - require_relative "native/string_list" - - # Remove the redundant "sass_" from the beginning of every method name - def self.attach_function(*args) - return super if args.size != 3 - - if args[0] =~ /^sass_/ - args.unshift args[0].to_s.sub(/^sass_/, "") - end - - super(*args) - end - - # https://github.com/ffi/ffi/wiki/Examples#array-of-strings - def self.return_string_array(ptr) - ptr.null? ? [] : ptr.get_array_of_string(0).compact - end - - def self.native_string(string) - m = FFI::MemoryPointer.from_string(string) - m.autorelease = false - m - end - - require_relative "native/native_context_api" - require_relative "native/native_functions_api" - require_relative "native/sass2scss_api" - end -end diff --git a/lib/sassc/native/native_context_api.rb b/lib/sassc/native/native_context_api.rb deleted file mode 100644 index a692ca98..00000000 --- a/lib/sassc/native/native_context_api.rb +++ /dev/null @@ -1,147 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Native - attach_function :version, :libsass_version, [], :string - - # Create and initialize an option struct - # ADDAPI struct Sass_Options* ADDCALL sass_make_options (void); - attach_function :sass_make_options, [], :sass_options_ptr - - # Create and initialize a specific context - # ADDAPI struct Sass_File_Context* ADDCALL sass_make_file_context (const char* input_path); - # ADDAPI struct Sass_Data_Context* ADDCALL sass_make_data_context (char* source_string); - attach_function :sass_make_file_context, [:string], :sass_file_context_ptr - attach_function :_make_data_context, :sass_make_data_context, [:pointer], :sass_data_context_ptr - - def self.make_data_context(data) - _make_data_context(Native.native_string(data)) - end - - # Call the compilation step for the specific context - # ADDAPI int ADDCALL sass_compile_file_context (struct Sass_File_Context* ctx); - # ADDAPI int ADDCALL sass_compile_data_context (struct Sass_Data_Context* ctx); - attach_function :sass_compile_file_context, [:sass_file_context_ptr], :int - attach_function :sass_compile_data_context, [:sass_data_context_ptr], :int - - # Create a sass compiler instance for more control - # ADDAPI struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx); - # ADDAPI struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx); - - # Execute the different compilation steps individually - # Usefull if you only want to query the included files - # ADDAPI int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler); - # ADDAPI int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler); - - # Release all memory allocated with the compiler - # This does _not_ include any contexts or options - # ADDAPI void ADDCALL sass_delete_compiler(struct Sass_Compiler* compiler); - - # Release all memory allocated and also ourself - # ADDAPI void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx); - # ADDAPI void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx); - attach_function :sass_delete_file_context, [:sass_file_context_ptr], :void - attach_function :sass_delete_data_context, [:sass_data_context_ptr], :void - - # Getters for context from specific implementation - # ADDAPI struct Sass_Context* ADDCALL sass_file_context_get_context (struct Sass_File_Context* file_ctx); - # ADDAPI struct Sass_Context* ADDCALL sass_data_context_get_context (struct Sass_Data_Context* data_ctx); - attach_function :sass_file_context_get_context, [:sass_file_context_ptr], :sass_context_ptr - attach_function :sass_data_context_get_context, [:sass_data_context_ptr], :sass_context_ptr - - # Getters for context options from Sass_Context - # ADDAPI struct Sass_Options* ADDCALL sass_context_get_options (struct Sass_Context* ctx); - # ADDAPI struct Sass_Options* ADDCALL sass_file_context_get_options (struct Sass_File_Context* file_ctx); - # ADDAPI struct Sass_Options* ADDCALL sass_data_context_get_options (struct Sass_Data_Context* data_ctx); - # ADDAPI void ADDCALL sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); - # ADDAPI void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); - attach_function :sass_context_get_options, [:sass_context_ptr], :sass_options_ptr - attach_function :sass_file_context_get_options, [:sass_file_context_ptr], :sass_options_ptr - attach_function :sass_data_context_get_options, [:sass_data_context_ptr], :sass_options_ptr - attach_function :sass_file_context_set_options, [:sass_file_context_ptr, :sass_options_ptr], :void - attach_function :sass_data_context_set_options, [:sass_data_context_ptr, :sass_options_ptr], :void - - # Getters for options - # ADDAPI int ADDCALL sass_option_get_precision (struct Sass_Options* options); - # ADDAPI enum Sass_Output_Style ADDCALL sass_option_get_output_style (struct Sass_Options* options); - # ADDAPI bool ADDCALL sass_option_get_source_comments (struct Sass_Options* options); - # ADDAPI bool ADDCALL sass_option_get_source_map_embed (struct Sass_Options* options); - # ADDAPI bool ADDCALL sass_option_get_source_map_contents (struct Sass_Options* options); - # ADDAPI bool ADDCALL sass_option_get_omit_source_map_url (struct Sass_Options* options); - # ADDAPI bool ADDCALL sass_option_get_is_indented_syntax_src (struct Sass_Options* options); - # ADDAPI const char* ADDCALL sass_option_get_input_path (struct Sass_Options* options); - # ADDAPI const char* ADDCALL sass_option_get_output_path (struct Sass_Options* options); - # ADDAPI const char* ADDCALL sass_option_get_include_path (struct Sass_Options* options); - # ADDAPI const char* ADDCALL sass_option_get_source_map_file (struct Sass_Options* options); - # ADDAPI Sass_C_Function_List ADDCALL sass_option_get_c_functions (struct Sass_Options* options); - attach_function :sass_option_get_precision, [:sass_options_ptr], :int - attach_function :sass_option_get_output_style, [:sass_options_ptr], SassOutputStyle - attach_function :sass_option_get_source_comments, [:sass_options_ptr], :bool - attach_function :sass_option_get_source_map_embed, [:sass_options_ptr], :bool - attach_function :sass_option_get_source_map_contents, [:sass_options_ptr], :bool - attach_function :sass_option_get_omit_source_map_url, [:sass_options_ptr], :bool - attach_function :sass_option_get_is_indented_syntax_src, [:sass_options_ptr], :bool - attach_function :sass_option_get_input_path, [:sass_options_ptr], :string - attach_function :sass_option_get_output_path, [:sass_options_ptr], :string - attach_function :sass_option_get_include_path, [:sass_options_ptr], :string - attach_function :sass_option_get_source_map_file, [:sass_options_ptr], :string - attach_function :sass_option_get_c_functions, [:sass_options_ptr], :sass_c_function_list_ptr - # ADDAPI Sass_C_Import_Callback ADDCALL sass_option_get_importer (struct Sass_Options* options); - - # Setters for options - # ADDAPI void ADDCALL sass_option_set_precision (struct Sass_Options* options, int precision); - # ADDAPI void ADDCALL sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); - # ADDAPI void ADDCALL sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); - # ADDAPI void ADDCALL sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); - # ADDAPI void ADDCALL sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); - # ADDAPI void ADDCALL sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); - # ADDAPI void ADDCALL sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); - # ADDAPI void ADDCALL sass_option_set_input_path (struct Sass_Options* options, const char* input_path); - # ADDAPI void ADDCALL sass_option_set_output_path (struct Sass_Options* options, const char* output_path); - # ADDAPI void ADDCALL sass_option_set_include_path (struct Sass_Options* options, const char* include_path); - # ADDAPI void ADDCALL sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); - # ADDAPI void ADDCALL sass_option_set_c_functions (struct Sass_Options* options, Sass_C_Function_List c_functions); - # ADDAPI void ADDCALL sass_option_set_c_importers (struct Sass_Options* options, Sass_Importer_List c_importers); - attach_function :sass_option_set_precision, [:sass_options_ptr, :int], :void - attach_function :sass_option_set_output_style, [:sass_options_ptr, SassOutputStyle], :void - attach_function :sass_option_set_source_comments, [:sass_options_ptr, :bool], :void - attach_function :sass_option_set_source_map_embed, [:sass_options_ptr, :bool], :void - attach_function :sass_option_set_source_map_contents, [:sass_options_ptr, :bool], :void - attach_function :sass_option_set_omit_source_map_url, [:sass_options_ptr, :bool], :void - attach_function :sass_option_set_is_indented_syntax_src, [:sass_options_ptr, :bool], :void - attach_function :sass_option_set_input_path, [:sass_options_ptr, :string], :void - attach_function :sass_option_set_output_path, [:sass_options_ptr, :string], :void - attach_function :sass_option_set_include_path, [:sass_options_ptr, :string], :void - attach_function :sass_option_set_source_map_file, [:sass_options_ptr, :string], :void - attach_function :sass_option_set_c_functions, [:sass_options_ptr, :pointer], :void - attach_function :sass_option_set_c_importers, [:sass_options_ptr, :pointer], :void - #attach_function :sass_option_set_c_importers, [:sass_options_ptr, :sass_importer], :void - - # Getter for context - # ADDAPI const char* ADDCALL sass_context_get_output_string (struct Sass_Context* ctx); - # ADDAPI int ADDCALL sass_context_get_error_status (struct Sass_Context* ctx); - # ADDAPI const char* ADDCALL sass_context_get_error_json (struct Sass_Context* ctx); - # ADDAPI const char* ADDCALL sass_context_get_error_message (struct Sass_Context* ctx); - # ADDAPI const char* ADDCALL sass_context_get_error_file (struct Sass_Context* ctx); - # ADDAPI size_t ADDCALL sass_context_get_error_line (struct Sass_Context* ctx); - # ADDAPI size_t ADDCALL sass_context_get_error_column (struct Sass_Context* ctx); - # ADDAPI const char* ADDCALL sass_context_get_source_map_string (struct Sass_Context* ctx); - # ADDAPI char** ADDCALL sass_context_get_included_files (struct Sass_Context* ctx); - attach_function :sass_context_get_output_string, [:sass_context_ptr], :string - attach_function :sass_context_get_error_status, [:sass_context_ptr], :int - attach_function :sass_context_get_error_json, [:sass_context_ptr], :string - attach_function :sass_context_get_error_message, [:sass_context_ptr], :string - attach_function :sass_context_get_error_file, [:sass_context_ptr], :string - attach_function :sass_context_get_error_line, [:sass_context_ptr], :size_t - attach_function :sass_context_get_error_column, [:sass_context_ptr], :size_t - attach_function :sass_context_get_source_map_string, [:sass_context_ptr], :string - attach_function :_context_get_included_files, :sass_context_get_included_files, [:sass_context_ptr], :pointer - - def self.context_get_included_files(*args) - return_string_array _context_get_included_files(*args) - end - - # ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler); - attach_function :sass_compiler_get_last_import, [:pointer], :pointer - end -end diff --git a/lib/sassc/native/native_functions_api.rb b/lib/sassc/native/native_functions_api.rb deleted file mode 100644 index 02fe68da..00000000 --- a/lib/sassc/native/native_functions_api.rb +++ /dev/null @@ -1,159 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Native - # Creators for sass function list and function descriptors - # ADDAPI Sass_C_Function_List ADDCALL sass_make_function_list (size_t length); - # ADDAPI Sass_C_Function_Callback ADDCALL sass_make_function (const char* signature, Sass_C_Function fn, void* cookie); - attach_function :sass_make_function_list, [:size_t], :sass_c_function_list_ptr - attach_function :sass_make_function, [:string, :sass_c_function, :pointer], :sass_c_function_callback_ptr - - # Setters and getters for callbacks on function lists - # ADDAPI Sass_C_Function_Callback ADDCALL sass_function_get_list_entry(Sass_C_Function_List list, size_t pos); - # ADDAPI void ADDCALL sass_function_set_list_entry(Sass_C_Function_List list, size_t pos, Sass_C_Function_Callback cb); - attach_function :sass_function_get_list_entry, [:sass_c_function_list_ptr, :size_t], :sass_c_function_callback_ptr - attach_function :sass_function_set_list_entry, [:sass_c_function_list_ptr, :size_t, :sass_c_function_callback_ptr], :void - - # ADDAPI union Sass_Value* ADDCALL sass_make_number (double val, const char* unit); - attach_function :sass_make_number, [:double, :string], :sass_value_ptr - - # ADDAPI union Sass_Value* ADDCALL sass_make_string (const char* val); - attach_function :sass_make_string, [:string], :sass_value_ptr - - # ADDAPI union Sass_Value* ADDCALL sass_make_qstring (const char* val); - attach_function :sass_make_qstring, [:string], :sass_value_ptr - - # ADDAPI union Sass_Value* ADDCALL sass_make_color (double r, double g, double b, double a); - attach_function :sass_make_color, [:double, :double, :double, :double], :sass_value_ptr - - # ADDAPI union Sass_Value* ADDCALL sass_make_map (size_t len); - attach_function :sass_make_map, [:size_t], :sass_value_ptr - - # ADDAPI union Sass_Value* ADDCALL sass_make_list (size_t len, enum Sass_Separator sep) - attach_function :sass_make_list, [:size_t, SassSeparator], :sass_value_ptr - - # ADDAPI union Sass_Value* ADDCALL sass_make_boolean (boolean val); - attach_function :sass_make_boolean, [:bool], :sass_value_ptr - - # ADDAPI void ADDCALL sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*); - attach_function :sass_map_set_key, [:sass_value_ptr, :size_t, :sass_value_ptr], :void - - # ADDAPI union Sass_Value* ADDCALL sass_map_get_key (const union Sass_Value* v, size_t i); - attach_function :sass_map_get_key, [:sass_value_ptr, :size_t], :sass_value_ptr - - # ADDAPI void ADDCALL sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*); - attach_function :sass_map_set_value, [:sass_value_ptr, :size_t, :sass_value_ptr], :void - - # ADDAPI union Sass_Value* ADDCALL sass_map_get_value (const union Sass_Value* v, size_t i); - attach_function :sass_map_get_value, [:sass_value_ptr, :size_t], :sass_value_ptr - - # ADDAPI size_t ADDCALL sass_map_get_length (const union Sass_Value* v); - attach_function :sass_map_get_length, [:sass_value_ptr], :size_t - - # ADDAPI union Sass_Value* ADDCALL sass_list_get_value (const union Sass_Value* v, size_t i); - attach_function :sass_list_get_value, [:sass_value_ptr, :size_t], :sass_value_ptr - - # ADDAPI void ADDCALL sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); - attach_function :sass_list_set_value, [:sass_value_ptr, :size_t, :sass_value_ptr], :void - - # ADDAPI size_t ADDCALL sass_list_get_length (const union Sass_Value* v); - attach_function :sass_list_get_length, [:sass_value_ptr], :size_t - - # ADDAPI union Sass_Value* ADDCALL sass_make_error (const char* msg); - attach_function :sass_make_error, [:string], :sass_value_ptr - - # ADDAPI enum Sass_Tag ADDCALL sass_value_get_tag (const union Sass_Value* v); - attach_function :sass_value_get_tag, [:sass_value_ptr], SassTag - attach_function :sass_value_is_null, [:sass_value_ptr], :bool - - # ADDAPI const char* ADDCALL sass_string_get_value (const union Sass_Value* v); - attach_function :sass_string_get_value, [:sass_value_ptr], :string - - # ADDAPI bool ADDCALL sass_string_is_quoted(const union Sass_Value* v); - attach_function :sass_string_is_quoted, [:sass_value_ptr], :bool - - # ADDAPI const char* ADDCALL sass_number_get_value (const union Sass_Value* v); - attach_function :sass_number_get_value, [:sass_value_ptr], :double - - # ADDAPI const char* ADDCALL sass_number_get_unit (const union Sass_Value* v); - attach_function :sass_number_get_unit, [:sass_value_ptr], :string - - # ADDAPI const char* ADDCALL sass_boolean_get_value (const union Sass_Value* v); - attach_function :sass_boolean_get_value, [:sass_value_ptr], :bool - - def self.string_get_type(native_value) - string_is_quoted(native_value) ? :string : :identifier - end - - # ADDAPI double ADDCALL sass_color_get_r (const union Sass_Value* v); - # ADDAPI void ADDCALL sass_color_set_r (union Sass_Value* v, double r); - # ADDAPI double ADDCALL sass_color_get_g (const union Sass_Value* v); - # ADDAPI void ADDCALL sass_color_set_g (union Sass_Value* v, double g); - # ADDAPI double ADDCALL sass_color_get_b (const union Sass_Value* v); - # ADDAPI void ADDCALL sass_color_set_b (union Sass_Value* v, double b); - # ADDAPI double ADDCALL sass_color_get_a (const union Sass_Value* v); - # ADDAPI void ADDCALL sass_color_set_a (union Sass_Value* v, double a); - ['r', 'g', 'b', 'a'].each do |color_channel| - attach_function "sass_color_get_#{color_channel}".to_sym, [:sass_value_ptr], :double - attach_function "sass_color_set_#{color_channel}".to_sym, [:sass_value_ptr, :double], :void - end - - # ADDAPI char* ADDCALL sass_error_get_message (const union Sass_Value* v); - # ADDAPI void ADDCALL sass_error_set_message (union Sass_Value* v, char* msg); - attach_function :sass_error_get_message, [:sass_value_ptr], :string - attach_function :sass_error_set_message, [:sass_value_ptr, :pointer], :void - - # Getters for custom function descriptors - # ADDAPI const char* ADDCALL sass_function_get_signature (Sass_C_Function_Callback fn); - # ADDAPI Sass_C_Function ADDCALL sass_function_get_function (Sass_C_Function_Callback fn); - # ADDAPI void* ADDCALL sass_function_get_cookie (Sass_C_Function_Callback fn); - attach_function :sass_function_get_signature, [:sass_c_function_callback_ptr], :string - attach_function :sass_function_get_function, [:sass_c_function_callback_ptr], :sass_c_function - attach_function :sass_function_get_cookie, [:sass_c_function_callback_ptr], :pointer - - # Creators for custom importer callback (with some additional pointer) - # The pointer is mostly used to store the callback into the actual binding - # ADDAPI Sass_C_Import_Callback ADDCALL sass_make_importer (Sass_C_Import_Fn, void* cookie); - attach_function :sass_make_importer, [:sass_c_import_function, :pointer], :sass_importer - - # Getters for import function descriptors - # ADDAPI Sass_C_Import_Fn ADDCALL sass_import_get_function (Sass_C_Import_Callback fn); - # ADDAPI void* ADDCALL sass_import_get_cookie (Sass_C_Import_Callback fn); - - # Deallocator for associated memory - # ADDAPI void ADDCALL sass_delete_importer (Sass_C_Import_Callback fn); - - # Creator for sass custom importer return argument list - # ADDAPI struct Sass_Import** ADDCALL sass_make_import_list (size_t length); - attach_function :sass_make_import_list, [:size_t], :sass_import_list_ptr - - # Creator for a single import entry returned by the custom importer inside the list - # ADDAPI struct Sass_Import* ADDCALL sass_make_import_entry (const char* path, char* source, char* srcmap); - # ADDAPI struct Sass_Import* ADDCALL sass_make_import (const char* path, const char* base, char* source, char* srcmap); - attach_function :sass_make_import_entry, [:string, :pointer, :pointer], :sass_import_ptr - - # Setters to insert an entry into the import list (you may also use [] access directly) - # Since we are dealing with pointers they should have a guaranteed and fixed size - # ADDAPI void ADDCALL sass_import_set_list_entry (struct Sass_Import** list, size_t idx, struct Sass_Import* entry); - attach_function :sass_import_set_list_entry, [:sass_import_list_ptr, :size_t, :sass_import_ptr], :void - # ADDAPI struct Sass_Import* ADDCALL sass_import_get_list_entry (struct Sass_Import** list, size_t idx); - - # Getters for import entry - # ADDAPI const char* ADDCALL sass_import_get_imp_path (struct Sass_Import*); - attach_function :sass_import_get_imp_path, [:sass_import_ptr], :string - # ADDAPI const char* ADDCALL sass_import_get_abs_path (struct Sass_Import*); - attach_function :sass_import_get_abs_path, [:sass_import_ptr], :string - # ADDAPI const char* ADDCALL sass_import_get_source (struct Sass_Import*); - attach_function :sass_import_get_source, [:sass_import_ptr], :string - # ADDAPI const char* ADDCALL sass_import_get_srcmap (struct Sass_Import*); - # Explicit functions to take ownership of these items - # The property on our struct will be reset to NULL - # ADDAPI char* ADDCALL sass_import_take_source (struct Sass_Import*); - # ADDAPI char* ADDCALL sass_import_take_srcmap (struct Sass_Import*); - - # Deallocator for associated memory (incl. entries) - # ADDAPI void ADDCALL sass_delete_import_list (struct Sass_Import**); - # Just in case we have some stray import structs - # ADDAPI void ADDCALL sass_delete_import (struct Sass_Import*); - end -end diff --git a/lib/sassc/native/sass2scss_api.rb b/lib/sassc/native/sass2scss_api.rb deleted file mode 100644 index 1520cd74..00000000 --- a/lib/sassc/native/sass2scss_api.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Native - # ADDAPI char* ADDCALL sass2scss (const char* sass, const int options); - attach_function :sass2scss, [:string, :int], :string - - # ADDAPI const char* ADDCALL sass2scss_version(void); - end -end diff --git a/lib/sassc/native/sass_input_style.rb b/lib/sassc/native/sass_input_style.rb deleted file mode 100644 index 593f382a..00000000 --- a/lib/sassc/native/sass_input_style.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Native - SassInputStyle = enum( - :sass_context_null, - :sass_context_file, - :sass_context_data, - :sass_context_folder - ) - end -end - diff --git a/lib/sassc/native/sass_output_style.rb b/lib/sassc/native/sass_output_style.rb deleted file mode 100644 index 266d4f11..00000000 --- a/lib/sassc/native/sass_output_style.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Native - SassOutputStyle = enum( - :sass_style_nested, - :sass_style_expanded, - :sass_style_compact, - :sass_style_compressed - ) - end -end diff --git a/lib/sassc/native/sass_value.rb b/lib/sassc/native/sass_value.rb deleted file mode 100644 index 86588edc..00000000 --- a/lib/sassc/native/sass_value.rb +++ /dev/null @@ -1,97 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Native - class SassValue < FFI::Union; end - - SassTag = enum( - :sass_boolean, - :sass_number, - :sass_color, - :sass_string, - :sass_list, - :sass_map, - :sass_null, - :sass_error, - :sass_warning - ) - - SassSeparator = enum( - :sass_comma, - :sass_space - ) - - class SassUnknown < FFI::Struct - layout :tag, SassTag - end - - class SassBoolean < FFI::Struct - layout :tag, SassTag, - :value, :bool - end - - class SassNumber < FFI::Struct - layout :tag, SassTag, - :value, :double, - :unit, :string - end - - class SassColor < FFI::Struct - layout :tag, SassTag, - :r, :double, - :g, :double, - :b, :double, - :a, :double - end - - class SassString < FFI::Struct - layout :tag, SassTag, - :value, :string - end - - class SassList < FFI::Struct - layout :tag, SassTag, - :separator, SassSeparator, - :length, :size_t, - :values, :pointer - end - - class SassMapPair < FFI::Struct - layout :key, SassValue.ptr, - :value, SassValue.ptr - end - - class SassMap < FFI::Struct - layout :tag, SassTag, - :length, :size_t, - :pairs, SassMapPair.ptr - end - - class SassNull < FFI::Struct - layout :tag, SassTag - end - - class SassError < FFI::Struct - layout :tag, SassTag, - :message, :string - end - - class SassWarning < FFI::Struct - layout :tag, SassTag, - :message, :string - end - - class SassValue # < FFI::Union - layout :unknown, SassUnknown, - :boolean, SassBoolean, - :number, SassNumber, - :color, SassColor, - :string, SassString, - :list, SassList, - :map, SassMap, - :null, SassNull, - :error, SassError, - :warning, SassWarning - end - end -end diff --git a/lib/sassc/native/string_list.rb b/lib/sassc/native/string_list.rb deleted file mode 100644 index 4b593b32..00000000 --- a/lib/sassc/native/string_list.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Native - class StringList < FFI::Struct - layout :string_list, StringList.ptr, - :string, :string - end - end -end diff --git a/lib/sassc/protocol.rb b/lib/sassc/protocol.rb new file mode 100644 index 00000000..11de74e6 --- /dev/null +++ b/lib/sassc/protocol.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SassC + module Protocol + FILE = 'file:' + IMPORT = 'sassc-embedded-import:' + LOADED = 'sassc-embedded-loaded:' + end + + private_constant :Protocol +end diff --git a/lib/sassc/sass_2_scss.rb b/lib/sassc/sass_2_scss.rb deleted file mode 100644 index 59e5b09c..00000000 --- a/lib/sassc/sass_2_scss.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module SassC - class Sass2Scss - def self.convert(sass) - Native.sass2scss(sass, 0) - end - end -end diff --git a/lib/sassc/script.rb b/lib/sassc/script.rb index 0eed756a..76cd8d1f 100644 --- a/lib/sassc/script.rb +++ b/lib/sassc/script.rb @@ -12,6 +12,5 @@ def self.formatted_function_name(function_name, functions: Functions) params = params.map { |param_type, name| "$#{name}#{': null' if param_type == :opt}" }.join(", ") return "#{function_name}(#{params})" end - end end diff --git a/lib/sassc/script/value_conversion.rb b/lib/sassc/script/value_conversion.rb index df580f1d..26c64837 100644 --- a/lib/sassc/script/value_conversion.rb +++ b/lib/sassc/script/value_conversion.rb @@ -2,68 +2,116 @@ module SassC::Script::ValueConversion - def self.from_native(native_value, options) - case value_tag = SassC::Native.value_get_tag(native_value) - when :sass_null - # no-op - when :sass_string - value = SassC::Native.string_get_value(native_value) - type = SassC::Native.string_get_type(native_value) - argument = SassC::Script::Value::String.new(value, type) - argument - when :sass_boolean - value = SassC::Native.boolean_get_value(native_value) - argument = SassC::Script::Value::Bool.new(value) - argument - when :sass_number - value = SassC::Native.number_get_value(native_value) - unit = SassC::Native.number_get_unit(native_value) - argument = SassC::Script::Value::Number.new(value, unit) - argument - when :sass_color - red, green, blue, alpha = SassC::Native.color_get_r(native_value), SassC::Native.color_get_g(native_value), SassC::Native.color_get_b(native_value), SassC::Native.color_get_a(native_value) - argument = SassC::Script::Value::Color.new(red:red, green:green, blue:blue, alpha:alpha) - argument.options = options - argument - when :sass_map - values = {} - length = SassC::Native::map_get_length native_value - (0..length-1).each do |index| - key = SassC::Native::map_get_key(native_value, index) - value = SassC::Native::map_get_value(native_value, index) - values[from_native(key, options)] = from_native(value, options) + def self.from_native(value, options) + case value + when ::Sass::Value::Null::NULL + nil + when ::Sass::Value::Boolean + ::SassC::Script::Value::Bool.new(value.to_bool) + when ::Sass::Value::Color + if value.instance_eval { defined? @hue } + ::SassC::Script::Value::Color.new( + hue: value.hue, + saturation: value.saturation, + lightness: value.lightness, + alpha: value.alpha + ) + else + ::SassC::Script::Value::Color.new( + red: value.red, + green: value.green, + blue: value.blue, + alpha: value.alpha + ) end - argument = SassC::Script::Value::Map.new values - argument - when :sass_list - length = SassC::Native::list_get_length(native_value) - items = (0...length).map do |index| - native_item = SassC::Native::list_get_value(native_value, index) - from_native(native_item, options) - end - SassC::Script::Value::List.new(items, separator: :space) + when ::Sass::Value::List + ::SassC::Script::Value::List.new( + value.to_a.map { |element| from_native(element, options) }, + separator: case value.separator + when ',' + :comma + when ' ' + :space + else + raise UnsupportedValue, "Sass list separator #{value.separator} unsupported" + end, + bracketed: value.bracketed? + ) + when ::Sass::Value::Map + ::SassC::Script::Value::Map.new( + value.contents.to_a.to_h { |k, v| [from_native(k, options), from_native(v, options)] } + ) + when ::Sass::Value::Number + ::SassC::Script::Value::Number.new( + value.value, + value.numerator_units, + value.denominator_units + ) + when ::Sass::Value::String + ::SassC::Script::Value::String.new( + value.text, + value.quoted? ? :string : :identifier + ) else - raise UnsupportedValue.new("Sass argument of type #{value_tag} unsupported") + raise UnsupportedValue, "Sass argument of type #{value.class.name.split('::').last} unsupported" end end def self.to_native(value) - case value_name = value.class.name.split("::").last - when "String" - SassC::Script::ValueConversion::String.new(value).to_native - when "Color" - SassC::Script::ValueConversion::Color.new(value).to_native - when "Number" - SassC::Script::ValueConversion::Number.new(value).to_native - when "Map" - SassC::Script::ValueConversion::Map.new(value).to_native - when "List" - SassC::Script::ValueConversion::List.new(value).to_native - when "Bool" - SassC::Script::ValueConversion::Bool.new(value).to_native + case value + when nil + ::Sass::Value::Null::NULL + when ::SassC::Script::Value::Bool + ::Sass::Value::Boolean.new(value.to_bool) + when ::SassC::Script::Value::Color + if value.rgba? + ::Sass::Value::Color.new( + red: value.red, + green: value.green, + blue: value.blue, + alpha: value.alpha + ) + elsif value.hlsa? + ::Sass::Value::Color.new( + hue: value.hue, + saturation: value.saturation, + lightness: value.lightness, + alpha: value.alpha + ) + else + raise UnsupportedValue, "Sass color mode #{value.instance_eval { @mode }} unsupported" + end + when ::SassC::Script::Value::List + ::Sass::Value::List.new( + value.to_a.map { |element| to_native(element) }, + separator: case value.separator + when :comma + ',' + when :space + ' ' + else + raise UnsupportedValue, "Sass list separator #{value.separator} unsupported" + end, + bracketed: value.bracketed + ) + when ::SassC::Script::Value::Map + ::Sass::Value::Map.new( + value.value.to_a.to_h { |k, v| [to_native(k), to_native(v)] } + ) + when ::SassC::Script::Value::Number + ::Sass::Value::Number.new( + value.value, { + numerator_units: value.numerator_units, + denominator_units: value.denominator_units + } + ) + when ::SassC::Script::Value::String + ::Sass::Value::String.new( + value.value, + quoted: value.type != :identifier + ) else - raise SassC::UnsupportedValue.new("Sass return type #{value_name} unsupported") + raise UnsupportedValue, "Sass return type #{value.class.name.split('::').last} unsupported" end end - end diff --git a/lib/sassc/script/value_conversion/base.rb b/lib/sassc/script/value_conversion/base.rb deleted file mode 100644 index a9ed1fa8..00000000 --- a/lib/sassc/script/value_conversion/base.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Script - module ValueConversion - class Base - def initialize(value) - @value = value - end - end - end - end -end diff --git a/lib/sassc/script/value_conversion/bool.rb b/lib/sassc/script/value_conversion/bool.rb deleted file mode 100644 index d1c475c9..00000000 --- a/lib/sassc/script/value_conversion/bool.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Script - module ValueConversion - class Bool < Base - def to_native - Native::make_boolean(@value.value) - end - end - end - end -end diff --git a/lib/sassc/script/value_conversion/color.rb b/lib/sassc/script/value_conversion/color.rb deleted file mode 100644 index 7ec35358..00000000 --- a/lib/sassc/script/value_conversion/color.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Script - module ValueConversion - class Color < Base - def to_native - Native::make_color( - @value.red, - @value.green, - @value.blue, - @value.alpha - ) - end - end - end - end -end diff --git a/lib/sassc/script/value_conversion/list.rb b/lib/sassc/script/value_conversion/list.rb deleted file mode 100644 index 0f60e15c..00000000 --- a/lib/sassc/script/value_conversion/list.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Script - module ValueConversion - SEPARATORS = { - space: :sass_space, - comma: :sass_comma - } - - class List < Base - def to_native - list = @value.to_a - sep = SEPARATORS.fetch(@value.separator) - native_list = Native::make_list(list.size, sep) - list.each_with_index do |item, index| - native_item = ValueConversion.to_native(item) - Native::list_set_value(native_list, index, native_item) - end - native_list - end - end - end - end -end diff --git a/lib/sassc/script/value_conversion/map.rb b/lib/sassc/script/value_conversion/map.rb deleted file mode 100644 index 8243a21b..00000000 --- a/lib/sassc/script/value_conversion/map.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Script - module ValueConversion - class Map < Base - def to_native - hash = @value.to_h - native_map = Native::make_map( hash.size ) - hash.each_with_index do |(key, value), index| - key = ValueConversion.to_native key - value = ValueConversion.to_native value - Native::map_set_key( native_map, index, key ) - Native::map_set_value( native_map, index, value ) - end - return native_map - end - end - end - end -end diff --git a/lib/sassc/script/value_conversion/number.rb b/lib/sassc/script/value_conversion/number.rb deleted file mode 100644 index 2351a2e5..00000000 --- a/lib/sassc/script/value_conversion/number.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Script - module ValueConversion - class Number < Base - def to_native - Native::make_number(@value.value, @value.numerator_units.first) - end - end - end - end -end diff --git a/lib/sassc/script/value_conversion/string.rb b/lib/sassc/script/value_conversion/string.rb deleted file mode 100644 index c62ef2e8..00000000 --- a/lib/sassc/script/value_conversion/string.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module SassC - module Script - module ValueConversion - class String < Base - def to_native(opts = {}) - if opts[:quote] == :none || @value.type == :identifier - Native::make_string(@value.to_s) - else - Native::make_qstring(@value.to_s) - end - end - end - end - end -end diff --git a/lib/sassc/url.rb b/lib/sassc/url.rb new file mode 100644 index 00000000..d23d2207 --- /dev/null +++ b/lib/sassc/url.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module SassC + module URL + PARSER = URI::Parser.new({ RESERVED: ';/?:@&=+$,' }) + + private_constant :PARSER + + module_function + + def parse(str) + PARSER.parse(str) + end + + def escape(str) + PARSER.escape(str) + end + + def unescape(str) + PARSER.unescape(str) + end + + def file_url_to_path(url) + return if url.nil? + + path = unescape(parse(url).path) + path = path[1..] if Gem.win_platform? && path[0].chr == '/' && path[1].chr =~ /[a-z]/i && path[2].chr == ':' + path + end + + def path_to_file_url(path) + return if path.nil? + + path = "/#{path}" unless path.start_with?('/') + URI::File.build([nil, escape(path)]).to_s + end + end + + private_constant :URL +end diff --git a/lib/sassc/util/normalized_map.rb b/lib/sassc/util/normalized_map.rb deleted file mode 100644 index e04a0eca..00000000 --- a/lib/sassc/util/normalized_map.rb +++ /dev/null @@ -1,117 +0,0 @@ -# frozen_string_literal: true - -require "delegate" - -# A hash that normalizes its string keys while still allowing you to get back -# to the original keys that were stored. If several different values normalize -# to the same value, whichever is stored last wins. - -class SassC::Util::NormalizedMap - - # Create a normalized map - def initialize(map = nil) - @key_strings = {} - @map = {} - map.each {|key, value| self[key] = value} if map - end - - # Specifies how to transform the key. - # This can be overridden to create other normalization behaviors. - def normalize(key) - key.tr("-", "_") - end - - # Returns the version of `key` as it was stored before - # normalization. If `key` isn't in the map, returns it as it was - # passed in. - # @return [String] - def denormalize(key) - @key_strings[normalize(key)] || key - end - - # @private - def []=(k, v) - normalized = normalize(k) - @map[normalized] = v - @key_strings[normalized] = k - v - end - - # @private - def [](k) - @map[normalize(k)] - end - - # @private - def has_key?(k) - @map.has_key?(normalize(k)) - end - - # @private - def delete(k) - normalized = normalize(k) - @key_strings.delete(normalized) - @map.delete(normalized) - end - - # @return [Hash] Hash with the keys as they were stored (before normalization). - def as_stored - SassC::Util.map_keys(@map) {|k| @key_strings[k]} - end - - def empty? - @map.empty? - end - - def values - @map.values - end - - def keys - @map.keys - end - - def each - @map.each {|k, v| yield(k, v)} - end - - def size - @map.size - end - - def to_hash - @map.dup - end - - def to_a - @map.to_a - end - - def map - @map.map {|k, v| yield(k, v)} - end - - def dup - d = super - d.send(:instance_variable_set, "@map", @map.dup) - d - end - - def sort_by - @map.sort_by {|k, v| yield k, v} - end - - def update(map) - map = map.as_stored if map.is_a?(NormalizedMap) - map.each {|k, v| self[k] = v} - end - - def method_missing(method, *args, &block) - @map.send(method, *args, &block) - end - - def respond_to_missing?(method, include_private = false) - @map.respond_to?(method, include_private) - end - -end diff --git a/lib/sassc/version.rb b/lib/sassc/version.rb index a5a8a205..23ced755 100644 --- a/lib/sassc/version.rb +++ b/lib/sassc/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SassC - VERSION = "2.4.0" + VERSION = "0.0.1" end diff --git a/sassc.gemspec b/sassc.gemspec index 912df0df..3a101d9b 100644 --- a/sassc.gemspec +++ b/sassc.gemspec @@ -2,68 +2,33 @@ lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require "sassc/version" +require 'sassc/version' Gem::Specification.new do |spec| - - spec.name = "sassc" + spec.name = 'sassc' spec.version = SassC::VERSION - spec.authors = ["Ryan Boland"] - spec.email = ["ryan@tanookilabs.com"] - spec.summary = "Use libsass with Ruby!" - spec.description = "Use libsass with Ruby!" - spec.homepage = "https://github.com/sass/sassc-ruby" - spec.license = "MIT" - - spec.files = `git ls-files -z`.split("\x0") - spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } - spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) - - spec.required_ruby_version = ">= 2.0.0" - - spec.require_paths = ["lib"] - - spec.platform = Gem::Platform::RUBY - spec.extensions = ["ext/extconf.rb"] - - spec.add_development_dependency "minitest", "~> 5.5.1" - spec.add_development_dependency "minitest-around" - spec.add_development_dependency "test_construct" - spec.add_development_dependency "pry" - spec.add_development_dependency "bundler" - spec.add_development_dependency "rake" - spec.add_development_dependency "rake-compiler" - spec.add_development_dependency "rake-compiler-dock" - - spec.add_dependency "ffi", "~> 1.9" - - gem_dir = File.expand_path(File.dirname(__FILE__)) + "/" - - libsass_dir = File.join(gem_dir, 'ext', 'libsass') - if !File.directory?(libsass_dir) || - # '.', '..', and possibly '.git' from a failed checkout: - Dir.entries(libsass_dir).size <= 3 - Dir.chdir(__dir__) { system('git submodule update --init') } or - fail 'Could not fetch libsass' - end - - # Write a VERSION file for non-binary gems (for `SassC::Native.version`). - if File.exist?(File.join(libsass_dir, '.git')) - libsass_version = Dir.chdir(libsass_dir) do - %x[git describe --abbrev=4 --dirty --always --tags].chomp - end - File.write(File.join(libsass_dir, 'VERSION'), libsass_version) - end - - Dir.chdir(libsass_dir) do - submodule_relative_path = File.join('ext', 'libsass') - skip_re = %r{(^("?test|docs|script)/)|\.md$|\.yml$} - only_re = %r{\.[ch](pp)?$} - `git ls-files`.split($\).each do |filename| - next if filename =~ skip_re || filename !~ only_re - spec.files << File.join(submodule_relative_path, filename) - end - spec.files << File.join(submodule_relative_path, 'VERSION') - end - + spec.authors = ['Ryan Boland', 'なつき'] + spec.email = ['ryan@tanookilabs.com', 'i@ntk.me'] + spec.summary = 'Use dart-sass with Ruby!' + spec.description = 'Use dart-sass with Ruby!' + spec.homepage = 'https://github.com/sass/sassc-ruby' + spec.license = 'MIT' + spec.metadata = { + 'documentation_uri' => "https://rubydoc.info/gems/#{spec.name}/#{spec.version}", + 'source_code_uri' => "#{spec.homepage}/tree/v#{spec.version}" + } + + spec.files = Dir['lib/**/*.rb'] + %w[LICENSE README.md] + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.required_ruby_version = '>= 2.6.0' + + spec.add_runtime_dependency 'sass-embedded', '~> 1.54' + + spec.add_development_dependency 'minitest', '~> 5.16.0' + spec.add_development_dependency 'minitest-around', '~> 0.5.0' + spec.add_development_dependency 'rake', '>= 10.0.0' + spec.add_development_dependency 'rubocop', '~> 1.37.0' + spec.add_development_dependency 'rubocop-minitest', '~> 0.22.0' + spec.add_development_dependency 'rubocop-performance', '~> 1.15.0' + spec.add_development_dependency 'rubocop-rake', '~> 0.6.0' end diff --git a/test/custom_importer_test.rb b/test/custom_importer_test.rb index b803b139..a79b4d42 100644 --- a/test/custom_importer_test.rb +++ b/test/custom_importer_test.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true -require_relative "test_helper" +require_relative 'test_helper' module SassC class CustomImporterTest < MiniTest::Test include TempFileTest class CustomImporter < Importer - def imports(path, parent_path) - if path =~ /styles/ + def imports(path, _parent_path) + if /styles/.match?(path) [ - Import.new("#{path}1.scss", source: "$var1: #000;"), + Import.new("#{path}1.scss", source: '$var1: #000;'), Import.new("#{path}2.scss") ] else @@ -20,108 +20,112 @@ def imports(path, parent_path) end class NoFilesImporter < Importer - def imports(path, parent_path) + def imports(_path, _parent_path) [] end end class OptionsImporter < Importer - def imports(path, parent_path) - Import.new("name.scss", source: options[:custom_option_source]) + def imports(_path, _parent_path) + Import.new('name.scss', source: options[:custom_option_source]) end end class ParentImporter < Importer - def imports(path, parent_path) - Import.new("name.scss", source: ".#{parent_path} { color: red; }") + def imports(_path, parent_path) + Import.new('name.scss', source: ".#{File.basename(parent_path)} { color: red; }") end end def test_custom_importer_works - temp_file("styles2.scss", ".hi { color: $var1; }") - temp_file("fonts.scss", ".font { color: $var1; }") + temp_file('styles2.scss', '.hi { color: $var1; }') + temp_file('fonts.scss', '.font { color: $var1; }') - data = < err + rescue SassC::SyntaxError => e expected = "#{filename}:3" - assert_equal expected, err.backtrace.first + assert_equal expected, e.backtrace.first end end end diff --git a/test/functions_test.rb b/test/functions_test.rb index 77716722..e1ec7437 100644 --- a/test/functions_test.rb +++ b/test/functions_test.rb @@ -1,14 +1,15 @@ # frozen_string_literal: true -require_relative "test_helper" -require "stringio" +require_relative 'test_helper' +require 'stringio' module SassC class FunctionsTest < MiniTest::Test include FixtureHelper def setup - @real_stderr, $stderr = $stderr, StringIO.new + @real_stderr = $stderr + $stderr = StringIO.new end def teardown @@ -119,28 +120,17 @@ def test_functions_may_accept_sass_color_type EXPECTED_CSS end - def test_function_with_unsupported_tag - skip('What are other unsupported tags?') - engine = Engine.new("div {url: function_with_unsupported_tag(());}") - - exception = assert_raises(SassC::SyntaxError) do - engine.render - end - - assert_match /Sass argument of type sass_list unsupported/, exception.message - assert_equal "[SassC::FunctionsHandler] Sass argument of type sass_list unsupported", stderr_output - end - def test_function_with_error - engine = Engine.new("div {url: function_that_raises_errors();}") + engine = Engine.new('div {url: function_that_raises_errors();}') exception = assert_raises(SassC::SyntaxError) do engine.render end - assert_match /Error: error in C function function_that_raises_errors/, exception.message - assert_match /Intentional wrong thing happened somewhere inside the custom function/, exception.message - assert_match /\[SassC::FunctionsHandler\] Intentional wrong thing happened somewhere inside the custom function/, stderr_output + assert_match(/Error: error in C function function_that_raises_errors/, exception.message) + assert_match(/Intentional wrong thing happened somewhere inside the custom function/, exception.message) + assert_match(/\[SassC::FunctionsHandler\] Intentional wrong thing happened somewhere inside the custom function/, + stderr_output) end def test_function_that_returns_a_sass_value @@ -190,11 +180,11 @@ def test_concurrency threads = [] 10.times do |i| threads << Thread.new(i) do |id| - out = Engine.new("div { url: inspect_options(); }", {test_key1: 'test_value', test_key2: id}).render - assert_match /test_key1/, out - assert_match /test_key2/, out - assert_match /test_value/, out - assert_match /#{id}/, out + out = Engine.new('div { url: inspect_options(); }', { test_key1: 'test_value', test_key2: id }).render + assert_match(/test_key1/, out) + assert_match(/test_key2/, out) + assert_match(/test_value/, out) + assert_match(/#{id}/, out) end end threads.each(&:join) @@ -202,13 +192,13 @@ def test_concurrency end def test_pass_custom_functions_as_a_parameter - out = Engine.new("div { url: test-function(); }", {functions: ExternalFunctions}).render - assert_match /custom_function/, out + out = Engine.new('div { url: test-function(); }', { functions: ExternalFunctions }).render + assert_match(/custom_function/, out) end def test_pass_incompatible_type_to_custom_functions assert_raises(TypeError) do - Engine.new("div { url: test-function(); }", {functions: Class.new}).render + Engine.new('div { url: test-function(); }', { functions: Class.new }).render end end @@ -216,16 +206,15 @@ def test_pass_incompatible_type_to_custom_functions def assert_sass(sass, expected_css) engine = Engine.new(sass) - assert_equal expected_css.strip.gsub!(/\s+/, " "), # poor man's String#squish - engine.render.strip.gsub!(/\s+/, " ") + assert_equal expected_css.strip.gsub!(/\s+/, ' '), # poor man's String#squish + engine.render.strip.gsub!(/\s+/, ' ') end def stderr_output $stderr.string.gsub("\u0000\n", '').chomp end - module Script::Functions - + module Script::Functions # rubocop:disable Style/ClassAndModuleChildren def javascript_path(path) SassC::Script::Value::String.new("/js/#{path.value}", :string) end @@ -234,107 +223,110 @@ def stylesheet_path(path) SassC::Script::Value::String.new("/css/#{path.value}", :identifier) end - def no_return_path(path) + def no_return_path(_path) nil end def sass_return_path(path) - SassC::Script::Value::String.new("#{path.value}", :string) + SassC::Script::Value::String.new(path.value.to_s, :string) end def optional_arguments(path, optional = nil) - optional ||= SassC::Script::Value::String.new("bar") + optional ||= SassC::Script::Value::String.new('bar') SassC::Script::Value::String.new("#{path.value}/#{optional.value}", :string) end def function_that_raises_errors - raise StandardError, "Intentional wrong thing happened somewhere inside the custom function" - end - - def function_with_unsupported_tag(value) + raise StandardError, 'Intentional wrong thing happened somewhere inside the custom function' end def nice_color_argument(color) - return SassC::Script::Value::String.new(color.to_s, :identifier) + SassC::Script::Value::String.new(color.to_s, :identifier) end def returns_a_color - return SassC::Script::Value::Color.new(red: 0, green: 0, blue: 0) + SassC::Script::Value::Color.new(red: 0, green: 0, blue: 0) end def returns_a_number - return SassC::Script::Value::Number.new(-312,'rem') + SassC::Script::Value::Number.new(-312, 'rem') end def returns_a_bool - return SassC::Script::Value::Bool.new(true) + SassC::Script::Value::Bool.new(true) end - def inspect_bool ( argument ) - raise StandardError.new "passed value is not a Sass::Script::Value::Bool" unless argument.is_a? SassC::Script::Value::Bool - return argument + def inspect_bool(argument) + unless argument.is_a? SassC::Script::Value::Bool + raise StandardError, 'passed value is not a Sass::Script::Value::Bool' + end + + argument end - def inspect_number ( argument ) - raise StandardError.new "passed value is not a Sass::Script::Value::Number" unless argument.is_a? SassC::Script::Value::Number - return argument + def inspect_number(argument) + unless argument.is_a? SassC::Script::Value::Number + raise StandardError, 'passed value is not a Sass::Script::Value::Number' + end + + argument end - def inspect_map ( argument ) + def inspect_map(argument) argument.to_h.each_pair do |key, value| - raise StandardError.new "key #{key.inspect} is not a string" unless key.is_a? SassC::Script::Value::String - - valueClass = case key.value - when 'string' - SassC::Script::Value::String - when 'number' - SassC::Script::Value::Number - when 'color' - SassC::Script::Value::Color - when 'map' - SassC::Script::Value::Map - end - - raise StandardError.new "unknown key #{key.inspect}" unless valueClass - raise StandardError.new "value for #{key.inspect} is not a #{valueClass}" unless value.is_a? valueClass + raise StandardError, "key #{key.inspect} is not a string" unless key.is_a? SassC::Script::Value::String + + value_class = case key.value + when 'string' + SassC::Script::Value::String + when 'number' + SassC::Script::Value::Number + when 'color' + SassC::Script::Value::Color + when 'map' + SassC::Script::Value::Map + end + + raise StandardError, "unknown key #{key.inspect}" unless value_class + raise StandardError, "value for #{key.inspect} is not a #{value_class}" unless value.is_a? value_class end - return argument + argument end def inspect_list(argument) - raise StandardError.new "passed value is not a Sass::Script::Value::List" unless argument.is_a? SassC::Script::Value::List - return argument + unless argument.is_a? SassC::Script::Value::List + raise StandardError, 'passed value is not a Sass::Script::Value::List' + end + + argument end def inspect_options - SassC::Script::Value::String.new(self.options.inspect, :string) + SassC::Script::Value::String.new(options.inspect, :string) end def returns_sass_value - return SassC::Script::Value::Color.new(red: 0, green: 0, blue: 0) + SassC::Script::Value::Color.new(red: 0, green: 0, blue: 0) end def returns_sass_map - key = SassC::Script::Value::String.new("color", "string") + key = SassC::Script::Value::String.new('color', 'string') value = SassC::Script::Value::Color.new(red: 0, green: 0, blue: 0) values = {} values[key] = value - map = SassC::Script::Value::Map.new values - return map + SassC::Script::Value::Map.new values end def returns_sass_list numbers = [10, 20, 30].map { |n| SassC::Script::Value::Number.new(n, '') } SassC::Script::Value::List.new(numbers, separator: :space) end - end module ExternalFunctions def test_function - SassC::Script::Value::String.new("custom_function", :string) + SassC::Script::Value::String.new('custom_function', :string) end end - end end diff --git a/test/native_test.rb b/test/native_test.rb deleted file mode 100644 index 876f56cd..00000000 --- a/test/native_test.rb +++ /dev/null @@ -1,213 +0,0 @@ -# frozen_string_literal: true - -require_relative "test_helper" - -module SassC - module NativeTest - SAMPLE_SASS_STRING = "$size: 30px; .hi { width: $size; }" - SPECIAL_SASS_STRING = "$sißßßßßße: 30px; .hßß©i { width: $size; }" - SAMPLE_CSS_OUTPUT = ".hi {\n width: 30px; }\n" - BAD_SASS_STRING = "$size = 30px;" - - class General < MiniTest::Test - def test_it_reports_the_libsass_version - assert_equal "3.6.4", Native.version - end - end - - class DataContext < MiniTest::Test - def teardown - Native.delete_data_context(@data_context) if @data_context - end - - def test_compile_status_is_zero_when_successful - @data_context = Native.make_data_context(SAMPLE_SASS_STRING) - context = Native.data_context_get_context(@data_context) - - status = Native.compile_data_context(@data_context) - assert_equal 0, status - - status = Native.context_get_error_status(context) - assert_equal 0, status - end - - def test_compiled_css_is_correct - @data_context = Native.make_data_context(SAMPLE_SASS_STRING) - context = Native.data_context_get_context(@data_context) - Native.compile_data_context(@data_context) - - css = Native.context_get_output_string(context) - assert_equal SAMPLE_CSS_OUTPUT, css - end - - def test_compile_status_is_one_if_failed - @data_context = Native.make_data_context(BAD_SASS_STRING) - context = Native.data_context_get_context(@data_context) - - status = Native.compile_data_context(@data_context) - refute_equal 0, status - - status = Native.context_get_error_status(context) - refute_equal 0, status - end - - def test_multibyte_characters_work - @data_context = Native.make_data_context(SPECIAL_SASS_STRING) - context = Native.data_context_get_context(@data_context) - - status = Native.compile_data_context(@data_context) - refute_equal 0, status - end - - def test_custom_function - data_context = Native.make_data_context("foo { margin: foo(); }") - context = Native.data_context_get_context(data_context) - options = Native.context_get_options(context) - - random_thing = FFI::MemoryPointer.from_string("hi") - - funct = FFI::Function.new(:pointer, [:pointer, :pointer]) do |s_args, cookie| - Native.make_number(43, "px") - end - - callback = Native.make_function( - "foo()", - funct, - random_thing - ) - - list = Native.make_function_list(1) - Native::function_set_list_entry(list, 0, callback); - Native::option_set_c_functions(options, list) - - assert_equal Native.option_get_c_functions(options), list - - first_list_entry = Native.function_get_list_entry(list, 0) - assert_equal Native.function_get_function(first_list_entry), - funct - assert_equal Native.function_get_signature(first_list_entry), - "foo()" - assert_equal Native.function_get_cookie(first_list_entry), - random_thing - - string = Native.make_string("hello") - assert_equal :sass_string, Native.value_get_tag(string) - assert_equal "hello", Native.string_get_value(string) - - number = Native.make_number(123.4, "rem") - assert_equal 123.4, Native.number_get_value(number) - assert_equal "rem", Native.number_get_unit(number) - - Native.compile_data_context(data_context) - - css = Native.context_get_output_string(context) - assert_equal "foo {\n margin: 43px; }\n", css - end - end - - class FileContext < MiniTest::Test - include TempFileTest - - def teardown - Native.delete_file_context(@file_context) if @file_context - end - - def test_compile_status_is_zero_when_successful - temp_file("style.scss", SAMPLE_SASS_STRING) - - @file_context = Native.make_file_context("style.scss") - context = Native.file_context_get_context(@file_context) - - status = Native.compile_file_context(@file_context) - assert_equal 0, status - - status = Native.context_get_error_status(context) - assert_equal 0, status - end - - def test_compiled_css_is_correct - temp_file("style.scss", SAMPLE_SASS_STRING) - - @file_context = Native.make_file_context("style.scss") - context = Native.file_context_get_context(@file_context) - Native.compile_file_context(@file_context) - - css = Native.context_get_output_string(context) - assert_equal SAMPLE_CSS_OUTPUT, css - end - - def test_invalid_file_name - temp_file("style.scss", SAMPLE_SASS_STRING) - - @file_context = Native.make_file_context("style.jajaja") - context = Native.file_context_get_context(@file_context) - status = Native.compile_file_context(@file_context) - - refute_equal 0, status - - error = Native.context_get_error_message(context) - - assert_match "Error: File to read not found or unreadable: style.jajaja", - error - end - - def test_file_import - temp_file("not_included.scss", "$size: 30px;") - temp_file("import_parent.scss", "$size: 30px;") - temp_file("import.scss", "@import 'import_parent'; $size: 30px;") - temp_file("styles.scss", "@import 'import.scss'; .hi { width: $size; }") - - @file_context = Native.make_file_context("styles.scss") - context = Native.file_context_get_context(@file_context) - status = Native.compile_file_context(@file_context) - - assert_equal 0, status - - css = Native.context_get_output_string(context) - assert_equal SAMPLE_CSS_OUTPUT, css - - included_files = Native.context_get_included_files(context) - included_files.sort! - - assert_match /import.scss/, included_files[0] - assert_match /import_parent.scss/, included_files[1] - assert_match /styles.scss/, included_files[2] - end - - def test_custom_importer - temp_file("not_included.scss", "$size: $var + 25;") - temp_file("styles.scss", "@import 'import.scss'; .hi { width: $size; }") - - @file_context = Native.make_file_context("styles.scss") - context = Native.file_context_get_context(@file_context) - options = Native.context_get_options(context) - - funct = FFI::Function.new(:pointer, [:pointer, :pointer, :pointer]) do |url, prev, cookie| - list = Native.make_import_list(2) - - str = "$var: 5px;" - data = FFI::MemoryPointer.from_string(str) - data.autorelease = false - - entry0 = Native.make_import_entry("fake_includ.scss", data, nil) - entry1 = Native.make_import_entry("not_included.scss", nil, nil) - Native.import_set_list_entry(list, 0, entry0) - Native.import_set_list_entry(list, 1, entry1) - list - end - - callback = Native.make_importer(funct, nil) - list = Native.make_function_list(1) - Native::function_set_list_entry(list, 0, callback) - - Native.option_set_c_importers(options, list) - - status = Native.compile_file_context(@file_context) - assert_equal 0, status - - css = Native.context_get_output_string(context) - assert_equal SAMPLE_CSS_OUTPUT, css - end - end - end -end diff --git a/test/output_style_test.rb b/test/output_style_test.rb index f9a90597..7413b5b9 100644 --- a/test/output_style_test.rb +++ b/test/output_style_test.rb @@ -1,41 +1,44 @@ # frozen_string_literal: true -require_relative "test_helper" +require_relative 'test_helper' module SassC class OutputStyleTest < MiniTest::Test def input_scss - input_scss = <<-CSS -$color: #fff; - -#main { - color: $color; - background-color: #000; - p { - width: 10em; - } -} - -.huge { - font-size: 10em; - font-weight: bold; - text-decoration: underline; -} -CSS + <<~CSS + $color: #fff; + + #main { + color: $color; + background-color: #000; + p { + width: 10em; + } + } + + .huge { + font-size: 10em; + font-weight: bold; + text-decoration: underline; + } + CSS end def expected_nested_output - <<-CSS -#main { - color: #fff; - background-color: #000; } - #main p { - width: 10em; } - -.huge { - font-size: 10em; - font-weight: bold; - text-decoration: underline; } + <<~CSS + #main { + color: #fff; + background-color: #000; + } + #main p { + width: 10em; + } + + .huge { + font-size: 10em; + font-weight: bold; + text-decoration: underline; + } CSS end @@ -61,46 +64,53 @@ def test_nested_output def test_expanded_output engine = Engine.new(input_scss, style: :sass_style_expanded) - assert_equal <<-CSS, engine.render -#main { - color: #fff; - background-color: #000; -} - -#main p { - width: 10em; -} - -.huge { - font-size: 10em; - font-weight: bold; - text-decoration: underline; -} + assert_equal <<~CSS, engine.render + #main { + color: #fff; + background-color: #000; + } + #main p { + width: 10em; + } + + .huge { + font-size: 10em; + font-weight: bold; + text-decoration: underline; + } CSS end def test_compact_output engine = Engine.new(input_scss, style: :sass_style_compact) - assert_equal <<-CSS, engine.render -#main { color: #fff; background-color: #000; } - -#main p { width: 10em; } - -.huge { font-size: 10em; font-weight: bold; text-decoration: underline; } + assert_equal <<~CSS, engine.render + #main { + color: #fff; + background-color: #000; + } + #main p { + width: 10em; + } + + .huge { + font-size: 10em; + font-weight: bold; + text-decoration: underline; + } CSS end def test_compressed_output engine = Engine.new(input_scss, style: :sass_style_compressed) - assert_equal <<-CSS, engine.render -#main{color:#fff;background-color:#000}#main p{width:10em}.huge{font-size:10em;font-weight:bold;text-decoration:underline} + assert_equal <<~CSS, engine.render + #main{color:#fff;background-color:#000}#main p{width:10em}.huge{font-size:10em;font-weight:bold;text-decoration:underline} CSS end def test_short_output_style_names engine = Engine.new(input_scss, style: :compressed) - assert_equal <<-CSS, engine.render -#main{color:#fff;background-color:#000}#main p{width:10em}.huge{font-size:10em;font-weight:bold;text-decoration:underline} + assert_equal <<~CSS, engine.render + #main{color:#fff;background-color:#000}#main p{width:10em}.huge{font-size:10em;font-weight:bold;text-decoration:underline} CSS end end diff --git a/test/sass_2_scss_test.rb b/test/sass_2_scss_test.rb deleted file mode 100644 index 85282f31..00000000 --- a/test/sass_2_scss_test.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require_relative "test_helper" - -module SassC - class Sass2ScssTest < MiniTest::Test - def test_compact_output - assert_equal ".blat { color: red; }", Sass2Scss.convert(< Date: Sat, 22 Oct 2022 00:18:39 +0900 Subject: [PATCH 02/10] Update readme, add version --- CHANGELOG.md | 3 ++ README.md | 117 ++++++++++++------------------------------- lib/sassc/version.rb | 2 +- 3 files changed, 35 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a67984d..f1e7e191 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +- **3.0.0** + - [Replace libass with sass-embedded gem](https://github.com/sass/sassc-ruby/pull/240) + - **2.4.0** - [Update libsass to 3.6.4](https://github.com/sass/sassc-ruby/pull/199) - [Use FFI::MemoryPointer instead of libc's malloc()](https://github.com/sass/sassc-ruby/pull/205) diff --git a/README.md b/README.md index 1afd3313..38e7e15c 100644 --- a/README.md +++ b/README.md @@ -1,117 +1,62 @@ -# SassC [![Build Status](https://travis-ci.org/sass/sassc-ruby.svg?branch=master)](https://travis-ci.org/sass/sassc-ruby) [![Gem Version](https://badge.fury.io/rb/sassc.svg)](http://badge.fury.io/rb/sassc) +# SassC -Use `sassc-embedded` with SassC Ruby! +[![build](https://github.com/sass/sassc-ruby/actions/workflows/build.yml/badge.svg)](https://github.com/sass/sassc-ruby/actions/workflows/build.yml) +[![gem](https://badge.fury.io/rb/sassc-ruby.svg)](https://rubygems.org/gems/sassc-ruby) -This fork removes the deprecated [`libsass`](https://github.com/sass/libsass) and replace it with [`sassc-embedded`](https://github.com/ntkme/sassc-embedded-polyfill-ruby), providing latest sass features and fast gem installation. +Use [dart-sass](https://sass-lang.com/dart-sass) with Ruby! -This should essentially be a drop in alternative to [sass/sassc-ruby](https://github.com/sass/sassc-ruby). +## Version 3.0 - Now using Dart Sass +As of version 3.0, this gem (SassC) delegates to the +[sass-embedded gem](https://github.com/ntkme/sass-embedded-host-ruby) +which provides native binaries for Dart Sass. +Version 2.x and prior of this gem contained a native binaries +of the now-deprecated [libsass C implementation](https://github.com/sass/libsass). +These binaries have been *removed* as of version 3.0. +For ease of upgrading, the root namespace `::SassC` is still used by this gem, +although it is now a bit of a misnomer. +### Upgrading to Dart Sass +The interface between versions 3.0 and 2.x is largely unchanged, however: - - - - - - -# Embedded Sass Shim for SassC Ruby - -[![build](https://github.com/ntkme/sassc-embedded-shim-ruby/actions/workflows/build.yml/badge.svg)](https://github.com/ntkme/sassc-embedded-shim-ruby/actions/workflows/build.yml) -[![gem](https://badge.fury.io/rb/sassc-embedded.svg)](https://rubygems.org/gems/sassc-embedded) - -Use `sass-embedded` with SassC Ruby! - -This library shims [`sassc`](https://github.com/sass/sassc-ruby) with the [`sass-embedded`](https://github.com/ntkme/sass-embedded-host-ruby) implementation. - -It has been tested with: - -- [`sassc`](https://github.com/sass/sassc-ruby) -- [`sassc-rails`](https://github.com/sass/sassc-rails) -- [`sprockets`](https://github.com/rails/sprockets) -- [`sprockets-rails`](https://github.com/rails/sprockets-rails) - -## Install - -Add these lines to your application's Gemfile: - -``` ruby -gem 'sassc', github: 'sass/sassc-ruby', ref: 'refs/pull/233/head' -gem 'sassc-embedded' -``` - -And then execute: - -``` sh -bundle -``` - -Or install it yourself as: - -``` sh -gem install sassc-embedded -``` - -## Usage - -This shim utilizes `sass-embedded` to allow you to compile SCSS or SASS syntax to CSS. To compile, use a `SassC::Engine`, e.g.: - -``` ruby -require 'sassc-embedded' - -SassC::Engine.new(sass, style: :compressed).render -``` - -See [rubydoc.info/gems/sassc](https://rubydoc.info/gems/sassc) for full API documentation. - -## Behavioral Differences from SassC Ruby - -1. Option `:style => :nested` and `:style => :compact` behave as `:style => :expanded`. - +1. Option `style: :nested` and `style: :compact` behave as `style: :expanded`. Use `style: :compressed` for minification. 2. Option `:precision` is ignored. - 3. Option `:line_comments` is ignored. +4. `Sass2Scss` functionality has been removed. See [the dart-sass documentation](https://github.com/sass/dart-sass#behavioral-differences-from-ruby-sass) for other differences. - - - - ## Installation Add this line to your application's Gemfile: ```ruby -gem 'rails-sass-embedded' +gem 'sassc' ``` -And then execute: +Rails/Sprockets users should additionally add [sassc-rails](https://github.com/sass/sassc-rails): -```bash -bundle +```ruby +gem 'sassc-rails' ``` ## Usage -This library utilizes `libsass` to allow you to compile SCSS or SASS syntax -to CSS. To compile, use a `SassC::Engine`, e.g.: +This library utilizes [dart-sass](https://github.com/sass/dart-sass) to compile +SCSS or SASS syntax to CSS. To compile, use a `SassC::Engine`, e.g.: ```ruby -SassC::Engine.new(sass, style: :compressed).render +SassC::Engine.new(".klass1, .klass2 { color: :red; }", style: :compressed).render ``` -**Note**: If you want to use this library with Rails/Sprockets, check out -[sassc-rails](https://github.com/bolandrm/sassc-rails). - -Additionally, you can use `SassC::Sass2Scss` to convert Sass syntax to Scss syntax. - ## Credits -This gem is maintained by [Ryan Boland](https://ryanboland.com) -and [awesome contributors](https://github.com/bolandrm/sassc-ruby/graphs/contributors). +* This gem is maintained by [Ryan Boland](https://ryanboland.com) +* Kudos to [@ntkme](https://github.com/ntkme) for dart-sass support. +* See our [awesome contributors](https://github.com/sassc/sassc-ruby/graphs/contributors). ## Changelog @@ -128,7 +73,7 @@ See [CHANGELOG.md](CHANGELOG.md). ### Code Changes 1. Fork it ( https://github.com/sass/sassc-ruby/fork ) -1. Create your feature branch (`git checkout -b my-new-feature`) -1. Commit your changes (`git commit -am 'Add some feature'`) - try to include tests -1. Push to the branch (`git push origin my-new-feature`) -1. Create a new Pull Request +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) - try to include tests +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request diff --git a/lib/sassc/version.rb b/lib/sassc/version.rb index 23ced755..7182e774 100644 --- a/lib/sassc/version.rb +++ b/lib/sassc/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SassC - VERSION = "0.0.1" + VERSION = '3.0.0' end From 0567b490aa863f4a6fa064265540bd5aa19a0683 Mon Sep 17 00:00:00 2001 From: johnnyshields Date: Sat, 22 Oct 2022 00:29:18 +0900 Subject: [PATCH 03/10] Add note to gemfile --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 38e7e15c..59a982fa 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,12 @@ SCSS or SASS syntax to CSS. To compile, use a `SassC::Engine`, e.g.: SassC::Engine.new(".klass1, .klass2 { color: :red; }", style: :compressed).render ``` +## Alternatives + +* [dartsass-rails](https://github.com/rails/dartsass-rails): Rails organization +maintains its own wrapper for dart-sass. Unlike this gem, dartsass-rails does +not support Sprockets. + ## Credits * This gem is maintained by [Ryan Boland](https://ryanboland.com) From 70f2635af00f27b0059f077ae3e815a1cadf84a2 Mon Sep 17 00:00:00 2001 From: johnnyshields Date: Sat, 22 Oct 2022 00:31:35 +0900 Subject: [PATCH 04/10] Ad GH actions --- .github/dependabot.yml | 11 ++++ .github/workflows/build.yml | 100 ++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 27 +++++++++ README.md | 2 +- 4 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..186103da --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + + - package-ecosystem: "bundler" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..758dd647 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,100 @@ +name: build + +on: [push, pull_request] + +jobs: + lint: + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ruby + bundler-cache: true + + - name: Lint + run: bundle exec rake rubocop + + test: + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + ruby-version: ['2.6', '2.7', '3.0', 'ruby', 'jruby'] + exclude: + - os: windows-latest + ruby-version: jruby + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + + - name: Test + run: bundle exec rake test + + test-vendor: + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + ruby-version: ['2.6', '2.7', '3.0', 'ruby', 'jruby'] + submodule: + - vendor/github.com/sass/sassc-rails + - vendor/github.com/twbs/bootstrap + exclude: + - os: windows-latest + ruby-version: jruby + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + + - name: Test + run: bundle exec rake git:submodule:test[${{matrix.submodule}}] + + release: + + if: github.event.repository.fork == false && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + + needs: [lint, test, test-vendor] + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ssh-key: ${{ secrets.DEPLOY_KEY }} + + - name: Setup ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ruby + + - name: Release + run: | + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git config user.name github-actions[bot] + rake -f -r bundler/gem_tasks release gem_push=no diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..26936724 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,27 @@ +name: release + +on: + push: + tags: + - 'v*' + +jobs: + release: + + if: github.event.repository.fork == false + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ruby + + - name: Release + run: rake -f -r bundler/gem_tasks release + env: + GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }} diff --git a/README.md b/README.md index 59a982fa..608de9bb 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ See [CHANGELOG.md](CHANGELOG.md). ### Code Changes -1. Fork it ( https://github.com/sass/sassc-ruby/fork ) +1. Fork it ([https://github.com/sass/sassc-ruby/fork](https://github.com/sass/sassc-ruby/fork)) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) - try to include tests 4. Push to the branch (`git push origin my-new-feature`) From 2c35279aa298bee2c5206f9be23d92b51e04199d Mon Sep 17 00:00:00 2001 From: johnnyshields Date: Sat, 22 Oct 2022 01:07:50 +0900 Subject: [PATCH 05/10] Rubocop, etc. --- .rubocop.yml | 23 ++ .rubocop_todo.yml | 455 ++++++++++++++++++++++++++++++++ CHANGELOG.md | 3 + lib/sassc.rb | 20 +- lib/sassc/script/value/color.rb | 16 +- lib/sassc/util.rb | 220 +-------------- 6 files changed, 497 insertions(+), 240 deletions(-) create mode 100644 .rubocop.yml create mode 100644 .rubocop_todo.yml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..af052e19 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,23 @@ +inherit_from: .rubocop_todo.yml + +require: + - rubocop-minitest + - rubocop-performance + - rubocop-rake + +AllCops: + Exclude: + - 'vendor/**/*' + + TargetRubyVersion: 2.6 + + NewCops: enable + +Metrics: + Enabled: false + +Minitest/MultipleAssertions: + Enabled: false + +Style/Documentation: + Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 00000000..5aa0c61f --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,455 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2022-10-21 15:49:29 UTC using RuboCop version 1.37.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Include. +# Include: **/*.gemspec +Gemspec/DeprecatedAttributeAssignment: + Exclude: + - 'sassc.gemspec' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Include. +# Include: **/*.gemspec +Gemspec/RequireMFA: + Exclude: + - 'sassc.gemspec' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyleAlignWith, Severity. +# SupportedStylesAlignWith: start_of_line, begin +Layout/BeginEndAlignment: + Exclude: + - 'lib/sassc/engine.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: leading, trailing +Layout/DotPosition: + Exclude: + - 'lib/sassc/script/value/list.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Layout/ElseAlignment: + Exclude: + - 'lib/sassc/import_handler.rb' + +# Offense count: 18 +# This cop supports safe autocorrection (--autocorrect). +Layout/EmptyLineAfterGuardClause: + Exclude: + - 'lib/sassc/engine.rb' + - 'lib/sassc/error.rb' + - 'lib/sassc/script/value.rb' + - 'lib/sassc/script/value/color.rb' + - 'lib/sassc/script/value/list.rb' + - 'lib/sassc/script/value/number.rb' + - 'lib/sassc/script/value/string.rb' + - 'lib/sassc/util.rb' + +# Offense count: 15 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only +Layout/EmptyLinesAroundClassBody: + Exclude: + - 'lib/sassc/error.rb' + - 'lib/sassc/script/value.rb' + - 'lib/sassc/script/value/bool.rb' + - 'lib/sassc/script/value/color.rb' + - 'lib/sassc/script/value/list.rb' + - 'lib/sassc/script/value/map.rb' + - 'lib/sassc/script/value/number.rb' + - 'lib/sassc/script/value/string.rb' + +# Offense count: 4 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines +Layout/EmptyLinesAroundModuleBody: + Exclude: + - 'lib/sassc/error.rb' + - 'lib/sassc/script.rb' + - 'lib/sassc/script/value_conversion.rb' + - 'lib/sassc/util.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyleAlignWith, Severity. +# SupportedStylesAlignWith: keyword, variable, start_of_line +Layout/EndAlignment: + Exclude: + - 'lib/sassc/import_handler.rb' + +# Offense count: 14 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. +# SupportedHashRocketStyles: key, separator, table +# SupportedColonStyles: key, separator, table +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Layout/HashAlignment: + Exclude: + - 'lib/sassc/script/value/number.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: Width, AllowedPatterns, IgnoredPatterns. +Layout/IndentationWidth: + Exclude: + - 'lib/sassc/import_handler.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: symmetrical, new_line, same_line +Layout/MultilineMethodCallBraceLayout: + Exclude: + - 'lib/sassc/script/value/list.rb' + - 'lib/sassc/script/value/number.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: aligned, indented, indented_relative_to_receiver +Layout/MultilineMethodCallIndentation: + Exclude: + - 'lib/sassc/script/value/list.rb' + +# Offense count: 7 +# This cop supports safe autocorrection (--autocorrect). +Layout/SpaceAfterColon: + Exclude: + - 'lib/sassc/script/value/color.rb' + +# Offense count: 16 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. +# SupportedStyles: space, no_space +# SupportedStylesForEmptyBraces: space, no_space +Layout/SpaceInsideBlockBraces: + Exclude: + - 'lib/sassc/script/value/list.rb' + - 'lib/sassc/script/value/map.rb' + - 'lib/sassc/script/value/number.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Lint/AmbiguousOperatorPrecedence: + Exclude: + - 'lib/sassc/script/value/list.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: DeprecatedConstants. +Lint/DeprecatedConstants: + Exclude: + - 'lib/sassc/script/value/bool.rb' + +# Offense count: 2 +Lint/FloatComparison: + Exclude: + - 'lib/sassc/script/value/color.rb' + +# Offense count: 2 +Lint/IneffectiveAccessModifier: + Exclude: + - 'lib/sassc/script/value/number.rb' + +# Offense count: 1 +Lint/MissingSuper: + Exclude: + - 'lib/sassc/script/value/color.rb' + +# Offense count: 6 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods. +Lint/UnusedMethodArgument: + Exclude: + - 'lib/sassc/script/value.rb' + - 'lib/sassc/script/value/bool.rb' + - 'lib/sassc/script/value/map.rb' + - 'lib/sassc/script/value/number.rb' + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +Lint/UselessMethodDefinition: + Exclude: + - 'lib/sassc/script/value/map.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Naming/BinaryOperatorParameterName: + Exclude: + - 'lib/sassc/script/value/color.rb' + +# Offense count: 1 +# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. +# AllowedNames: as, at, by, db, id, in, io, ip, of, on, os, pp, to +Naming/MethodParameterName: + Exclude: + - 'lib/sassc/script/value/list.rb' + +# Offense count: 1 +# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros. +# NamePrefix: is_, has_, have_ +# ForbiddenPrefixes: is_, has_, have_ +# AllowedMethods: is_a? +# MethodDefinitionMacros: define_method, define_singleton_method +Naming/PredicateName: + Exclude: + - 'spec/**/*' + - 'lib/sassc/script/value/number.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Performance/RegexpMatch: + Exclude: + - 'lib/sassc/script/value/string.rb' + +# Offense count: 9 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: separated, grouped +Style/AccessorGrouping: + Exclude: + - 'lib/sassc/dependency.rb' + - 'lib/sassc/script/value/color.rb' + +# Offense count: 8 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: prefer_alias, prefer_alias_method +Style/Alias: + Exclude: + - 'lib/sassc/script/value.rb' + - 'lib/sassc/script/value/bool.rb' + - 'lib/sassc/script/value/color.rb' + - 'lib/sassc/script/value/list.rb' + - 'lib/sassc/script/value/map.rb' + - 'lib/sassc/script/value/number.rb' + +# Offense count: 9 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: nested, compact +Style/ClassAndModuleChildren: + Exclude: + - 'lib/sassc/script/value.rb' + - 'lib/sassc/script/value/bool.rb' + - 'lib/sassc/script/value/color.rb' + - 'lib/sassc/script/value/list.rb' + - 'lib/sassc/script/value/map.rb' + - 'lib/sassc/script/value/number.rb' + - 'lib/sassc/script/value/string.rb' + - 'lib/sassc/script/value_conversion.rb' + - 'lib/sassc/util.rb' + +# Offense count: 3 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. +# SupportedStyles: assign_to_condition, assign_inside_condition +Style/ConditionalAssignment: + Exclude: + - 'lib/sassc/script/value/string.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, AllowComments. +# SupportedStyles: empty, nil, both +Style/EmptyElse: + Exclude: + - 'lib/sassc/import_handler.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/ExpandPathArguments: + Exclude: + - 'sassc.gemspec' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: format, sprintf, percent +Style/FormatString: + Exclude: + - 'lib/sassc/script/value/number.rb' + +# Offense count: 2 +# Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. +Style/GuardClause: + Exclude: + - 'lib/sassc/script/value/color.rb' + - 'lib/sassc/script/value/list.rb' + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowedReceivers. +Style/HashEachMethods: + Exclude: + - 'lib/sassc/script/value/number.rb' + +# Offense count: 2 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, EnforcedShorthandSyntax, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. +# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys +# SupportedShorthandSyntax: always, never, either, consistent +Style/HashSyntax: + Exclude: + - 'lib/sassc/script/value/string.rb' + +# Offense count: 8 +# This cop supports safe autocorrection (--autocorrect). +Style/IfUnlessModifier: + Exclude: + - 'lib/sassc/import_handler.rb' + - 'lib/sassc/script/value/color.rb' + - 'lib/sassc/script/value/list.rb' + - 'lib/sassc/script/value/number.rb' + - 'test/functions_test.rb' + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle, Autocorrect. +# SupportedStyles: module_function, extend_self, forbidden +Style/ModuleFunction: + Exclude: + - 'lib/sassc/util.rb' + +# Offense count: 5 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: literals, strict +Style/MutableConstant: + Exclude: + - 'lib/sassc/engine.rb' + - 'lib/sassc/script/value/number.rb' + +# Offense count: 3 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns, IgnoredMethods. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Exclude: + - 'spec/**/*' + - 'lib/sassc/script/value/list.rb' + - 'lib/sassc/script/value/number.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/PerlBackrefs: + Exclude: + - 'lib/sassc/script/value/number.rb' + +# Offense count: 10 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, AllowedCompactTypes. +# SupportedStyles: compact, exploded +Style/RaiseArgs: + Exclude: + - 'lib/sassc/script/value.rb' + - 'lib/sassc/script/value/list.rb' + - 'lib/sassc/script/value/map.rb' + - 'lib/sassc/script/value/number.rb' + +# Offense count: 7 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowMultipleReturnValues. +Style/RedundantReturn: + Exclude: + - 'lib/sassc/script.rb' + - 'lib/sassc/script/value/color.rb' + - 'lib/sassc/script/value/number.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/RedundantSelf: + Exclude: + - 'lib/sassc/script/value/color.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowIfMethodIsEmpty. +Style/SingleLineMethods: + Exclude: + - 'lib/sassc/script/value.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Style/StderrPuts: + Exclude: + - 'lib/sassc/functions_handler.rb' + +# Offense count: 56 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiterals: + Exclude: + - 'Gemfile' + - 'lib/sassc.rb' + - 'lib/sassc/engine.rb' + - 'lib/sassc/error.rb' + - 'lib/sassc/functions_handler.rb' + - 'lib/sassc/script.rb' + - 'lib/sassc/script/value/color.rb' + - 'lib/sassc/script/value/list.rb' + - 'lib/sassc/script/value/map.rb' + - 'lib/sassc/script/value/number.rb' + - 'lib/sassc/script/value/string.rb' + - 'sassc.gemspec' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: . +# SupportedStyles: percent, brackets +Style/SymbolArray: + EnforcedStyle: percent + MinSize: 8 + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, IgnoredMethods, AllowComments. +# AllowedMethods: respond_to, define_method +Style/SymbolProc: + Exclude: + - 'lib/sassc/script/value/list.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInArrayLiteral: + Exclude: + - 'lib/sassc/script/value/color.rb' + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: forbid_for_all_comparison_operators, forbid_for_equality_operators_only, require_for_all_comparison_operators, require_for_equality_operators_only +Style/YodaCondition: + Exclude: + - 'lib/sassc/script/value/number.rb' + +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +Style/ZeroLengthPredicate: + Exclude: + - 'lib/sassc/script/value/list.rb' + - 'lib/sassc/script/value/number.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, IgnoredPatterns. +# URISchemes: http, https +Layout/LineLength: + Max: 240 diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e7e191..3da55867 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ - **3.0.0** - [Replace libass with sass-embedded gem](https://github.com/sass/sassc-ruby/pull/240) + - [Require minimum Ruby version to 2.6](https://github.com/sass/sassc-ruby/pull/240) + - [Replace Travis CI with Github Actions](https://github.com/sass/sassc-ruby/pull/240) + - [Replace Travis CI with Github Actions](https://github.com/sass/sassc-ruby/pull/240) - **2.4.0** - [Update libsass to 3.6.4](https://github.com/sass/sassc-ruby/pull/199) diff --git a/lib/sassc.rb b/lib/sassc.rb index 8cc9d5b7..feab0110 100644 --- a/lib/sassc.rb +++ b/lib/sassc.rb @@ -27,11 +27,16 @@ def self.load_paths end end +require 'sass-embedded' +require 'base64' +require 'json' +require 'uri' +require 'set' + require_relative "sassc/version" require_relative "sassc/import_handler" require_relative "sassc/importer" require_relative "sassc/util" -# require_relative "sassc/util/normalized_map" require_relative "sassc/script" require_relative "sassc/script/value" require_relative "sassc/script/value/bool" @@ -48,16 +53,3 @@ def self.load_paths require_relative "sassc/url" require_relative "sassc/protocol" require_relative "sassc/engine" - -# ####### -# require 'sassc' -require 'sass-embedded' - -require 'base64' -require 'json' -require 'uri' - -# require_relative 'embedded/version' -# require_relative 'sassc/embedded' -############# - diff --git a/lib/sassc/script/value/color.rb b/lib/sassc/script/value/color.rb index 1827e459..3b793fe9 100644 --- a/lib/sassc/script/value/color.rb +++ b/lib/sassc/script/value/color.rb @@ -22,16 +22,16 @@ class SassC::Script::Value::Color < SassC::Script::Value def initialize(red:nil, green:nil, blue:nil, hue:nil, saturation:nil, lightness:nil, alpha:1.0) if red && green && blue && alpha @mode = :rgba - @red = SassC::Util.clamp(red.to_i, 0, 255) - @green = SassC::Util.clamp(green.to_i, 0, 255) - @blue = SassC::Util.clamp(blue.to_i, 0, 255) - @alpha = SassC::Util.clamp(alpha.to_f, 0.0, 1.0) + @red = red.to_i.clamp(0, 255) + @green = green.to_i.clamp(0, 255) + @blue = blue.to_i.clamp(0, 255) + @alpha = alpha.to_f.clamp(0.0, 1.0) elsif hue && saturation && lightness && alpha @mode = :hsla - @hue = SassC::Util.clamp(hue.to_i, 0, 360) - @saturation = SassC::Util.clamp(saturation.to_i, 0, 100) - @lightness = SassC::Util.clamp(lightness.to_i, 0, 100) - @alpha = SassC::Util.clamp(alpha.to_f, 0.0, 1.0) + @hue = hue.to_i.clamp(0, 360) + @saturation = saturation.to_i.clamp(0, 100) + @lightness = lightness.to_i.clamp(0, 100) + @alpha = alpha.to_f.clamp(0.0, 1.0) else raise SassC::UnsupportedValue, "Unable to determine color configuration for " end diff --git a/lib/sassc/util.rb b/lib/sassc/util.rb index 546a4989..3ad84557 100644 --- a/lib/sassc/util.rb +++ b/lib/sassc/util.rb @@ -1,231 +1,15 @@ # frozen_string_literal: true -require "erb" -require "set" -require "enumerator" -require "stringio" -require "rbconfig" -require "uri" -require "thread" -require "pathname" - -# A module containing various useful functions. - +# # A module containing various useful functions. module SassC::Util extend self - # An array of ints representing the Ruby version number. - # @api public - RUBY_VERSION_COMPONENTS = RUBY_VERSION.split(".").map {|s| s.to_i} - - # The Ruby engine we're running under. Defaults to `"ruby"` - # if the top-level constant is undefined. - # @api public - RUBY_ENGINE = defined?(::RUBY_ENGINE) ? ::RUBY_ENGINE : "ruby" - - # Maps the keys in a hash according to a block. - # @example - # map_keys({:foo => "bar", :baz => "bang"}) {|k| k.to_s} - # #=> {"foo" => "bar", "baz" => "bang"} - # @param hash [Hash] The hash to map - # @yield [key] A block in which the keys are transformed - # @yieldparam key [Object] The key that should be mapped - # @yieldreturn [Object] The new value for the key - # @return [Hash] The mapped hash - # @see #map_vals - # @see #map_hash - def map_keys(hash) - map_hash(hash) {|k, v| [yield(k), v]} - end - - # Restricts the numeric `value` to be within `min` and `max`, inclusive. - # If the value is lower than `min` - def clamp(value, min, max) - return min if value < min - return max if value > max - return value - end - - # Like [Fixnum.round], but leaves rooms for slight floating-point - # differences. - # - # @param value [Numeric] - # @return [Numeric] - def round(value) - # If the number is within epsilon of X.5, round up (or down for negative - # numbers). - mod = value % 1 - mod_is_half = (mod - 0.5).abs < SassC::Script::Value::Number.epsilon - if value > 0 - !mod_is_half && mod < 0.5 ? value.floor : value.ceil - else - mod_is_half || mod < 0.5 ? value.floor : value.ceil - end - end - - # Return an array of all possible paths through the given arrays. - # - # @param arrs [Array] - # @return [Array] - # - # @example - # paths([[1, 2], [3, 4], [5]]) #=> - # # [[1, 3, 5], - # # [2, 3, 5], - # # [1, 4, 5], - # # [2, 4, 5]] - def paths(arrs) - arrs.inject([[]]) do |paths, arr| - arr.map {|e| paths.map {|path| path + [e]}}.flatten(1) - end - end - - # Returns information about the caller of the previous method. - # - # @param entry [String] An entry in the `#caller` list, or a similarly formatted string - # @return [[String, Integer, (String, nil)]] - # An array containing the filename, line, and method name of the caller. - # The method name may be nil - def caller_info(entry = nil) - # JRuby evaluates `caller` incorrectly when it's in an actual default argument. - entry ||= caller[1] - info = entry.scan(/^((?:[A-Za-z]:)?.*?):(-?.*?)(?::.*`(.+)')?$/).first - info[1] = info[1].to_i - # This is added by Rubinius to designate a block, but we don't care about it. - info[2].sub!(/ \{\}\Z/, '') if info[2] - info - end - - # Throws a NotImplementedError for an abstract method. - # - # @param obj [Object] `self` - # @raise [NotImplementedError] - def abstract(obj) - raise NotImplementedError.new("#{obj.class} must implement ##{caller_info[2]}") - end - - # Prints a deprecation warning for the caller method. - # - # @param obj [Object] `self` - # @param message [String] A message describing what to do instead. - def deprecated(obj, message = nil) - obj_class = obj.is_a?(Class) ? "#{obj}." : "#{obj.class}#" - full_message = "DEPRECATION WARNING: #{obj_class}#{caller_info[2]} " + - "will be removed in a future version of Sass.#{("\n" + message) if message}" - SassC::Util.sass_warn full_message - end - - # Silences all Sass warnings within a block. - # - # @yield A block in which no Sass warnings will be printed - def silence_sass_warnings - old_level, Sass.logger.log_level = Sass.logger.log_level, :error - yield - ensure - SassC.logger.log_level = old_level - end - - # The same as `Kernel#warn`, but is silenced by \{#silence\_sass\_warnings}. - # - # @param msg [String] - def sass_warn(msg) - Sass.logger.warn("#{msg}\n") - end - - ## Cross Rails Version Compatibility - - # Returns the root of the Rails application, - # if this is running in a Rails context. - # Returns `nil` if no such root is defined. - # - # @return [String, nil] - def rails_root - if defined?(::Rails.root) - return ::Rails.root.to_s if ::Rails.root - raise "ERROR: Rails.root is nil!" - end - return RAILS_ROOT.to_s if defined?(RAILS_ROOT) - nil - end - - # Returns the environment of the Rails application, - # if this is running in a Rails context. - # Returns `nil` if no such environment is defined. - # - # @return [String, nil] - def rails_env - return ::Rails.env.to_s if defined?(::Rails.env) - return RAILS_ENV.to_s if defined?(RAILS_ENV) - nil - end - - ## Cross-OS Compatibility - # - # These methods are cached because some of them are called quite frequently - # and even basic checks like String#== are too costly to be called repeatedly. - # Whether or not this is running on Windows. # # @return [Boolean] def windows? return @windows if defined?(@windows) - @windows = (RbConfig::CONFIG['host_os'] =~ /mswin|windows|mingw/i) - end - - # Whether or not this is running on IronRuby. - # - # @return [Boolean] - def ironruby? - return @ironruby if defined?(@ironruby) - @ironruby = RUBY_ENGINE == "ironruby" - end - - # Whether or not this is running on Rubinius. - # - # @return [Boolean] - def rbx? - return @rbx if defined?(@rbx) - @rbx = RUBY_ENGINE == "rbx" - end - - # Whether or not this is running on JRuby. - # - # @return [Boolean] - def jruby? - return @jruby if defined?(@jruby) - @jruby = RUBY_PLATFORM =~ /java/ - end - - # Returns an array of ints representing the JRuby version number. - # - # @return [Array] - def jruby_version - @jruby_version ||= ::JRUBY_VERSION.split(".").map {|s| s.to_i} + @windows = RbConfig::CONFIG['host_os'].match?(/mswin|windows|mingw/i) end - - # Returns `path` relative to `from`. - # - # This is like `Pathname#relative_path_from` except it accepts both strings - # and pathnames, it handles Windows path separators correctly, and it throws - # an error rather than crashing if the paths use different encodings - # (https://github.com/ruby/ruby/pull/713). - # - # @param path [String, Pathname] - # @param from [String, Pathname] - # @return [Pathname?] - def relative_path_from(path, from) - pathname(path.to_s).relative_path_from(pathname(from.to_s)) - rescue NoMethodError => e - raise e unless e.name == :zero? - - # Work around https://github.com/ruby/ruby/pull/713. - path = path.to_s - from = from.to_s - raise ArgumentError("Incompatible path encodings: #{path.inspect} is #{path.encoding}, " + - "#{from.inspect} is #{from.encoding}") - end - - singleton_methods.each {|method| module_function method} - end From 8bd446d6ac17d28cfec238cbd7c2f68cfce35b97 Mon Sep 17 00:00:00 2001 From: johnnyshields Date: Sat, 22 Oct 2022 01:25:30 +0900 Subject: [PATCH 06/10] Add shim for Sass2Scss --- lib/sassc.rb | 1 + lib/sassc/sass_2_scss.rb | 12 ++++++++++++ test/sass_2_scss_test.rb | 16 ++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 lib/sassc/sass_2_scss.rb create mode 100644 test/sass_2_scss_test.rb diff --git a/lib/sassc.rb b/lib/sassc.rb index feab0110..aca02cfb 100644 --- a/lib/sassc.rb +++ b/lib/sassc.rb @@ -50,6 +50,7 @@ def self.load_paths require_relative "sassc/functions_handler" require_relative "sassc/dependency" require_relative "sassc/error" +require_relative "sassc/sass_2_scss" require_relative "sassc/url" require_relative "sassc/protocol" require_relative "sassc/engine" diff --git a/lib/sassc/sass_2_scss.rb b/lib/sassc/sass_2_scss.rb new file mode 100644 index 00000000..0d859236 --- /dev/null +++ b/lib/sassc/sass_2_scss.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module SassC + class Sass2Scss + def self.convert(sass) + { + contents: sass, + syntax: :indented + } + end + end +end diff --git a/test/sass_2_scss_test.rb b/test/sass_2_scss_test.rb new file mode 100644 index 00000000..d30914ad --- /dev/null +++ b/test/sass_2_scss_test.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +module SassC + class Sass2ScssTest < MiniTest::Test + def test_compact_output + exp = { contents: ".blat\n color: red\n", + syntax: :indented } + assert_equal exp, Sass2Scss.convert(< Date: Sat, 22 Oct 2022 01:26:52 +0900 Subject: [PATCH 07/10] Fix rubocop --- .rubocop_todo.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5aa0c61f..3ae35d01 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2022-10-21 15:49:29 UTC using RuboCop version 1.37.0. +# on 2022-10-21 16:25:20 UTC using RuboCop version 1.37.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -101,6 +101,12 @@ Layout/HashAlignment: Exclude: - 'lib/sassc/script/value/number.rb' +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Layout/HeredocIndentation: + Exclude: + - 'test/sass_2_scss_test.rb' + # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: Width, AllowedPatterns, IgnoredPatterns. @@ -389,7 +395,7 @@ Style/StderrPuts: Exclude: - 'lib/sassc/functions_handler.rb' -# Offense count: 56 +# Offense count: 58 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes @@ -407,6 +413,7 @@ Style/StringLiterals: - 'lib/sassc/script/value/number.rb' - 'lib/sassc/script/value/string.rb' - 'sassc.gemspec' + - 'test/sass_2_scss_test.rb' # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). From 4606e34052ff7841ee81e4e237a318968af57805 Mon Sep 17 00:00:00 2001 From: Johnny Shields Date: Wed, 18 Jan 2023 17:12:01 +0900 Subject: [PATCH 08/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3da55867..a571f4f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ - **3.0.0** - - [Replace libass with sass-embedded gem](https://github.com/sass/sassc-ruby/pull/240) + - [Replace libsass with sass-embedded gem](https://github.com/sass/sassc-ruby/pull/240) - [Require minimum Ruby version to 2.6](https://github.com/sass/sassc-ruby/pull/240) - [Replace Travis CI with Github Actions](https://github.com/sass/sassc-ruby/pull/240) - [Replace Travis CI with Github Actions](https://github.com/sass/sassc-ruby/pull/240) From e8a84b3d780fbf051af30f3ac215e8878e51ce63 Mon Sep 17 00:00:00 2001 From: Rafael dos Santos Silva Date: Wed, 18 Jan 2023 14:07:37 -0300 Subject: [PATCH 09/10] FEATURE: Option to disable source map path validation --- lib/sassc/engine.rb | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/sassc/engine.rb b/lib/sassc/engine.rb index 3d1d1483..a6b83602 100644 --- a/lib/sassc/engine.rb +++ b/lib/sassc/engine.rb @@ -106,6 +106,10 @@ def source_map_file @options[:source_map_file] end + def validate_source_map_path? + @options.fetch(:validate_source_map_path, true) + end + def import_handler @import_handler ||= ImportHandler.new(@options) end @@ -130,7 +134,13 @@ def output_url end def source_map_file_url - @source_map_file_url ||= (URL.path_to_file_url(File.absolute_path(source_map_file)) if source_map_file) + return unless source_map_file + @source_map_file_url ||= + if validate_source_map_path? + URL.path_to_file_url(File.absolute_path(source_map_file)) + else + source_map_file + end end def output_style_enum @@ -172,9 +182,13 @@ def post_process_source_map(source_map) url = URL.parse(source_map_file_url || file_url) data = JSON.parse(source_map) - data['file'] = URL.parse(output_url).route_from(url).to_s if output_url - data['sources'].map! do |source| - if source.start_with?(Protocol::FILE) + data["file"] = if validate_source_map_path? + URL.parse(output_url).route_from(url).to_s + else + output_url + end + data["sources"].map! do |source| + if source.start_with?(Protocol::FILE) && validate_source_map_path? URL.parse(source).route_from(url).to_s else source @@ -188,11 +202,15 @@ def post_process_css(css) css += "\n" unless css.empty? unless @source_map.nil? || omit_source_map_url? url = URL.parse(output_url || file_url) - source_mapping_url = if source_map_embed? - "data:application/json;base64,#{Base64.strict_encode64(@source_map)}" - else - URL.parse(source_map_file_url).route_from(url).to_s - end + source_mapping_url = + if source_map_embed? + "data:application/json;base64,#{Base64.strict_encode64(@source_map)}" + else + if validate_source_map_path? + URL.parse(source_map_file_url).route_from(url).to_s + else + source_map_file_url + end css += "\n/*# sourceMappingURL=#{source_mapping_url} */" end css From 1c6e527f8f219b6476d968d54f3d57b30da0a1db Mon Sep 17 00:00:00 2001 From: Rafael dos Santos Silva Date: Wed, 18 Jan 2023 14:28:29 -0300 Subject: [PATCH 10/10] FIX: Missing end for if block --- lib/sassc/engine.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/sassc/engine.rb b/lib/sassc/engine.rb index a6b83602..6c1d4825 100644 --- a/lib/sassc/engine.rb +++ b/lib/sassc/engine.rb @@ -211,6 +211,7 @@ def post_process_css(css) else source_map_file_url end + end css += "\n/*# sourceMappingURL=#{source_mapping_url} */" end css