diff --git a/lib/sassc/engine.rb b/lib/sassc/engine.rb index 935b547d..a9d5449b 100644 --- a/lib/sassc/engine.rb +++ b/lib/sassc/engine.rb @@ -16,6 +16,7 @@ class Engine def initialize(template, options = {}) @template = template @options = options + @functions = options.fetch(:functions, Script::Functions) end def render @@ -37,7 +38,7 @@ def render 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_handler.setup(native_options, functions: @functions) status = Native.compile_data_context(data_context) diff --git a/lib/sassc/functions_handler.rb b/lib/sassc/functions_handler.rb index 97a10973..8a3c3495 100644 --- a/lib/sassc/functions_handler.rb +++ b/lib/sassc/functions_handler.rb @@ -6,24 +6,24 @@ def initialize(options) @options = options end - def setup(native_options) + def setup(native_options, functions: Script::Functions) @callbacks = {} @function_names = {} - list = Native.make_function_list(Script.custom_functions.count) + list = Native.make_function_list(Script.custom_functions(functions: functions).count) # use an anonymous class wrapper to avoid mutations in a threaded environment - functions = Class.new do + functions_wrapper = Class.new do attr_accessor :options - include Script::Functions + include functions end.new - functions.options = @options + functions_wrapper.options = @options - Script.custom_functions.each_with_index do |custom_function, i| + 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| begin function_arguments = arguments_from_native_list(native_argument_list) - result = functions.send(custom_function, *function_arguments) + 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 @@ -32,7 +32,7 @@ def setup(native_options) end end - @function_names[custom_function] = Script.formatted_function_name(custom_function) + @function_names[custom_function] = Script.formatted_function_name(custom_function, functions: functions) callback = Native.make_function( @function_names[custom_function], diff --git a/lib/sassc/script.rb b/lib/sassc/script.rb index aecb9874..0eed756a 100644 --- a/lib/sassc/script.rb +++ b/lib/sassc/script.rb @@ -3,12 +3,12 @@ module SassC module Script - def self.custom_functions - Functions.public_instance_methods + def self.custom_functions(functions: Functions) + functions.public_instance_methods end - def self.formatted_function_name(function_name) - params = Functions.instance_method(function_name).parameters + def self.formatted_function_name(function_name, functions: Functions) + params = functions.instance_method(function_name).parameters params = params.map { |param_type, name| "$#{name}#{': null' if param_type == :opt}" }.join(", ") return "#{function_name}(#{params})" end diff --git a/test/functions_test.rb b/test/functions_test.rb index 5cf7aeca..4b25c4bd 100644 --- a/test/functions_test.rb +++ b/test/functions_test.rb @@ -201,6 +201,17 @@ def test_concurrency end end + def test_pass_custom_functions_as_a_parameter + 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 + end + end + private def assert_sass(sass, expected_css) @@ -319,5 +330,11 @@ def returns_sass_list end + module ExternalFunctions + def test_function + SassC::Script::Value::String.new("custom_function", :string) + end + end + end end