Skip to content

Commit

Permalink
Add support for SassCalculation
Browse files Browse the repository at this point in the history
  • Loading branch information
ntkme committed Jul 20, 2023
1 parent e0d2757 commit 2a9e51e
Show file tree
Hide file tree
Showing 31 changed files with 846 additions and 55 deletions.
17 changes: 17 additions & 0 deletions lib/sass/calculation_value.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module Sass
# The type of values that can be arguments to a SassCalculation.
#
# @see https://sass-lang.com/documentation/js-api/types/calculationvalue/
module CalculationValue
# @return [CalculationValue]
# @raise [ScriptError]
def assert_calculation_value(_name = nil)
self
end
end
end

require_relative 'calculation_value/calculation_interpolation'
require_relative 'calculation_value/calculation_operation'
27 changes: 27 additions & 0 deletions lib/sass/calculation_value/calculation_interpolation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module Sass
module CalculationValue
# A string injected into a SassCalculation using interpolation.
#
# @see https://sass-lang.com/documentation/js-api/classes/calculationinterpolation/
class CalculationInterpolation
include CalculationValue

def initialize(value)
@value = value
end

attr_reader :value

def ==(other)
other.is_a?(Sass::CalculationValue::CalculationInterpolation) &&
other.value == value
end

def hash
@hash ||= value.hash
end
end
end
end
40 changes: 40 additions & 0 deletions lib/sass/calculation_value/calculation_operation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true

module Sass
module CalculationValue
# A binary operation that can appear in a SassCalculation.
#
# @see https://sass-lang.com/documentation/js-api/classes/calculationoperation/
class CalculationOperation
include CalculationValue

OPERATORS = ['+', '-', '*', '/'].freeze

private_constant :OPERATORS

def initialize(operator, left, right)
raise Sass::ScriptError, "Invalid operator: #{operator}" unless OPERATORS.include?(operator)

left.assert_calculation_value
right.assert_calculation_value

@operator = operator.freeze
@left = left.freeze
@right = right.freeze
end

attr_reader :operator, :left, :right

def ==(other)
other.is_a?(Sass::CalculationValue::CalculationOperation) &&
other.operator == operator &&
other.left == left &&
other.right == right
end

def hash
@hash ||= [operator, left, right].hash
end
end
end
end
2 changes: 1 addition & 1 deletion lib/sass/compile_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module Sass
# The result of compiling Sass to CSS. Returned by {Sass.compile} and {Sass.compile_string}.
#
# @see https://sass-lang.com/documentation/js-api/interfaces/CompileResult
# @see https://sass-lang.com/documentation/js-api/interfaces/compileresult/
class CompileResult
# @return [String]
attr_reader :css
Expand Down
14 changes: 7 additions & 7 deletions lib/sass/embedded.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def initialize
# Compiles the Sass file at +path+ to CSS.
# @param path [String]
# @param load_paths [Array<String>] Paths in which to look for stylesheets loaded by rules like
# {@use}[https://sass-lang.com/documentation/at-rules/use] and {@import}[https://sass-lang.com/documentation/at-rules/import].
# {@use}[https://sass-lang.com/documentation/at-rules/use/] and {@import}[https://sass-lang.com/documentation/at-rules/import/].
# @param charset [Boolean] By default, if the CSS document contains non-ASCII characters, Sass adds a +@charset+
# declaration (in expanded output mode) or a byte-order mark (in compressed mode) to indicate its encoding to
# browsers or other consumers. If +charset+ is +false+, these annotations are omitted.
Expand All @@ -104,7 +104,7 @@ def initialize
# @param style [String, Symbol] The OutputStyle of the compiled CSS.
# @param functions [Hash<String, Proc>] Additional built-in Sass functions that are available in all stylesheets.
# @param importers [Array<Object>] Custom importers that control how Sass resolves loads from rules like
# {@use}[https://sass-lang.com/documentation/at-rules/use] and {@import}[https://sass-lang.com/documentation/at-rules/import].
# {@use}[https://sass-lang.com/documentation/at-rules/use/] and {@import}[https://sass-lang.com/documentation/at-rules/import/].
# @param alert_ascii [Boolean] If this is +true+, the compiler will exclusively use ASCII characters in its error
# and warning messages. Otherwise, it may use non-ASCII Unicode characters as well.
# @param alert_color [Boolean] If this is +true+, the compiler will use ANSI color escape codes in its error and
Expand All @@ -119,7 +119,7 @@ def initialize
# deprecation warning it encounters.
# @return [CompileResult]
# @raise [CompileError]
# @see https://sass-lang.com/documentation/js-api/modules#compile
# @see https://sass-lang.com/documentation/js-api/functions/compile/
def compile(path,
load_paths: [],

Expand Down Expand Up @@ -163,7 +163,7 @@ def compile(path,
# @param source [String]
# @param importer [Object] The importer to use to handle loads that are relative to the entrypoint stylesheet.
# @param load_paths [Array<String>] Paths in which to look for stylesheets loaded by rules like
# {@use}[https://sass-lang.com/documentation/at-rules/use] and {@import}[https://sass-lang.com/documentation/at-rules/import].
# {@use}[https://sass-lang.com/documentation/at-rules/use/] and {@import}[https://sass-lang.com/documentation/at-rules/import/].
# @param syntax [String, Symbol] The Syntax to use to parse the entrypoint stylesheet.
# @param url [String] The canonical URL of the entrypoint stylesheet. If this is passed along with +importer+, it's
# used to resolve relative loads in the entrypoint stylesheet.
Expand All @@ -175,7 +175,7 @@ def compile(path,
# @param style [String, Symbol] The OutputStyle of the compiled CSS.
# @param functions [Hash<String, Proc>] Additional built-in Sass functions that are available in all stylesheets.
# @param importers [Array<Object>] Custom importers that control how Sass resolves loads from rules like
# {@use}[https://sass-lang.com/documentation/at-rules/use] and {@import}[https://sass-lang.com/documentation/at-rules/import].
# {@use}[https://sass-lang.com/documentation/at-rules/use/] and {@import}[https://sass-lang.com/documentation/at-rules/import/].
# @param alert_ascii [Boolean] If this is +true+, the compiler will exclusively use ASCII characters in its error
# and warning messages. Otherwise, it may use non-ASCII Unicode characters as well.
# @param alert_color [Boolean] If this is +true+, the compiler will use ANSI color escape codes in its error and
Expand All @@ -190,7 +190,7 @@ def compile(path,
# deprecation warning it encounters.
# @return [CompileResult]
# @raise [CompileError]
# @see https://sass-lang.com/documentation/js-api/modules#compileString
# @see https://sass-lang.com/documentation/js-api/functions/compilestring/
def compile_string(source,
importer: nil,
load_paths: [],
Expand Down Expand Up @@ -234,7 +234,7 @@ def compile_string(source,
end

# @return [String] Information about the Sass implementation.
# @see https://sass-lang.com/documentation/js-api/modules#info
# @see https://sass-lang.com/documentation/js-api/variables/info/
def info
@info ||= Host.new(@dispatcher).version_request
end
Expand Down
8 changes: 7 additions & 1 deletion lib/sass/embedded/host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,13 @@ def version_response(message)
end

def error(message)
@error = message
if message.is_a?(EmbeddedProtocol::ProtocolError)
return if message.id != id

@error = Errno::EPROTO.new(message.message)
else
@error = message
end
@queue.close
end

Expand Down
191 changes: 180 additions & 11 deletions lib/sass/embedded/host/value_protofier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@ def to_proto(obj)
)
when Sass::Value::Number
EmbeddedProtocol::Value.new(
number: EmbeddedProtocol::Value::Number.new(
value: obj.value.to_f,
numerators: obj.numerator_units,
denominators: obj.denominator_units
)
number: Number.to_proto(obj)
)
when Sass::Value::Color
if obj.instance_eval { !defined?(@hue) }
Expand Down Expand Up @@ -100,6 +96,10 @@ def to_proto(obj)
)
)
end
when Sass::Value::Calculation
EmbeddedProtocol::Value.new(
calculation: Calculation.to_proto(obj)
)
when Sass::Value::Boolean
EmbeddedProtocol::Value.new(
singleton: obj.value ? :TRUE : :FALSE
Expand All @@ -123,12 +123,7 @@ def from_proto(proto)
quoted: obj.quoted
)
when :number
Sass::Value::Number.new(
obj.value, {
numerator_units: obj.numerators.to_a,
denominator_units: obj.denominators.to_a
}
)
Number.from_proto(obj)
when :rgb_color
Sass::Value::Color.new(
red: obj.red,
Expand Down Expand Up @@ -181,6 +176,8 @@ def from_proto(proto)
Sass::Value::Function.new(obj.id)
when :host_function
raise Sass::ScriptError, 'The compiler may not send Value.host_function to host'
when :calculation
Calculation.from_proto(obj)
when :singleton
case obj
when :TRUE
Expand All @@ -197,6 +194,178 @@ def from_proto(proto)
end
end

# The {Number} Protofier.
module Number
module_function

def to_proto(obj)
EmbeddedProtocol::Value::Number.new(
value: obj.value.to_f,
numerators: obj.numerator_units,
denominators: obj.denominator_units
)
end

def from_proto(obj)
Sass::Value::Number.new(
obj.value, {
numerator_units: obj.numerators.to_a,
denominator_units: obj.denominators.to_a
}
)
end
end

private_constant :Number

# The {Calculation} Protofier.
module Calculation
module_function

def to_proto(obj)
EmbeddedProtocol::Value::Calculation.new(
name: obj.name,
arguments: obj.arguments.map { |argument| CalculationValue.to_proto(argument) }
)
end

def from_proto(obj)
case obj.name
when 'calc'
if obj.arguments.length != 1
raise Sass::ScriptError,
'Value.Calculation.arguments must have exactly one argument for calc().'
end

Sass::Value::Calculation.calc(*obj.arguments.map { |argument| CalculationValue.from_proto(argument) })
when 'clamp'
if obj.arguments.length != 3
raise Sass::ScriptError,
'Value.Calculation.arguments must have exactly 3 arguments for clamp().'
end

Sass::Value::Calculation.clamp(*obj.arguments.map { |argument| CalculationValue.from_proto(argument) })
when 'min'
if obj.arguments.empty?
raise Sass::ScriptError,
'Value.Calculation.arguments must have at least 1 argument for min().'
end

Sass::Value::Calculation.min(obj.arguments.map { |argument| CalculationValue.from_proto(argument) })
when 'max'
if obj.arguments.empty?
raise Sass::ScriptError,
'Value.Calculation.arguments must have at least 1 argument for max().'
end

Sass::Value::Calculation.max(obj.arguments.map { |argument| CalculationValue.from_proto(argument) })
else
raise Sass::ScriptError,
"Value.Calculation.name #{calculation.name.inspect} is not a recognized calculation type."
end
end
end

private_constant :Calculation

# The {CalculationValue} Protofier.
module CalculationValue
module_function

def to_proto(value)
case value
when Sass::Value::Number
EmbeddedProtocol::Value::Calculation::CalculationValue.new(
number: Number.to_proto(value)
)
when Sass::Value::Calculation
EmbeddedProtocol::Value::Calculation::CalculationValue.new(
calculation: Calculation.to_proto(value)
)
when Sass::Value::String
EmbeddedProtocol::Value::Calculation::CalculationValue.new(
string: value.text
)
when Sass::CalculationValue::CalculationOperation
EmbeddedProtocol::Value::Calculation::CalculationValue.new(
operation: EmbeddedProtocol::Value::Calculation::CalculationOperation.new(
operator: CalculationOperator.to_proto(value.operator),
left: to_proto(value.left),
right: to_proto(value.right)
)
)
when Sass::CalculationValue::CalculationInterpolation
EmbeddedProtocol::Value::Calculation::CalculationValue.new(
interpolation: value.value
)
else
raise Sass::ScriptError, "Unknown CalculationValue #{value}"
end
end

def from_proto(value)
oneof = value.value
obj = proto.public_send(oneof)
case oneof
when :number
Number.from_proto(obj)
when :calculation
Calculation.from_proto(obj)
when :string
Sass::Value::String.new(obj, quoted: false)
when :operation
Sass::CalculationValue::CalculationOperation.new(
CalculationOperator.from_proto(obj.operator),
from_proto(obj.left),
from_proto(obj.right)
)
when :interpolation
Sass::CalculationValue::CalculationInterpolation.new(obj)
else
raise Sass::ScriptError, "Unknown CalculationValue #{value}"
end
end
end

private_constant :CalculationValue

# The {CalculationOperator} Protofier.
module CalculationOperator
module_function

def to_proto(operator)
case operator
when '+'
:PLUS
when '-'
:MINUS
when '*'
:TIMES
when '/'
:DIVIDE
else
raise Sass::ScriptError, "Unknown CalculationOperator #{separator}"
end
end

def from_proto(operator)
case operator
when :PLUS
'+'
when :MINUS
'-'
when :TIMES
'*'
when :DIVIDE
'/'
else
raise Sass::ScriptError, "Unknown CalculationOperator #{separator}"
end
end
end

private_constant :CalculationOperator

# The {ListSeparator} Protofier.
module ListSeparator
module_function
Expand Down
Loading

0 comments on commit 2a9e51e

Please sign in to comment.