Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
@@ -0,0 +1,68 @@
# frozen_string_literal: true
module Sass
class Compiler
# The {Channel} class.
#
# It manages the lifecycle of {Dispatcher}.
class Channel
def initialize(dispatcher_class)
@dispatcher_class = dispatcher_class
@dispatcher = @dispatcher_class.new
@mutex = Mutex.new
end
def close
@mutex.synchronize do
unless @dispatcher.nil?
@dispatcher.close
@dispatcher = nil
end
end
end
def closed?
@mutex.synchronize do
@dispatcher.nil?
end
end
def stream(host)
@mutex.synchronize do
raise IOError, 'closed compiler' if @dispatcher.nil?
Stream.new(@dispatcher, host)
rescue Errno::EBUSY
@dispatcher = @dispatcher_class.new
Stream.new(@dispatcher, host)
end
end
# The {Stream} between {Dispatcher} and {Host}.
class Stream
attr_reader :id
def initialize(dispatcher, host)
@dispatcher = dispatcher
@id = @dispatcher.subscribe(host)
end
def close
@dispatcher.unsubscribe(@id)
end
def error(...)
@dispatcher.error(...)
end
def send_proto(...)
@dispatcher.send_proto(...)
end
end
private_constant :Stream
end
private_constant :Channel
end
end
@@ -0,0 +1,89 @@
# frozen_string_literal: true
require 'open3'
require_relative '../../../ext/sass/cli'
module Sass
class Compiler
# The stdio based {Connection} between the {Dispatcher} and the compiler.
#
# It runs the `sass --embedded` command.
class Connection
def initialize
@mutex = Mutex.new
@stdin, @stdout, @stderr, @wait_thread = Open3.popen3(*CLI::COMMAND, '--embedded', chdir: __dir__)
@stdin.binmode
# # https://dart.dev/tools/dart-devtools
# if 'dart' == File.basename(CLI::COMMAND.first, '.exe') && CLI::COMMAND.include?('--observe')
# Kernel.warn(@stdout.readline, uplevel: 0)
# Kernel.warn(@stdout.readline, uplevel: 0)
# end
@stdout.binmode
@wait_thread.name = "sass-embedded-process-waiter-#{id}"
end
def id
@wait_thread.pid
end
def listen(dispatcher)
Thread.new do
Thread.current.name = "sass-embedded-process-stdout-poller-#{id}"
loop do
length = Varint.read(@stdout)
id = Varint.read(@stdout)
proto = @stdout.read(length - Varint.length(id))
dispatcher.receive_proto(id, proto)
end
rescue IOError, Errno::EBADF, Errno::EPROTO => e
dispatcher.error(e)
@mutex.synchronize do
@stdout.close
end
end
Thread.new do
Thread.current.name = "sass-embedded-process-stderr-poller-#{id}"
loop do
Kernel.warn(@stderr.readline, uplevel: 0)
end
rescue IOError, Errno::EBADF
@mutex.synchronize do
@stderr.close
end
end
end
def close
@mutex.synchronize do
@stdin.close
@wait_thread.join
@stdout.close
@stderr.close
end
end
def closed?
@mutex.synchronize do
@stdin.closed? && !@wait_thread.alive?
end
end
def write(id, proto)
buffer = []
Varint.write(buffer, Varint.length(id) + proto.length)
Varint.write(buffer, id)
@mutex.synchronize do
@stdin.write(buffer.pack('C*'), proto)
end
end
end
private_constant :Connection
end
end
@@ -0,0 +1,116 @@
# frozen_string_literal: true
module Sass
class Compiler
# The {Dispatcher} class.
#
# It dispatches messages between multiple instances of {Host} and a single {Connection} to the compiler.
class Dispatcher
def initialize
@id = 1
@observers = {}.compare_by_identity
@mutex = Mutex.new
@connection = Connection.new
@connection.listen(self)
ForkTracker.add(self)
end
def subscribe(observer)
@mutex.synchronize do
raise Errno::EBUSY if _closed?
id = @id
@id = id.next
@observers[id] = observer
id
end
end
def unsubscribe(id)
@mutex.synchronize do
@observers.delete(id)
return unless @observers.empty?
if _closed?
Thread.new do
close
end
else
_idle
end
end
end
def close
@mutex.synchronize do
_close
end
@connection.close
ForkTracker.delete(self)
end
def closed?
@connection.closed?
end
def error(error)
observers = @mutex.synchronize do
_close
@observers.values
end
if observers.empty?
close
else
observers.each do |observer|
observer.error(error)
end
end
end
def receive_proto(id, proto)
case id
when 1...0xffffffff
@mutex.synchronize { @observers[id] }&.receive_proto(proto)
when 0
outbound_message = EmbeddedProtocol::OutboundMessage.decode(proto)
oneof = outbound_message.message
message = outbound_message.public_send(oneof)
@mutex.synchronize { @observers[message.id] }&.public_send(oneof, message)
when 0xffffffff
outbound_message = EmbeddedProtocol::OutboundMessage.decode(proto)
oneof = outbound_message.message
message = outbound_message.public_send(oneof)
raise Errno::EPROTO, message.message
else
raise Errno::EPROTO
end
end
def send_proto(...)
@connection.write(...)
end
private
def _close
@id = 0xffffffff
end
def _closed?
@id == 0xffffffff
end
def _idle
@id = 1
end
def _idle?
@id == 1
end
end
private_constant :Dispatcher
end
end
@@ -0,0 +1,226 @@
# frozen_string_literal: true
require_relative 'host/function_registry'
require_relative 'host/importer_registry'
require_relative 'host/logger_registry'
require_relative 'host/protofier'
require_relative 'host/structifier'
module Sass
class Compiler
# The {Host} class.
#
# It communicates with {Dispatcher} and handles the host logic.
class Host
def initialize(channel)
@channel = channel
end
def compile_request(path:,
source:,
importer:,
load_paths:,
syntax:,
url:,
charset:,
source_map:,
source_map_include_sources:,
style:,
functions:,
importers:,
alert_ascii:,
alert_color:,
fatal_deprecations:,
future_deprecations:,
logger:,
quiet_deps:,
silence_deprecations:,
verbose:)
alert_color = Exception.to_tty? if alert_color.nil?
@function_registry = FunctionRegistry.new(functions, alert_color:)
@importer_registry = ImporterRegistry.new(importers, load_paths, alert_color:)
@logger_registry = LoggerRegistry.new(logger)
compile_request = EmbeddedProtocol::InboundMessage::CompileRequest.new(
string: unless source.nil?
EmbeddedProtocol::InboundMessage::CompileRequest::StringInput.new(
source: source.to_str,
url: url&.to_s,
syntax: @importer_registry.syntax_to_proto(syntax),
importer: (@importer_registry.register(importer) unless importer.nil?)
)
end,
path: (File.absolute_path(path) unless path.nil?),
style: case style&.to_sym
when :expanded
EmbeddedProtocol::OutputStyle::EXPANDED
when :compressed
EmbeddedProtocol::OutputStyle::COMPRESSED
else
raise ArgumentError, 'style must be one of :expanded, :compressed'
end,
charset:,
source_map:,
source_map_include_sources:,
importers: @importer_registry.importers,
global_functions: @function_registry.global_functions,
alert_ascii:,
alert_color:,
fatal_deprecation: fatal_deprecations.map(&:to_s),
future_deprecation: future_deprecations.map(&:to_s),
quiet_deps:,
silent: logger == Logger.silent,
silence_deprecation: silence_deprecations.map(&:to_s),
verbose:
)
compile_response = await do
send_message(compile_request:)
end
oneof = compile_response.result
result = compile_response.public_send(oneof)
case oneof
when :failure
raise CompileError.new(
result.message,
result.formatted == '' ? nil : result.formatted,
result.stack_trace == '' ? nil : result.stack_trace,
result.span.nil? ? nil : Logger::SourceSpan.new(result.span),
compile_response.loaded_urls.to_a
)
when :success
CompileResult.new(
result.css,
result.source_map == '' ? nil : result.source_map,
compile_response.loaded_urls.to_a
)
else
raise ArgumentError, "Unknown CompileResponse.result #{result}"
end
end
def version_request
version_response = await0 do
send_message0(version_request: EmbeddedProtocol::InboundMessage::VersionRequest.new(
id:
))
end
info = [
version_response.implementation_name,
version_response.implementation_version,
'(Sass Compiler)'
]
case version_response.implementation_name
when 'dart-sass'
info << (CLI::COMMAND.first == 'node' ? '[JavaScript]' : '[Dart]')
end
info
end
def compile_response(message)
@result = message
@queue.close
end
def version_response(message)
@result = message
@queue.close
end
def error(message)
case message
when EmbeddedProtocol::ProtocolError
raise Errno::EPROTO, message.message
else
@error ||= message
@queue.close
end
end
def log_event(message)
@logger_registry.log(message)
end
def canonicalize_request(message)
send_message(canonicalize_response: @importer_registry.canonicalize(message))
end
def import_request(message)
send_message(import_response: @importer_registry.import(message))
end
def file_import_request(message)
send_message(file_import_response: @importer_registry.file_import(message))
end
def function_call_request(message)
send_message(function_call_response: @function_registry.function_call(message))
end
def receive_proto(proto)
@queue.push(proto)
end
private
def await0
listen do
yield
@queue.pop
end
end
def await
listen do
yield
while (proto = @queue.pop)
outbound_message = EmbeddedProtocol::OutboundMessage.decode(proto)
oneof = outbound_message.message
message = outbound_message.public_send(oneof)
public_send(oneof, message)
end
rescue Exception => e # rubocop:disable Lint/RescueException
@stream.error(e)
raise
end
end
def listen
@queue = Queue.new
@stream = @channel.stream(self)
yield
raise @error if @error
@result
ensure
@stream&.close
@queue&.close
end
def id
@stream.id
end
def send_message0(...)
inbound_message = EmbeddedProtocol::InboundMessage.new(...)
@stream.send_proto(0, EmbeddedProtocol::InboundMessage.encode(inbound_message))
end
def send_message(...)
inbound_message = EmbeddedProtocol::InboundMessage.new(...)
@stream.send_proto(id, EmbeddedProtocol::InboundMessage.encode(inbound_message))
end
end
private_constant :Host
end
end
@@ -0,0 +1,87 @@
# frozen_string_literal: true
module Sass
class Compiler
class Host
# The {FunctionRegistry} class.
#
# It stores sass custom functions and handles function calls.
class FunctionRegistry
attr_reader :global_functions
def initialize(functions, alert_color:)
functions = functions.transform_keys(&:to_s)
@global_functions = functions.keys
@functions_by_name = functions.transform_keys do |signature|
index = signature.index('(')
if index
signature.slice(0, index)
else
signature
end
end
@id = 0
@functions_by_id = {}.compare_by_identity
@ids_by_function = {}.compare_by_identity
@highlight = alert_color
end
def register(function)
@ids_by_function.fetch(function) do |fn|
id = @id
@id = id.next
@functions_by_id[id] = fn
@ids_by_function[fn] = id
end
end
def function_call(function_call_request)
oneof = function_call_request.identifier
identifier = function_call_request.public_send(oneof)
function = case oneof
when :name
@functions_by_name[identifier]
when :function_id
@functions_by_id[identifier]
else
raise ArgumentError, "Unknown FunctionCallRequest.identifier #{identifier}"
end
arguments = function_call_request.arguments.map do |argument|
protofier.from_proto(argument)
end
success = protofier.to_proto(function.call(arguments))
accessed_argument_lists = arguments.filter_map do |argument|
if argument.is_a?(Sass::Value::ArgumentList) && argument.instance_eval { @keywords_accessed }
argument.instance_eval { @id }
end
end
EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
id: function_call_request.id,
success:,
accessed_argument_lists:
)
rescue StandardError => e
EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
id: function_call_request.id,
error: e.full_message(highlight: @highlight, order: :top)
)
end
private
def protofier
@protofier ||= Protofier.new(self)
end
end
private_constant :FunctionRegistry
end
end
end
@@ -0,0 +1,141 @@
# frozen_string_literal: true
module Sass
class Compiler
class Host
# The {ImporterRegistry} class.
#
# It stores importers and handles import requests.
class ImporterRegistry
attr_reader :importers
def initialize(importers, load_paths, alert_color:)
@id = 0
@importers_by_id = {}.compare_by_identity
@importers = importers
.map { |importer| register(importer) }
.concat(
load_paths.map do |load_path|
EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
path: File.absolute_path(load_path)
)
end
)
@highlight = alert_color
end
def register(importer)
if importer.is_a?(Sass::NodePackageImporter)
EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
node_package_importer: EmbeddedProtocol::NodePackageImporter.new(
entry_point_directory: importer.instance_eval { @entry_point_directory }
)
)
else
importer = Structifier.to_struct(importer, :canonicalize, :load, :non_canonical_scheme, :find_file_url)
is_importer = importer.respond_to?(:canonicalize) && importer.respond_to?(:load)
is_file_importer = importer.respond_to?(:find_file_url)
raise ArgumentError, 'importer must be an Importer or a FileImporter' if is_importer == is_file_importer
id = @id
@id = id.next
@importers_by_id[id] = importer
if is_importer
EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
importer_id: id,
non_canonical_scheme: if importer.respond_to?(:non_canonical_scheme)
non_canonical_scheme = importer.non_canonical_scheme
if non_canonical_scheme.is_a?(String)
[non_canonical_scheme]
else
non_canonical_scheme || []
end
else
[]
end
)
else
EmbeddedProtocol::InboundMessage::CompileRequest::Importer.new(
file_importer_id: id
)
end
end
end
def canonicalize(canonicalize_request)
importer = @importers_by_id[canonicalize_request.importer_id]
canonicalize_context = CanonicalizeContext.new(canonicalize_request)
url = importer.canonicalize(canonicalize_request.url,
canonicalize_context)&.to_s
EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
id: canonicalize_request.id,
url:,
containing_url_unused: canonicalize_context.instance_eval { @containing_url_unused }
)
rescue StandardError => e
EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
id: canonicalize_request.id,
error: e.full_message(highlight: @highlight, order: :top)
)
end
def import(import_request)
importer = @importers_by_id[import_request.importer_id]
importer_result = Structifier.to_struct importer.load(import_request.url), :contents, :syntax, :source_map_url
EmbeddedProtocol::InboundMessage::ImportResponse.new(
id: import_request.id,
success: EmbeddedProtocol::InboundMessage::ImportResponse::ImportSuccess.new(
contents: importer_result.contents.to_str,
syntax: syntax_to_proto(importer_result.syntax),
source_map_url: (importer_result.source_map_url&.to_s if importer_result.respond_to?(:source_map_url))
)
)
rescue StandardError => e
EmbeddedProtocol::InboundMessage::ImportResponse.new(
id: import_request.id,
error: e.full_message(highlight: @highlight, order: :top)
)
end
def file_import(file_import_request)
importer = @importers_by_id[file_import_request.importer_id]
canonicalize_context = CanonicalizeContext.new(file_import_request)
file_url = importer.find_file_url(file_import_request.url,
canonicalize_context)&.to_s
EmbeddedProtocol::InboundMessage::FileImportResponse.new(
id: file_import_request.id,
file_url:,
containing_url_unused: canonicalize_context.instance_eval { @containing_url_unused }
)
rescue StandardError => e
EmbeddedProtocol::InboundMessage::FileImportResponse.new(
id: file_import_request.id,
error: e.full_message(highlight: @highlight, order: :top)
)
end
def syntax_to_proto(syntax)
case syntax&.to_sym
when :scss
EmbeddedProtocol::Syntax::SCSS
when :indented
EmbeddedProtocol::Syntax::INDENTED
when :css
EmbeddedProtocol::Syntax::CSS
else
raise ArgumentError, 'syntax must be one of :scss, :indented, :css'
end
end
end
private_constant :ImporterRegistry
end
end
end
@@ -0,0 +1,80 @@
# frozen_string_literal: true
module Sass
class Compiler
class Host
# The {LoggerRegistry} class.
#
# It stores logger and handles log events.
class LoggerRegistry
def initialize(logger)
logger = Structifier.to_struct(logger, :debug, :warn)
{ debug: DebugContext, warn: WarnContext }.each do |symbol, context_class|
next unless logger.respond_to?(symbol)
define_singleton_method(symbol) do |event|
logger.public_send(symbol, event.message, context_class.new(event))
end
end
end
def log(event)
case event.type
when :DEBUG
debug(event)
when :DEPRECATION_WARNING, :WARNING
warn(event)
else
raise ArgumentError, "Unknown LogEvent.type #{event.type}"
end
end
private
def debug(event)
Kernel.warn(event.formatted)
end
def warn(event)
Kernel.warn(event.formatted)
end
# Contextual information passed to `debug`.
class DebugContext
# @return [Logger::SourceSpan, nil]
attr_reader :span
def initialize(event)
@span = event.span.nil? ? nil : Logger::SourceSpan.new(event.span)
end
end
private_constant :DebugContext
# Contextual information passed to `warn`.
class WarnContext < DebugContext
# @return [Boolean]
attr_reader :deprecation
# @return [String, nil]
attr_reader :deprecation_type
# @return [String]
attr_reader :stack
def initialize(event)
super
@deprecation = event.type == :DEPRECATION_WARNING
@deprecation_type = (event.deprecation_type if @deprecation)
@stack = event.stack_trace
end
end
private_constant :WarnContext
end
private_constant :LoggerRegistry
end
end
end
@@ -0,0 +1,360 @@
# frozen_string_literal: true
module Sass
class Compiler
class Host
# The {Protofier} class.
#
# It converts Pure Ruby types and Protobuf Ruby types.
class Protofier
def initialize(function_registry)
@function_registry = function_registry
end
def to_proto(obj)
case obj
when Sass::Value::String
EmbeddedProtocol::Value.new(
string: EmbeddedProtocol::Value::String.new(
text: obj.text.to_str,
quoted: obj.quoted?
)
)
when Sass::Value::Number
EmbeddedProtocol::Value.new(
number: Number.to_proto(obj)
)
when Sass::Value::Color
EmbeddedProtocol::Value.new(
color: EmbeddedProtocol::Value::Color.new(
channel1: obj.send(:channel0_or_nil),
channel2: obj.send(:channel1_or_nil),
channel3: obj.send(:channel2_or_nil),
alpha: obj.send(:alpha_or_nil),
space: obj.space
)
)
when Sass::Value::ArgumentList
EmbeddedProtocol::Value.new(
argument_list: EmbeddedProtocol::Value::ArgumentList.new(
id: obj.instance_eval { @id },
contents: obj.to_a.map { |element| to_proto(element) },
keywords: obj.keywords.each_with_object({}) { |(key, value), hash| hash[key.to_s] = to_proto(value) },
separator: ListSeparator.to_proto(obj.separator)
)
)
when Sass::Value::List
EmbeddedProtocol::Value.new(
list: EmbeddedProtocol::Value::List.new(
contents: obj.to_a.map { |element| to_proto(element) },
separator: ListSeparator.to_proto(obj.separator),
has_brackets: obj.bracketed?
)
)
when Sass::Value::Map
EmbeddedProtocol::Value.new(
map: EmbeddedProtocol::Value::Map.new(
entries: obj.contents.map do |key, value|
EmbeddedProtocol::Value::Map::Entry.new(
key: to_proto(key),
value: to_proto(value)
)
end
)
)
when Sass::Value::Function
if obj.instance_eval { @id }
EmbeddedProtocol::Value.new(
compiler_function: EmbeddedProtocol::Value::CompilerFunction.new(
id: obj.instance_eval { @id }
)
)
else
EmbeddedProtocol::Value.new(
host_function: EmbeddedProtocol::Value::HostFunction.new(
id: @function_registry.register(obj.callback),
signature: obj.signature
)
)
end
when Sass::Value::Mixin
EmbeddedProtocol::Value.new(
compiler_mixin: EmbeddedProtocol::Value::CompilerMixin.new(
id: obj.instance_eval { @id }
)
)
when Sass::Value::Calculation
EmbeddedProtocol::Value.new(
calculation: Calculation.to_proto(obj)
)
when Sass::Value::Boolean
EmbeddedProtocol::Value.new(
singleton: obj.value ? :TRUE : :FALSE
)
when Sass::Value::Null
EmbeddedProtocol::Value.new(
singleton: :NULL
)
else
raise Sass::ScriptError, "Unknown Sass::Value #{obj}"
end
end
def from_proto(proto)
oneof = proto.value
obj = proto.public_send(oneof)
case oneof
when :string
Sass::Value::String.new(
obj.text,
quoted: obj.quoted
)
when :number
Number.from_proto(obj)
when :color
obj.to_s if RUBY_ENGINE == 'jruby' # TODO: https://github.com/protocolbuffers/protobuf/issues/18807
Sass::Value::Color.send(
:for_space,
obj.space,
obj.has_channel1? ? obj.channel1 : nil,
obj.has_channel2? ? obj.channel2 : nil,
obj.has_channel3? ? obj.channel3 : nil,
obj.has_alpha? ? obj.alpha : nil
)
when :argument_list
Sass::Value::ArgumentList.new(
obj.contents.map do |element|
from_proto(element)
end,
obj.keywords.to_enum.with_object({}) do |(key, value), hash|
hash[key.to_sym] = from_proto(value)
end,
ListSeparator.from_proto(obj.separator)
).instance_eval do
@id = obj.id
self
end
when :list
Sass::Value::List.new(
obj.contents.map do |element|
from_proto(element)
end,
separator: ListSeparator.from_proto(obj.separator),
bracketed: obj.has_brackets
)
when :map
Sass::Value::Map.new(
obj.entries.to_enum.with_object({}) do |entry, hash|
hash[from_proto(entry.key)] = from_proto(entry.value)
end
)
when :compiler_function
Sass::Value::Function.new(nil).instance_eval do
@id = obj.id
self
end
when :host_function
raise Sass::ScriptError, 'The compiler may not send Value.host_function to host'
when :compiler_mixin
Sass::Value::Mixin.send(:new).instance_eval do
@id = obj.id
self
end
when :calculation
Calculation.from_proto(obj)
when :singleton
case obj
when :TRUE
Sass::Value::Boolean::TRUE
when :FALSE
Sass::Value::Boolean::FALSE
when :NULL
Sass::Value::Null::NULL
else
raise Sass::ScriptError, "Unknown Value.singleton #{obj}"
end
else
raise Sass::ScriptError, "Unknown Value.value #{obj}"
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)
Sass::Value::Calculation.send(
:new,
obj.name,
obj.arguments.map { |argument| CalculationValue.from_proto(argument) }
)
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)
)
)
else
raise Sass::ScriptError, "Unknown CalculationValue #{value}"
end
end
def from_proto(value)
oneof = value.value
obj = value.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)
)
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
def to_proto(separator)
case separator
when ','
:COMMA
when ' '
:SPACE
when '/'
:SLASH
when nil
:UNDECIDED
else
raise Sass::ScriptError, "Unknown ListSeparator #{separator}"
end
end
def from_proto(separator)
case separator
when :COMMA
','
when :SPACE
' '
when :SLASH
'/'
when :UNDECIDED
nil
else
raise Sass::ScriptError, "Unknown ListSeparator #{separator}"
end
end
end
private_constant :ListSeparator
end
private_constant :Protofier
end
end
end
@@ -0,0 +1,37 @@
# frozen_string_literal: true
module Sass
class Compiler
class Host
# The {Structifier} module.
#
# It converts {::Hash} to {Struct}-like objects.
module Structifier
module_function
def to_struct(obj, *symbols)
return obj unless obj.is_a?(Hash)
struct = Object.new
symbols.each do |key|
next unless obj.key?(key)
value = obj[key]
if value.respond_to?(:call)
struct.define_singleton_method key do |*args, **kwargs|
value.call(*args, **kwargs)
end
else
struct.define_singleton_method key do
value
end
end
end
struct
end
end
private_constant :Structifier
end
end
end
@@ -0,0 +1,39 @@
# frozen_string_literal: true
module Sass
class Compiler
# The {Varint} module.
#
# It reads and writes varints.
module Varint
module_function
def length(value)
return 1 if value < 128
(value.bit_length + 6) / 7
end
def read(readable)
value = bits = 0
loop do
byte = readable.readbyte
value |= (byte & 0x7f) << bits
bits += 7
break if byte < 0x80
end
value
end
def write(writeable, value)
until value < 0x80
writeable << ((value & 0x7f) | 0x80)
value >>= 7
end
writeable << value
end
end
private_constant :Varint
end
end