sass-embedded 1.99.0 → 1.101.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The {Platform} module.
4
+ #
5
+ # @!visibility private
6
+ module Platform
7
+ HOST_CPU = RbConfig::CONFIG['host_cpu'].downcase
8
+
9
+ CPU = case HOST_CPU
10
+ when /amd64|x86_64|x64/
11
+ 'x86_64'
12
+ when /i\d86|x86|i86pc/
13
+ 'i386'
14
+ when /arm64|aarch64/
15
+ 'aarch64'
16
+ when /arm/
17
+ 'arm'
18
+ when /ppc64le|powerpc64le/
19
+ 'ppc64le'
20
+ else
21
+ HOST_CPU
22
+ end
23
+
24
+ HOST_OS = RbConfig::CONFIG['host_os'].downcase
25
+
26
+ OS = case HOST_OS
27
+ when /darwin/
28
+ 'darwin'
29
+ when /linux-android/
30
+ 'linux-android'
31
+ when /linux-musl/
32
+ 'linux-musl'
33
+ when /linux-none/
34
+ 'linux-none'
35
+ when /linux-uclibc/
36
+ 'linux-uclibc'
37
+ when /linux/
38
+ 'linux'
39
+ when *Gem::WIN_PATTERNS
40
+ 'windows'
41
+ else
42
+ HOST_OS
43
+ end
44
+
45
+ ARCH = "#{CPU}-#{OS}".freeze
46
+ end
Binary file
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'platform'
4
+
5
+ # The {SassConfig} module.
6
+ #
7
+ # @!visibility private
8
+ module SassConfig
9
+ module_function
10
+
11
+ def package_json(path = '.')
12
+ require 'json'
13
+
14
+ JSON.parse(File.read(File.absolute_path('package.json', path)))
15
+ end
16
+
17
+ def dart_support?
18
+ %w[darwin linux linux-android linux-musl windows].include?(Platform::OS) &&
19
+ %w[x86_64 aarch64 arm riscv64].include?(Platform::CPU)
20
+ end
21
+
22
+ def dart_sass_version
23
+ package_json(__dir__)['dependencies']['sass']
24
+ # TODO: remove after https://github.com/sass/dart-sass/pull/2413
25
+ .delete_prefix('file:sass-').delete_suffix('.tgz')
26
+ end
27
+
28
+ def dart_sass
29
+ repo = 'https://github.com/sass/dart-sass'
30
+
31
+ tag_name = dart_sass_version
32
+
33
+ message = "dart-sass for #{Platform::ARCH} not available at #{repo}/releases/tag/#{tag_name}"
34
+
35
+ env = ''
36
+
37
+ os = case Platform::OS
38
+ when 'darwin'
39
+ 'macos'
40
+ when 'linux'
41
+ 'linux'
42
+ when 'linux-android'
43
+ 'android'
44
+ when 'linux-musl'
45
+ env = '-musl'
46
+ 'linux'
47
+ when 'windows'
48
+ 'windows'
49
+ else
50
+ raise NotImplementedError, message
51
+ end
52
+
53
+ cpu = case Platform::CPU
54
+ when 'x86_64'
55
+ 'x64'
56
+ when 'aarch64'
57
+ 'arm64'
58
+ when 'arm'
59
+ 'arm'
60
+ when 'riscv64'
61
+ 'riscv64'
62
+ else
63
+ raise NotImplementedError, message
64
+ end
65
+
66
+ ext = Platform::OS == 'windows' ? 'zip' : 'tar.gz'
67
+
68
+ "#{repo}/releases/download/#{tag_name}/dart-sass-#{tag_name}-#{os}-#{cpu}#{env}.#{ext}"
69
+ end
70
+
71
+ def protoc
72
+ repo = 'https://repo.maven.apache.org/maven2/com/google/protobuf/protoc'
73
+
74
+ dependency = Gem::Dependency.new('google-protobuf')
75
+
76
+ spec = dependency.to_spec
77
+
78
+ version = spec.version
79
+
80
+ message = "protoc for #{Platform::ARCH} not available at #{repo}/#{version}"
81
+
82
+ os = case Platform::OS
83
+ when 'darwin'
84
+ 'osx'
85
+ when 'linux', 'linux-android', 'linux-musl', 'linux-none', 'linux-uclibc'
86
+ 'linux'
87
+ when 'windows'
88
+ 'windows'
89
+ else
90
+ raise NotImplementedError, message
91
+ end
92
+
93
+ cpu = case Platform::CPU
94
+ when 'i386'
95
+ 'x86_32'
96
+ when 'x86_64'
97
+ 'x86_64'
98
+ when 'aarch64'
99
+ Platform::OS == 'windows' ? 'x86_64' : 'aarch_64'
100
+ when 'ppc64le'
101
+ 'ppcle_64'
102
+ when 's390x'
103
+ 's390_64'
104
+ else
105
+ raise NotImplementedError, message
106
+ end
107
+
108
+ uri = "#{repo}/#{version}/protoc-#{version}-#{os}-#{cpu}.exe"
109
+
110
+ Utils.fetch_https("#{uri}.sha1")
111
+
112
+ uri
113
+ rescue Gem::RemoteFetcher::FetchError
114
+ dependency_request = Gem::Resolver::DependencyRequest.new(dependency, nil)
115
+
116
+ versions = Gem::Resolver::BestSet.new.find_all(dependency_request).filter_map do |s|
117
+ s.version if s.platform == Gem::Platform::RUBY
118
+ end
119
+
120
+ versions.sort.reverse_each do |v|
121
+ uri = "#{repo}/#{v}/protoc-#{v}-#{os}-#{cpu}.exe"
122
+
123
+ Utils.fetch_https("#{uri}.sha1")
124
+
125
+ return uri
126
+ rescue Gem::RemoteFetcher::FetchError
127
+ next
128
+ end
129
+
130
+ raise NotImplementedError, message
131
+ end
132
+
133
+ def embedded_sass_protocol
134
+ require 'json'
135
+
136
+ rubyarchdir = ENV.fetch('RUBYARCHDIR', nil)
137
+
138
+ rubylibdir = ENV.fetch('RUBYLIBDIR', nil)
139
+
140
+ version = Utils.capture(RbConfig.ruby,
141
+ "-I#{File.absolute_path('../../lib', __dir__)}",
142
+ *("-I#{rubyarchdir}" if rubyarchdir),
143
+ *("-I#{rubylibdir}" if rubylibdir),
144
+ File.absolute_path('../../exe/sass', __dir__),
145
+ '--embedded',
146
+ '--version')
147
+
148
+ tag_name = JSON.parse(version)['protocolVersion']
149
+
150
+ "https://github.com/sass/sass/raw/embedded-protocol-#{tag_name}/spec/embedded_sass.proto"
151
+ end
152
+
153
+ def development?
154
+ File.exist?('../../Gemfile')
155
+ end
156
+
157
+ def gem_version
158
+ require_relative '../../lib/sass/embedded/version'
159
+
160
+ development? ? dart_sass_version : Sass::Embedded::VERSION
161
+ end
162
+
163
+ def gem_platform
164
+ platform = Gem::Platform.new("#{Platform::CPU}-#{Platform::HOST_OS}")
165
+ case Platform::OS
166
+ when 'darwin'
167
+ case platform.cpu
168
+ when 'aarch64'
169
+ Gem::Platform.new(['arm64', platform.os])
170
+ else
171
+ platform
172
+ end
173
+ when 'linux'
174
+ if platform.version&.start_with?('gnu')
175
+ platform
176
+ else
177
+ Gem::Platform.new([platform.cpu, platform.os, "gnu#{platform.version}"])
178
+ end
179
+ when 'windows'
180
+ case platform.cpu
181
+ when 'x86_64'
182
+ Gem::Platform.new('x64-mingw-ucrt')
183
+ else
184
+ Gem::Platform.new([platform.cpu, 'mingw', 'ucrt'])
185
+ end
186
+ else
187
+ platform
188
+ end
189
+ end
190
+ end
data/ext/sass/utils.rb ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The {Utils} module.
4
+ #
5
+ # @!visibility private
6
+ module Utils
7
+ module_function
8
+
9
+ def capture(...)
10
+ require 'open3'
11
+
12
+ stdout, stderr, status = Open3.capture3(...)
13
+
14
+ raise stderr unless status.success?
15
+
16
+ stdout
17
+ end
18
+
19
+ def fetch_https(source_uri)
20
+ require 'rubygems/remote_fetcher'
21
+
22
+ source_uri = begin
23
+ Gem::Uri.parse!(source_uri)
24
+ rescue NoMethodError
25
+ URI.parse(source_uri)
26
+ end
27
+
28
+ Gem::RemoteFetcher.fetcher.fetch_https(source_uri)
29
+ end
30
+
31
+ def windows_system_directory
32
+ path = capture('powershell.exe',
33
+ '-NoLogo',
34
+ '-NoProfile',
35
+ '-NonInteractive',
36
+ '-Command',
37
+ '[Environment]::GetFolderPath([Environment+SpecialFolder]::System) | Write-Host -NoNewline')
38
+
39
+ File.absolute_path(path)
40
+ end
41
+ end
@@ -29,24 +29,24 @@ module Sass
29
29
  end
30
30
  end
31
31
 
32
- def stream(host)
32
+ def stream(session)
33
33
  @mutex.synchronize do
34
34
  raise IOError, 'closed compiler' if @dispatcher.nil?
35
35
 
36
- Stream.new(@dispatcher, host)
36
+ Stream.new(@dispatcher, session)
37
37
  rescue Errno::EBUSY
38
38
  @dispatcher = Dispatcher.new(*@args, **@kwargs, &@block)
39
- Stream.new(@dispatcher, host)
39
+ Stream.new(@dispatcher, session)
40
40
  end
41
41
  end
42
42
 
43
- # The {Stream} between {Dispatcher} and {Host}.
43
+ # The {Stream} between {Dispatcher} and {Session}.
44
44
  class Stream
45
45
  attr_reader :id
46
46
 
47
- def initialize(dispatcher, host)
47
+ def initialize(dispatcher, session)
48
48
  @dispatcher = dispatcher
49
- @id = @dispatcher.subscribe(host)
49
+ @id = @dispatcher.subscribe(session)
50
50
  end
51
51
 
52
52
  def close
@@ -4,7 +4,7 @@ module Sass
4
4
  class Compiler
5
5
  # The {Dispatcher} class.
6
6
  #
7
- # It dispatches messages between multiple instances of {Host} and a single {Connection} to the compiler.
7
+ # It dispatches messages between multiple instances of {Session} and a single {Connection} to the compiler.
8
8
  class Dispatcher
9
9
  def initialize(idle_timeout: 0)
10
10
  @id = 1
@@ -2,14 +2,14 @@
2
2
 
3
3
  module Sass
4
4
  class Compiler
5
- class Host
5
+ class Session
6
6
  # The {FunctionRegistry} class.
7
7
  #
8
8
  # It stores sass custom functions and handles function calls.
9
9
  class FunctionRegistry
10
10
  attr_reader :compile_context, :global_functions
11
11
 
12
- def initialize(functions, alert_color:)
12
+ def initialize(functions, session:)
13
13
  @compile_context = Object.new
14
14
  @global_functions = functions.keys.map!(&:to_s)
15
15
  @functions_by_name = functions.transform_keys do |signature|
@@ -26,7 +26,7 @@ module Sass
26
26
  @functions_by_id = {}.compare_by_identity
27
27
  @ids_by_function = {}.compare_by_identity
28
28
 
29
- @highlight = alert_color
29
+ @session = session
30
30
  end
31
31
 
32
32
  def register(function)
@@ -68,9 +68,14 @@ module Sass
68
68
  accessed_argument_lists:
69
69
  )
70
70
  rescue StandardError => e
71
+ @session.backtrace = e.backtrace
71
72
  EmbeddedProtocol::InboundMessage::FunctionCallResponse.new(
72
73
  id: function_call_request.id,
73
- error: e.full_message(highlight: @highlight, order: :top)
74
+ error: if e.respond_to?(:detailed_message)
75
+ e.detailed_message(highlight: false)
76
+ else # TODO: remove once ruby 3.1 support is dropped
77
+ "#{e.message} (#{e.class.name})"
78
+ end
74
79
  )
75
80
  end
76
81
 
@@ -2,14 +2,14 @@
2
2
 
3
3
  module Sass
4
4
  class Compiler
5
- class Host
5
+ class Session
6
6
  # The {ImporterRegistry} class.
7
7
  #
8
8
  # It stores importers and handles import requests.
9
9
  class ImporterRegistry
10
10
  attr_reader :importers
11
11
 
12
- def initialize(importers, load_paths, alert_color:)
12
+ def initialize(importers, load_paths, session:)
13
13
  @id = 0
14
14
  @importers_by_id = {}.compare_by_identity
15
15
  @importers = importers
@@ -22,7 +22,7 @@ module Sass
22
22
  end
23
23
  )
24
24
 
25
- @highlight = alert_color
25
+ @session = session
26
26
  end
27
27
 
28
28
  IMPORTER_ATTRS = %i[non_canonical_scheme].freeze
@@ -79,9 +79,14 @@ module Sass
79
79
  containing_url_unused: canonicalize_context.instance_variable_get(:@containing_url_unused)
80
80
  )
81
81
  rescue StandardError => e
82
+ @session.backtrace = e.backtrace
82
83
  EmbeddedProtocol::InboundMessage::CanonicalizeResponse.new(
83
84
  id: canonicalize_request.id,
84
- error: e.full_message(highlight: @highlight, order: :top)
85
+ error: if e.respond_to?(:detailed_message)
86
+ e.detailed_message(highlight: false)
87
+ else # TODO: remove once ruby 3.1 support is dropped
88
+ "#{e.message} (#{e.class.name})"
89
+ end
85
90
  )
86
91
  end
87
92
 
@@ -103,9 +108,14 @@ module Sass
103
108
  )
104
109
  )
105
110
  rescue StandardError => e
111
+ @session.backtrace = e.backtrace
106
112
  EmbeddedProtocol::InboundMessage::ImportResponse.new(
107
113
  id: import_request.id,
108
- error: e.full_message(highlight: @highlight, order: :top)
114
+ error: if e.respond_to?(:detailed_message)
115
+ e.detailed_message(highlight: false)
116
+ else # TODO: remove once ruby 3.1 support is dropped
117
+ "#{e.message} (#{e.class.name})"
118
+ end
109
119
  )
110
120
  end
111
121
 
@@ -121,9 +131,14 @@ module Sass
121
131
  containing_url_unused: canonicalize_context.instance_variable_get(:@containing_url_unused)
122
132
  )
123
133
  rescue StandardError => e
134
+ @session.backtrace = e.backtrace
124
135
  EmbeddedProtocol::InboundMessage::FileImportResponse.new(
125
136
  id: file_import_request.id,
126
- error: e.full_message(highlight: @highlight, order: :top)
137
+ error: if e.respond_to?(:detailed_message)
138
+ e.detailed_message(highlight: false)
139
+ else # TODO: remove once ruby 3.1 support is dropped
140
+ "#{e.message} (#{e.class.name})"
141
+ end
127
142
  )
128
143
  end
129
144
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Sass
4
4
  class Compiler
5
- class Host
5
+ class Session
6
6
  # The {LoggerRegistry} class.
7
7
  #
8
8
  # It stores logger and handles log events.
@@ -24,13 +24,13 @@ module Sass
24
24
  if @logger_respond_to_debug
25
25
  @logger.debug(event.message, DebugContext.new(event))
26
26
  else
27
- Kernel.warn(event.formatted)
27
+ Kernel.warn(Path.pretty_formatted!(+event.formatted, event.span.url))
28
28
  end
29
29
  when :DEPRECATION_WARNING, :WARNING
30
30
  if @logger_respond_to_warn
31
31
  @logger.warn(event.message, WarnContext.new(event))
32
32
  else
33
- Kernel.warn(event.formatted)
33
+ Kernel.warn(StackTrace.pretty_formatted!(+event.formatted, event.stack_trace))
34
34
  end
35
35
  else
36
36
  raise ArgumentError, "Unknown LogEvent.type #{event.type}"
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ class Compiler
5
+ class Session
6
+ # @see https://pub.dev/documentation/path/latest/path/
7
+ module Path
8
+ module_function
9
+
10
+ # @see https://pub.dev/documentation/path/latest/path/Context/prettyUri.html
11
+ def pretty_uri(uri)
12
+ return uri unless uri&.start_with?('file:')
13
+
14
+ absolute_path = Uri.file_uri_to_path(uri)
15
+ relative_path = Uri.decode_uri_component(Uri.relative(uri, Uri.pwd))
16
+ relative_path.count('/') > absolute_path.count('/') ? absolute_path : relative_path
17
+ end
18
+
19
+ def pretty_formatted!(formatted, uri)
20
+ index = formatted.index(uri)
21
+ return formatted unless index
22
+
23
+ replacement = pretty_uri(uri)
24
+ return formatted if uri == replacement
25
+
26
+ formatted[index, uri.length] = replacement
27
+ formatted
28
+ end
29
+ end
30
+
31
+ private_constant :Path
32
+ end
33
+ end
34
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Sass
4
4
  class Compiler
5
- class Host
5
+ class Session
6
6
  # The {Protofier} class.
7
7
  #
8
8
  # It converts Pure Ruby types and Protobuf Ruby types.
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ class Compiler
5
+ class Session
6
+ # @see https://pub.dev/documentation/stack_trace/latest/stack_trace/
7
+ module StackTrace
8
+ module_function
9
+
10
+ # @see https://pub.dev/documentation/stack_trace/latest/stack_trace/Trace/toString.html
11
+ def pretty_formatted!(formatted, stack_trace)
12
+ longest = 0
13
+
14
+ frames = stack_trace.lines("\n", chomp: true).map do |frame|
15
+ location, member = frame.split(/ +/, 2)
16
+ uri, line_column = location.split(' ', 2)
17
+
18
+ uri = Path.pretty_uri(uri)
19
+ location = line_column.nil? ? uri : "#{uri} #{line_column}"
20
+
21
+ longest = location.length if location.length > longest
22
+ [frame, location, member]
23
+ end
24
+
25
+ offset = formatted.length
26
+
27
+ frames.reverse_each do |frame, location, member|
28
+ index = formatted.rindex(frame, offset)
29
+ next unless index
30
+
31
+ offset = index
32
+
33
+ replacement = "#{location.ljust(longest)} #{member}"
34
+ next if frame == replacement
35
+
36
+ formatted[index, frame.length] = replacement
37
+ end
38
+
39
+ formatted
40
+ end
41
+ end
42
+
43
+ private_constant :StackTrace
44
+ end
45
+ end
46
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Sass
4
4
  class Compiler
5
- class Host
5
+ class Session
6
6
  # The {Struct} class.
7
7
  #
8
8
  # It creates {::Struct}-like objects from {::Hash}.
@@ -1,19 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'host/function_registry'
4
- require_relative 'host/importer_registry'
5
- require_relative 'host/logger_registry'
6
- require_relative 'host/protofier'
7
- require_relative 'host/struct'
3
+ require_relative 'session/function_registry'
4
+ require_relative 'session/importer_registry'
5
+ require_relative 'session/logger_registry'
6
+ require_relative 'session/path'
7
+ require_relative 'session/protofier'
8
+ require_relative 'session/stack_trace'
9
+ require_relative 'session/struct'
8
10
 
9
11
  module Sass
10
12
  class Compiler
11
- # The {Host} class.
13
+ # The {Session} class.
12
14
  #
13
15
  # It communicates with {Dispatcher} and handles the host logic.
14
- class Host
16
+ class Session
17
+ attr_writer :backtrace
18
+
15
19
  def initialize(channel)
16
20
  @channel = channel
21
+ @backtrace = nil
17
22
  end
18
23
 
19
24
  def compile_request(path:,
@@ -38,8 +43,8 @@ module Sass
38
43
  verbose:)
39
44
  alert_color = Exception.to_tty? if alert_color.nil?
40
45
 
41
- @function_registry = FunctionRegistry.new(functions, alert_color:)
42
- @importer_registry = ImporterRegistry.new(importers, load_paths, alert_color:)
46
+ @function_registry = FunctionRegistry.new(functions, session: self)
47
+ @importer_registry = ImporterRegistry.new(importers, load_paths, session: self)
43
48
  @logger_registry = LoggerRegistry.new(logger)
44
49
 
45
50
  compile_request = EmbeddedProtocol::InboundMessage::CompileRequest.new(
@@ -83,13 +88,15 @@ module Sass
83
88
  result = compile_response.public_send(oneof)
84
89
  case oneof
85
90
  when :failure
86
- raise CompileError.new(
91
+ compile_error = CompileError.new(
87
92
  result.message,
88
- result.formatted == '' ? nil : result.formatted,
93
+ result.formatted == '' ? nil : StackTrace.pretty_formatted!(+result.formatted, result.stack_trace),
89
94
  result.stack_trace == '' ? nil : result.stack_trace,
90
95
  result.span.nil? ? nil : Logger::SourceSpan.new(result.span),
91
96
  compile_response.loaded_urls.to_a
92
97
  )
98
+ compile_error.set_backtrace(@backtrace) unless @backtrace.nil?
99
+ raise compile_error
93
100
  when :success
94
101
  CompileResult.new(
95
102
  result.css,
@@ -221,6 +228,6 @@ module Sass
221
228
  end
222
229
  end
223
230
 
224
- private_constant :Host
231
+ private_constant :Session
225
232
  end
226
233
  end
data/lib/sass/compiler.rb CHANGED
@@ -5,7 +5,7 @@ require_relative 'compile_result'
5
5
  require_relative 'compiler/channel'
6
6
  require_relative 'compiler/connection'
7
7
  require_relative 'compiler/dispatcher'
8
- require_relative 'compiler/host'
8
+ require_relative 'compiler/session'
9
9
  require_relative 'compiler/varint'
10
10
  require_relative 'embedded/version'
11
11
  require_relative 'embedded_protocol'
@@ -17,6 +17,7 @@ require_relative 'logger/source_location'
17
17
  require_relative 'logger/source_span'
18
18
  require_relative 'node_package_importer'
19
19
  require_relative 'serializer'
20
+ require_relative 'uri'
20
21
  require_relative 'value'
21
22
 
22
23
  module Sass
@@ -86,7 +87,7 @@ module Sass
86
87
  verbose: false)
87
88
  raise ArgumentError, 'path must be set' if path.nil?
88
89
 
89
- Host.new(@channel).compile_request(
90
+ Session.new(@channel).compile_request(
90
91
  path:,
91
92
  source: nil,
92
93
  importer: nil,
@@ -169,7 +170,7 @@ module Sass
169
170
  verbose: false)
170
171
  raise ArgumentError, 'source must be set' if source.nil?
171
172
 
172
- Host.new(@channel).compile_request(
173
+ Session.new(@channel).compile_request(
173
174
  path: nil,
174
175
  source:,
175
176
  importer:,
@@ -198,7 +199,7 @@ module Sass
198
199
  def info
199
200
  @info ||= [
200
201
  ['sass-embedded', Embedded::VERSION, '(Embedded Host)', '[Ruby]'].join("\t"),
201
- Host.new(@channel).version_request.join("\t")
202
+ Session.new(@channel).version_request.join("\t")
202
203
  ].join("\n").freeze
203
204
  end
204
205
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sass
4
4
  module Embedded
5
- VERSION = '1.99.0'
5
+ VERSION = '1.101.0'
6
6
  end
7
7
  end