sass-embedded 0.9.2 → 0.12.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.
data/lib/sass/embedded.rb CHANGED
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'base64'
4
- require 'json'
3
+ require_relative 'compile_error'
4
+ require_relative 'compile_result'
5
+ require_relative 'importer_result'
5
6
  require_relative 'embedded/channel'
6
7
  require_relative 'embedded/compile_context'
7
- require_relative 'embedded/error'
8
- require_relative 'embedded/result'
9
- require_relative 'embedded/util'
8
+ require_relative 'embedded/render'
9
+ require_relative 'embedded/url'
10
10
  require_relative 'embedded/version'
11
11
  require_relative 'embedded/version_context'
12
+ require_relative 'logger'
12
13
 
13
14
  module Sass
14
15
  # The {Embedded} host for using dart-sass-embedded. Each instance creates
@@ -16,241 +17,117 @@ module Sass
16
17
  #
17
18
  # @example
18
19
  # embedded = Sass::Embedded.new
19
- # result = embedded.render(data: 'h1 { font-size: 40px; }')
20
- # result = embedded.render(file: 'style.css')
20
+ # result = embedded.compile_string('h1 { font-size: 40px; }')
21
+ # result = embedded.compile('style.scss')
21
22
  # embedded.close
22
23
  class Embedded
23
- def self.include_paths
24
- @include_paths ||= if ENV['SASS_PATH']
25
- ENV['SASS_PATH'].split(File::PATH_SEPARATOR)
26
- else
27
- []
28
- end
29
- end
30
-
31
24
  def initialize
32
25
  @channel = Channel.new
33
26
  end
34
27
 
35
- # The {Embedded#info} method.
28
+ # The {Embedded#compile} method.
36
29
  #
30
+ # @return [CompileResult]
31
+ # @raise [CompileError]
37
32
  # @raise [ProtocolError]
38
- def info
39
- @info ||= VersionContext.new(@channel).receive_message
40
- end
33
+ def compile(path,
34
+ load_paths: [],
41
35
 
42
- # The {Embedded#render} method.
43
- #
44
- # See {file:README.md#options} for supported options.
45
- #
46
- # @return [RenderResult]
47
- # @raise [ProtocolError]
48
- # @raise [RenderError]
49
- def render(data: nil,
50
- file: nil,
51
- indented_syntax: false,
52
- include_paths: [],
53
- output_style: :expanded,
54
- indent_type: :space,
55
- indent_width: 2,
56
- linefeed: :lf,
57
- source_map: false,
58
- out_file: nil,
59
- omit_source_map_url: false,
60
- source_map_contents: false,
61
- source_map_embed: false,
62
- source_map_root: '',
63
- functions: {},
64
- importer: [])
65
- start = Util.now
36
+ source_map: false,
37
+ style: :expanded,
66
38
 
67
- indent_type = parse_indent_type(indent_type)
68
- indent_width = parse_indent_width(indent_width)
69
- linefeed = parse_linefeed(linefeed)
39
+ functions: {},
40
+ importers: [],
41
+
42
+ alert_ascii: false,
43
+ alert_color: $stderr.tty?,
44
+ logger: nil,
45
+ quiet_deps: false,
46
+ verbose: false)
47
+
48
+ raise ArgumentError, 'path must be set' if path.nil?
70
49
 
71
50
  message = CompileContext.new(@channel,
72
- data: data,
73
- file: file,
74
- indented_syntax: indented_syntax,
75
- include_paths: include_paths,
76
- output_style: output_style,
51
+ path: path,
52
+ source: nil,
53
+ importer: nil,
54
+ load_paths: load_paths,
55
+ syntax: nil,
56
+ url: nil,
77
57
  source_map: source_map,
78
- out_file: out_file,
58
+ style: style,
79
59
  functions: functions,
80
- importer: importer).receive_message
81
-
82
- if message.failure
83
- raise RenderError.new(
84
- message.failure.message,
85
- message.failure.formatted,
86
- if message.failure.span.nil?
87
- nil
88
- elsif message.failure.span.url == ''
89
- 'stdin'
90
- else
91
- Util.path_from_file_uri(message.failure.span.url)
92
- end,
93
- message.failure.span ? message.failure.span.start.line + 1 : nil,
94
- message.failure.span ? message.failure.span.start.column + 1 : nil,
95
- 1
96
- )
97
- end
60
+ importers: importers,
61
+ alert_color: alert_color,
62
+ alert_ascii: alert_ascii,
63
+ logger: logger,
64
+ quiet_deps: quiet_deps,
65
+ verbose: verbose).receive_message
98
66
 
99
- map, source_map = post_process_map(map: message.success.source_map,
100
- data: data,
101
- file: file,
102
- out_file: out_file,
103
- source_map: source_map,
104
- source_map_contents: source_map_contents,
105
- source_map_root: source_map_root)
67
+ raise CompileError.from_proto(message.failure) if message.failure
106
68
 
107
- css = post_process_css(css: message.success.css,
108
- indent_type: indent_type,
109
- indent_width: indent_width,
110
- linefeed: linefeed,
111
- map: map,
112
- out_file: out_file,
113
- omit_source_map_url: omit_source_map_url,
114
- source_map: source_map,
115
- source_map_embed: source_map_embed)
116
-
117
- finish = Util.now
118
-
119
- stats = RenderResultStats.new(file.nil? ? 'data' : file, start, finish, finish - start)
120
-
121
- RenderResult.new(css, map, stats)
122
- end
123
-
124
- def close
125
- @channel.close
126
- end
127
-
128
- def closed?
129
- @channel.closed?
69
+ CompileResult.from_proto(message.success)
130
70
  end
131
71
 
132
- private
133
-
134
- def post_process_map(map:,
135
- data:,
136
- file:,
137
- out_file:,
138
- source_map:,
139
- source_map_contents:,
140
- source_map_root:)
141
- return if map.nil? || map.empty?
142
-
143
- map_data = JSON.parse(map)
144
-
145
- map_data['sourceRoot'] = source_map_root
146
-
147
- source_map_path = if source_map.is_a? String
148
- source_map
149
- else
150
- "#{out_file}.map"
151
- end
152
-
153
- source_map_dir = File.dirname(source_map_path)
154
-
155
- if out_file
156
- map_data['file'] = Util.relative_path(source_map_dir, out_file)
157
- elsif file
158
- ext = File.extname(file)
159
- map_data['file'] = "#{file[0..(ext.empty? ? -1 : -ext.length - 1)]}.css"
160
- else
161
- map_data['file'] = 'stdin.css'
162
- end
163
-
164
- map_data['sourcesContent'] = [] if source_map_contents
72
+ # The {Embedded#compile_string} method.
73
+ #
74
+ # @return [CompileResult]
75
+ # @raise [CompileError]
76
+ # @raise [ProtocolError]
77
+ def compile_string(source,
78
+ importer: nil,
79
+ load_paths: [],
80
+ syntax: :scss,
81
+ url: nil,
165
82
 
166
- file = File.absolute_path(file) unless file.nil?
83
+ source_map: false,
84
+ style: :expanded,
167
85
 
168
- map_data['sources'].map! do |source|
169
- if source.start_with? 'file://'
170
- path = Util.path_from_file_uri(source)
171
- content = if path == file && !data.nil?
172
- data
173
- else
174
- begin
175
- File.read(path)
176
- rescue StandardError
177
- nil
178
- end
179
- end
180
- map_data['sourcesContent'].push(content) if source_map_contents
181
- Util.relative_path(source_map_dir, path)
182
- else
183
- map_data['sourcesContent'].push(nil) if source_map_contents
184
- source
185
- end
186
- end
86
+ functions: {},
87
+ importers: [],
187
88
 
188
- [-JSON.generate(map_data), source_map_path]
189
- end
89
+ alert_ascii: false,
90
+ alert_color: $stderr.tty?,
91
+ logger: nil,
92
+ quiet_deps: false,
93
+ verbose: false)
94
+ raise ArgumentError, 'source must be set' if source.nil?
190
95
 
191
- def post_process_css(css:,
192
- indent_type:,
193
- indent_width:,
194
- linefeed:,
195
- map:,
196
- omit_source_map_url:,
197
- out_file:,
198
- source_map:,
199
- source_map_embed:)
200
- css = +css
201
- if indent_width != 2 || indent_type.to_sym != :space
202
- indent = indent_type * indent_width
203
- css.gsub!(/^ +/) do |space|
204
- indent * (space.length / 2)
205
- end
206
- end
207
- css.gsub!("\n", linefeed) if linefeed != "\n"
96
+ message = CompileContext.new(@channel,
97
+ path: nil,
98
+ source: source,
99
+ importer: importer,
100
+ load_paths: load_paths,
101
+ syntax: syntax,
102
+ url: url,
103
+ source_map: source_map,
104
+ style: style,
105
+ functions: functions,
106
+ importers: importers,
107
+ alert_color: alert_color,
108
+ alert_ascii: alert_ascii,
109
+ logger: logger,
110
+ quiet_deps: quiet_deps,
111
+ verbose: verbose).receive_message
208
112
 
209
- unless map.nil? || omit_source_map_url == true
210
- url = if source_map_embed
211
- "data:application/json;base64,#{Base64.strict_encode64(map)}"
212
- elsif out_file
213
- Util.relative_path(File.dirname(out_file), source_map)
214
- else
215
- source_map
216
- end
217
- css += "#{linefeed}#{linefeed}/*# sourceMappingURL=#{url} */"
218
- end
113
+ raise CompileError.from_proto(message.failure) if message.failure
219
114
 
220
- -css
115
+ CompileResult.from_proto(message.success)
221
116
  end
222
117
 
223
- def parse_indent_type(indent_type)
224
- case indent_type.to_sym
225
- when :space
226
- ' '
227
- when :tab
228
- "\t"
229
- else
230
- raise ArgumentError, 'indent_type must be one of :space, :tab'
231
- end
118
+ # The {Embedded#info} method.
119
+ #
120
+ # @raise [ProtocolError]
121
+ def info
122
+ @info ||= VersionContext.new(@channel).receive_message
232
123
  end
233
124
 
234
- def parse_indent_width(indent_width)
235
- raise ArgumentError, 'indent_width must be an integer' unless indent_width.is_a? Integer
236
- raise RangeError, 'indent_width must be in between 0 and 10 (inclusive)' unless indent_width.between? 0, 10
237
-
238
- indent_width
125
+ def close
126
+ @channel.close
239
127
  end
240
128
 
241
- def parse_linefeed(linefeed)
242
- case linefeed.to_sym
243
- when :lf
244
- "\n"
245
- when :lfcr
246
- "\n\r"
247
- when :cr
248
- "\r"
249
- when :crlf
250
- "\r\n"
251
- else
252
- raise ArgumentError, 'linefeed must be one of :lf, :lfcr, :cr, :crlf'
253
- end
129
+ def closed?
130
+ @channel.closed?
254
131
  end
255
132
  end
256
133
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ # The {FileImporter} interface.
5
+ class FileImporter
6
+ def find_file_url(url, from_import:) # rubocop:disable Lint/UnusedMethodArgument
7
+ raise NotImplementedError, 'FileImporter#find_file_url must be implemented'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ # The {Importer} interface.
5
+ class Importer
6
+ def canonicalize(url) # rubocop:disable Lint/UnusedMethodArgument
7
+ raise NotImplementedError, 'Importer#canonicalize must be implemented'
8
+ end
9
+
10
+ def load(canonical_url) # rubocop:disable Lint/UnusedMethodArgument
11
+ raise NotImplementedError, 'Importer#load must be implemented'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ # The {ImporterResult} of {Importer#load}.
5
+ class ImporterResult
6
+ attr_reader :contents, :syntax, :source_map_url
7
+
8
+ def initialize(contents, syntax, source_map_url = nil)
9
+ @contents = contents
10
+ @syntax = syntax
11
+ @source_map_url = source_map_url
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ module Logger
5
+ # The {SourceLocation} in {SourceSpan}.
6
+ class SourceLocation
7
+ attr_reader :offset, :line, :column
8
+
9
+ def initialize(offset, line, column)
10
+ @offset = offset
11
+ @line = line
12
+ @column = column
13
+ end
14
+
15
+ def self.from_proto(source_location)
16
+ return nil if source_location.nil?
17
+
18
+ SourceLocation.new(source_location.offset,
19
+ source_location.line,
20
+ source_location.column)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'source_location'
4
+
5
+ module Sass
6
+ module Logger
7
+ # The {SourceSpan} in {CompileError}.
8
+ class SourceSpan
9
+ attr_reader :start, :end, :text, :url, :context
10
+
11
+ def initialize(start, end_, text, url, context)
12
+ @start = start
13
+ @end = end_
14
+ @text = text
15
+ @url = url == '' ? nil : url
16
+ @context = context == '' ? nil : context
17
+ end
18
+
19
+ def self.from_proto(source_span)
20
+ return nil if source_span.nil?
21
+
22
+ SourceSpan.new(SourceLocation.from_proto(source_span.start),
23
+ SourceLocation.from_proto(source_span.end),
24
+ source_span.text,
25
+ source_span.url,
26
+ source_span.context)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sass
4
+ # The {Logger} module.
5
+ module Logger
6
+ module_function
7
+
8
+ # The instance of a silent {Logger}.
9
+ def silent
10
+ Silent
11
+ end
12
+
13
+ # The silent {Logger}.
14
+ module Silent
15
+ module_function
16
+
17
+ def warn(message, deprecation: false, span: nil, stack: nil); end
18
+
19
+ def debug(message, span: nil); end
20
+ end
21
+
22
+ private_constant :Silent
23
+ end
24
+ end
data/lib/sass.rb CHANGED
@@ -6,6 +6,35 @@ require_relative 'sass/embedded'
6
6
  # the Embedded Sass protocol.
7
7
  module Sass
8
8
  class << self
9
+ # The global {.compile} method. This instantiates a global {Embedded} instance
10
+ # and calls {Embedded#compile}.
11
+ #
12
+ # See {Embedded#compile} for supported options.
13
+ #
14
+ # @example
15
+ # Sass.compile('style.scss')
16
+ # @return [CompileResult]
17
+ # @raise [CompileError]
18
+ # @raise [ProtocolError]
19
+ def compile(path, **kwargs)
20
+ instance.compile(path, **kwargs)
21
+ end
22
+
23
+ # The global {.compile_string} method. This instantiates a global {Embedded} instance
24
+ # and calls {Embedded#compile_string}.
25
+ #
26
+ # See {Embedded#compile_string} for supported options.
27
+ #
28
+ # @example
29
+ # Sass.compile_string('h1 { font-size: 40px; }')
30
+ # @return [CompileResult]
31
+ # @raise [CompileError]
32
+ # @raise [ProtocolError]
33
+ def compile_string(source, **kwargs)
34
+ instance.compile_string(source, **kwargs)
35
+ end
36
+
37
+ # @deprecated
9
38
  # The global {.include_paths} for Sass files. This is meant for plugins and
10
39
  # libraries to register the paths to their Sass stylesheets to that they may
11
40
  # be included via `@import` or `@use`. This include path is used by every
@@ -32,10 +61,11 @@ module Sass
32
61
  instance.info
33
62
  end
34
63
 
64
+ # @deprecated
35
65
  # The global {.render} method. This instantiates a global {Embedded} instance
36
66
  # and calls {Embedded#render}.
37
67
  #
38
- # See {file:README.md#options} for supported options.
68
+ # See {Embedded#render} for supported options.
39
69
  #
40
70
  # @example
41
71
  # Sass.render(data: 'h1 { font-size: 40px; }')