sass-embedded 0.2.2 → 0.4.1

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/error.rb CHANGED
@@ -1,17 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sass
4
- class BaseError < StandardError; end
4
+ class SassError < StandardError; end
5
5
 
6
- class ProtocolError < BaseError; end
6
+ class ProtocolError < SassError; end
7
7
 
8
- class NotRenderedError < BaseError; end
9
-
10
- class InvalidStyleError < BaseError; end
11
-
12
- class UnsupportedValue < BaseError; end
13
-
14
- class CompilationError < BaseError
8
+ # The error returned by {Sass.render}
9
+ class RenderError < SassError
15
10
  attr_accessor :formatted, :file, :line, :column, :status
16
11
 
17
12
  def initialize(message, formatted, file, line, column, status)
@@ -2,23 +2,56 @@
2
2
 
3
3
  require 'open3'
4
4
  require 'observer'
5
- require_relative '../../ext/sass_embedded/embedded_sass_pb'
5
+ require_relative '../../ext/embedded_sass_pb'
6
6
 
7
7
  module Sass
8
+ # The interface for communicating with dart-sass-embedded.
9
+ # It handles message serialization and deserialization as well as
10
+ # tracking concurrent request and response
8
11
  class Transport
9
12
  include Observable
10
13
 
11
14
  DART_SASS_EMBEDDED = File.absolute_path(
12
- "../../ext/sass_embedded/sass_embedded/dart-sass-embedded#{Sass::Platform::OS == 'windows' ? '.bat' : ''}", __dir__
15
+ "../../ext/sass_embedded/dart-sass-embedded#{Platform::OS == 'windows' ? '.bat' : ''}", __dir__
13
16
  )
14
17
 
15
18
  PROTOCOL_ERROR_ID = 4_294_967_295
16
19
 
17
20
  def initialize
18
- @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(DART_SASS_EMBEDDED)
19
21
  @stdin_semaphore = Mutex.new
20
22
  @observerable_semaphore = Mutex.new
23
+ @stdin, @stdout, @stderr, @wait_thread = Open3.popen3(DART_SASS_EMBEDDED)
24
+ pipe @stderr, $stderr
25
+ receive
26
+ end
27
+
28
+ def add_observer(*args)
29
+ @observerable_semaphore.synchronize do
30
+ super(*args)
31
+ end
32
+ end
33
+
34
+ def send(req)
35
+ req_kind = req.class.name.split('::').last.gsub(/\B(?=[A-Z])/, '_').downcase
36
+ message = EmbeddedProtocol::InboundMessage.new(req_kind => req)
37
+ write message.to_proto
38
+ end
39
+
40
+ def close
41
+ delete_observers
42
+ @stdin.close unless @stdin.closed?
43
+ @stdout.close unless @stdout.closed?
44
+ @stderr.close unless @stderr.closed?
45
+ nil
46
+ end
47
+
48
+ def closed?
49
+ @stdin.closed?
50
+ end
51
+
52
+ private
21
53
 
54
+ def receive
22
55
  Thread.new do
23
56
  loop do
24
57
  bits = length = 0
@@ -28,10 +61,11 @@ module Sass
28
61
  bits += 7
29
62
  break if byte <= 0x7f
30
63
  end
31
- changed
32
64
  payload = @stdout.read length
65
+ message = EmbeddedProtocol::OutboundMessage.decode payload
33
66
  @observerable_semaphore.synchronize do
34
- notify_observers nil, Sass::EmbeddedProtocol::OutboundMessage.decode(payload)
67
+ changed
68
+ notify_observers nil, message
35
69
  end
36
70
  rescue Interrupt
37
71
  break
@@ -41,10 +75,12 @@ module Sass
41
75
  break
42
76
  end
43
77
  end
78
+ end
44
79
 
80
+ def pipe(readable, writeable)
45
81
  Thread.new do
46
82
  loop do
47
- warn @stderr.read
83
+ writeable.write readable.read
48
84
  rescue Interrupt
49
85
  break
50
86
  rescue IOError => e
@@ -57,82 +93,14 @@ module Sass
57
93
  end
58
94
  end
59
95
 
60
- def send(req, id)
61
- mutex = Mutex.new
62
- resource = ConditionVariable.new
63
-
64
- req_name = req.class.name.split('::').last.gsub(/\B(?=[A-Z])/, '_').downcase
65
-
66
- message = Sass::EmbeddedProtocol::InboundMessage.new(req_name.to_sym => req)
67
-
68
- error = nil
69
- res = nil
70
-
71
- @observerable_semaphore.synchronize do
72
- MessageObserver.new self, id do |e, r|
73
- mutex.synchronize do
74
- error = e
75
- res = r
76
-
77
- resource.signal
78
- end
79
- end
80
- end
81
-
82
- mutex.synchronize do
83
- write message.to_proto
84
-
85
- resource.wait(mutex)
86
- end
87
-
88
- raise error if error
89
-
90
- res
91
- end
92
-
93
- def close
94
- delete_observers
95
- @stdin.close unless @stdin.closed?
96
- @stdout.close unless @stdout.closed?
97
- @stderr.close unless @stderr.closed?
98
- nil
99
- end
100
-
101
- private
102
-
103
- def write(proto)
96
+ def write(payload)
104
97
  @stdin_semaphore.synchronize do
105
- length = proto.length
98
+ length = payload.length
106
99
  while length.positive?
107
100
  @stdin.write ((length > 0x7f ? 0x80 : 0) | (length & 0x7f)).chr
108
101
  length >>= 7
109
102
  end
110
- @stdin.write proto
111
- end
112
- end
113
-
114
- class MessageObserver
115
- def initialize(obs, id, &block)
116
- @obs = obs
117
- @id = id
118
- @block = block
119
- @obs.add_observer self
120
- end
121
-
122
- def update(error, message)
123
- if error
124
- @obs.delete_observer self
125
- @block.call error, nil
126
- elsif message.error&.id == Sass::Transport::PROTOCOL_ERROR_ID
127
- @obs.delete_observer self
128
- @block.call Sass::ProtocolError.new(message.error.message), nil
129
- else
130
- res = message[message.message.to_s]
131
- if (res['compilation_id'] || res['id']) == @id
132
- @obs.delete_observer self
133
- @block.call error, res
134
- end
135
- end
103
+ @stdin.write payload
136
104
  end
137
105
  end
138
106
  end
data/lib/sass/util.rb CHANGED
@@ -1,19 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sass
4
+ # Utilities functions
4
5
  module Util
5
6
  module_function
6
7
 
8
+ FILE_PROTOCOL = 'file://'
9
+
7
10
  def file_uri(path)
8
11
  absolute_path = File.absolute_path(path)
9
12
 
10
- unless absolute_path.start_with?('/')
13
+ unless absolute_path.start_with? File::SEPARATOR
14
+ components = absolute_path.split File::SEPARATOR
15
+ components[0] = components[0][0].downcase
16
+ absolute_path = components.join File::SEPARATOR
17
+ end
18
+
19
+ "#{FILE_PROTOCOL}#{absolute_path}"
20
+ end
21
+
22
+ def path(file_uri)
23
+ absolute_path = file_uri[FILE_PROTOCOL.length..]
24
+
25
+ unless absolute_path.start_with? File::SEPARATOR
11
26
  components = absolute_path.split File::SEPARATOR
12
- components[0] = components[0].split(':').first.downcase
27
+ components[0] = "#{components[0].upcase}:"
13
28
  absolute_path = components.join File::SEPARATOR
14
29
  end
15
30
 
16
- "file://#{absolute_path}"
31
+ absolute_path
32
+ end
33
+
34
+ def relative(from, to)
35
+ Pathname.new(File.absolute_path(to)).relative_path_from(Pathname.new(File.absolute_path(from))).to_s
17
36
  end
18
37
 
19
38
  def now
data/lib/sass/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sass
4
- VERSION = '0.2.2'
4
+ VERSION = '0.4.1'
5
5
  end
@@ -15,11 +15,11 @@ Gem::Specification.new do |spec|
15
15
  spec.license = 'MIT'
16
16
 
17
17
  spec.files = `git ls-files -z`.split("\x0")
18
- spec.extensions = ['ext/sass_embedded/extconf.rb']
18
+ spec.extensions = ['ext/extconf.rb']
19
19
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
21
 
22
- spec.required_ruby_version = '>= 2.0.0'
22
+ spec.required_ruby_version = '>= 2.6.0'
23
23
 
24
24
  spec.require_paths = ['lib']
25
25
 
@@ -33,4 +33,6 @@ Gem::Specification.new do |spec|
33
33
  spec.add_development_dependency 'rake'
34
34
  spec.add_development_dependency 'rake-compiler'
35
35
  spec.add_development_dependency 'rubocop'
36
+ spec.add_development_dependency 'rubocop-minitest'
37
+ spec.add_development_dependency 'rubocop-rake'
36
38
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'test_helper'
4
+
5
+ module Sass
6
+ class ConcurrencyTest < MiniTest::Test
7
+ include TempFileTest
8
+
9
+ def setup
10
+ @embedded = Embedded.new
11
+ end
12
+
13
+ def teardown
14
+ @embedded.close
15
+ end
16
+
17
+ def render(data)
18
+ @embedded.render(data: data)[:css]
19
+ end
20
+
21
+ def test_concurrency
22
+ 10.times do
23
+ threads = []
24
+ 10.times do |i|
25
+ threads << Thread.new(i) do |id|
26
+ output = @embedded.render(data: "div { width: #{id} }")[:css]
27
+ assert_match(/#{id}/, output)
28
+ end
29
+ end
30
+ threads.each(&:join)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -13,155 +13,153 @@ module Sass
13
13
  end
14
14
 
15
15
  def render(sass)
16
- @embedded.render({
17
- data: sass,
18
- functions: {
19
- 'javascript_path($path)': lambda { |path|
20
- path.string.text = "/js/#{path.string.text}"
21
- path
22
- },
23
- 'stylesheet_path($path)': lambda { |path|
24
- path.string.text = "/css/#{path.string.text}"
25
- path
26
- },
27
- 'sass_return_path($path)': lambda { |path|
28
- path
29
- },
30
- 'no_return_path($path)': lambda { |_path|
31
- Sass::EmbeddedProtocol::Value.new(
32
- singleton: Sass::EmbeddedProtocol::SingletonValue::NULL
33
- )
16
+ @embedded.render(data: sass,
17
+ functions: {
18
+ 'javascript_path($path)': lambda { |path|
19
+ path.string.text = "/js/#{path.string.text}"
20
+ path
21
+ },
22
+ 'stylesheet_path($path)': lambda { |path|
23
+ path.string.text = "/css/#{path.string.text}"
24
+ path
25
+ },
26
+ 'sass_return_path($path)': lambda { |path|
27
+ path
34
28
  },
35
- 'optional_arguments($path, $optional: null)': lambda { |path, optional|
36
- Sass::EmbeddedProtocol::Value.new(
37
- string: Sass::EmbeddedProtocol::Value::String.new(
38
- text: "#{path.string.text}/#{optional.singleton == :NULL ? 'bar' : optional.string.text}",
39
- quoted: true
40
- )
29
+ 'no_return_path($path)': lambda { |_path|
30
+ EmbeddedProtocol::Value.new(
31
+ singleton: EmbeddedProtocol::SingletonValue::NULL
32
+ )
33
+ },
34
+ 'optional_arguments($path, $optional: null)': lambda { |path, optional|
35
+ EmbeddedProtocol::Value.new(
36
+ string: EmbeddedProtocol::Value::String.new(
37
+ text: "#{path.string.text}/#{optional.singleton == :NULL ? 'bar' : optional.string.text}",
38
+ quoted: true
41
39
  )
42
- },
43
- 'function_that_raises_errors()': lambda {
44
- raise StandardError,
45
- 'Intentional wrong thing happened somewhere inside the custom function'
46
- },
47
- 'nice_color_argument($color)': lambda { |color|
48
- color
40
+ )
41
+ },
42
+ 'function_that_raises_errors()': lambda {
43
+ raise StandardError,
44
+ 'Intentional wrong thing happened somewhere inside the custom function'
49
45
  },
50
- 'returns_a_color()': lambda {
51
- Sass::EmbeddedProtocol::Value.new(
52
- rgb_color: Sass::EmbeddedProtocol::Value::RgbColor.new(
53
- red: 0,
54
- green: 0,
55
- blue: 0,
56
- alpha: 1
57
- )
46
+ 'nice_color_argument($color)': lambda { |color|
47
+ color
48
+ },
49
+ 'returns_a_color()': lambda {
50
+ EmbeddedProtocol::Value.new(
51
+ rgb_color: EmbeddedProtocol::Value::RgbColor.new(
52
+ red: 0,
53
+ green: 0,
54
+ blue: 0,
55
+ alpha: 1
58
56
  )
59
- },
60
- 'returns_a_number()': lambda {
61
- Sass::EmbeddedProtocol::Value.new(
62
- number: Sass::EmbeddedProtocol::Value::Number.new(
63
- value: -312,
64
- numerators: ['rem']
65
- )
57
+ )
58
+ },
59
+ 'returns_a_number()': lambda {
60
+ EmbeddedProtocol::Value.new(
61
+ number: EmbeddedProtocol::Value::Number.new(
62
+ value: -312,
63
+ numerators: ['rem']
66
64
  )
67
- },
68
- 'returns_a_bool()': lambda {
69
- Sass::EmbeddedProtocol::Value.new(
70
- singleton: Sass::EmbeddedProtocol::SingletonValue::TRUE
71
65
  )
72
66
  },
73
- 'inspect_bool($argument)': lambda { |argument|
74
- unless argument&.singleton == :TRUE || argument.singleton == :FALSE
67
+ 'returns_a_bool()': lambda {
68
+ EmbeddedProtocol::Value.new(
69
+ singleton: EmbeddedProtocol::SingletonValue::TRUE
70
+ )
71
+ },
72
+ 'inspect_bool($argument)': lambda { |argument|
73
+ unless argument&.singleton == :TRUE || argument.singleton == :FALSE
74
+ raise StandardError,
75
+ 'passed value is not a EmbeddedProtocol::SingletonValue::TRUE or EmbeddedProtocol::SingletonValue::FALSE'
76
+ end
77
+
78
+ argument
79
+ },
80
+ 'inspect_number($argument)': lambda { |argument|
81
+ unless argument&.number.is_a? EmbeddedProtocol::Value::Number
75
82
  raise StandardError,
76
- 'passed value is not a Sass::EmbeddedProtocol::SingletonValue::TRUE or Sass::EmbeddedProtocol::SingletonValue::FALSE'
83
+ 'passed value is not a EmbeddedProtocol::Value::Number'
77
84
  end
78
85
 
79
86
  argument
80
87
  },
81
- 'inspect_number($argument)': lambda { |argument|
82
- unless argument&.number.is_a? Sass::EmbeddedProtocol::Value::Number
83
- raise StandardError,
84
- 'passed value is not a Sass::EmbeddedProtocol::Value::Number'
85
- end
86
-
87
- argument
88
- },
89
- 'inspect_map($argument)': lambda { |argument|
90
- unless argument&.map.is_a? Sass::EmbeddedProtocol::Value::Map
91
- raise StandardError,
92
- 'passed value is not a Sass::EmbeddedProtocol::Value::Map'
93
- end
88
+ 'inspect_map($argument)': lambda { |argument|
89
+ unless argument&.map.is_a? EmbeddedProtocol::Value::Map
90
+ raise StandardError,
91
+ 'passed value is not a EmbeddedProtocol::Value::Map'
92
+ end
94
93
 
95
- argument
96
- },
97
- 'inspect_list($argument)': lambda { |argument|
98
- unless argument&.list.is_a? Sass::EmbeddedProtocol::Value::List
99
- raise StandardError,
100
- 'passed value is not a Sass::EmbeddedProtocol::Value::List'
101
- end
94
+ argument
95
+ },
96
+ 'inspect_list($argument)': lambda { |argument|
97
+ unless argument&.list.is_a? EmbeddedProtocol::Value::List
98
+ raise StandardError,
99
+ 'passed value is not a EmbeddedProtocol::Value::List'
100
+ end
102
101
 
103
- argument
104
- },
105
- 'returns_sass_value()': lambda {
106
- Sass::EmbeddedProtocol::Value.new(
107
- rgb_color: Sass::EmbeddedProtocol::Value::RgbColor.new(
108
- red: 0,
109
- green: 0,
110
- blue: 0,
111
- alpha: 1
112
- )
102
+ argument
103
+ },
104
+ 'returns_sass_value()': lambda {
105
+ EmbeddedProtocol::Value.new(
106
+ rgb_color: EmbeddedProtocol::Value::RgbColor.new(
107
+ red: 0,
108
+ green: 0,
109
+ blue: 0,
110
+ alpha: 1
113
111
  )
114
- },
115
- 'returns_sass_map()': lambda {
116
- Sass::EmbeddedProtocol::Value.new(
117
- map: Sass::EmbeddedProtocol::Value::Map.new(
118
- entries: [
119
- Sass::EmbeddedProtocol::Value::Map::Entry.new(
120
- key: Sass::EmbeddedProtocol::Value.new(
121
- string: Sass::EmbeddedProtocol::Value::String.new(
122
- text: 'color',
123
- quoted: true
124
- )
125
- ),
126
- value: Sass::EmbeddedProtocol::Value.new(
127
- rgb_color: Sass::EmbeddedProtocol::Value::RgbColor.new(
128
- red: 0,
129
- green: 0,
130
- blue: 0,
131
- alpha: 1
132
- )
112
+ )
113
+ },
114
+ 'returns_sass_map()': lambda {
115
+ EmbeddedProtocol::Value.new(
116
+ map: EmbeddedProtocol::Value::Map.new(
117
+ entries: [
118
+ EmbeddedProtocol::Value::Map::Entry.new(
119
+ key: EmbeddedProtocol::Value.new(
120
+ string: EmbeddedProtocol::Value::String.new(
121
+ text: 'color',
122
+ quoted: true
123
+ )
124
+ ),
125
+ value: EmbeddedProtocol::Value.new(
126
+ rgb_color: EmbeddedProtocol::Value::RgbColor.new(
127
+ red: 0,
128
+ green: 0,
129
+ blue: 0,
130
+ alpha: 1
133
131
  )
134
132
  )
135
- ]
136
- )
133
+ )
134
+ ]
137
135
  )
138
- },
139
- 'returns_sass_list()': lambda {
140
- Sass::EmbeddedProtocol::Value.new(
141
- list: Sass::EmbeddedProtocol::Value::List.new(
142
- separator: Sass::EmbeddedProtocol::ListSeparator::COMMA,
143
- has_brackets: true,
144
- contents: [
145
- Sass::EmbeddedProtocol::Value.new(
146
- number: Sass::EmbeddedProtocol::Value::Number.new(
147
- value: 10
148
- )
149
- ),
150
- Sass::EmbeddedProtocol::Value.new(
151
- number: Sass::EmbeddedProtocol::Value::Number.new(
152
- value: 20
153
- )
154
- ),
155
- Sass::EmbeddedProtocol::Value.new(
156
- number: Sass::EmbeddedProtocol::Value::Number.new(
157
- value: 30
158
- )
136
+ )
137
+ },
138
+ 'returns_sass_list()': lambda {
139
+ EmbeddedProtocol::Value.new(
140
+ list: EmbeddedProtocol::Value::List.new(
141
+ separator: EmbeddedProtocol::ListSeparator::COMMA,
142
+ has_brackets: true,
143
+ contents: [
144
+ EmbeddedProtocol::Value.new(
145
+ number: EmbeddedProtocol::Value::Number.new(
146
+ value: 10
159
147
  )
160
- ]
161
- )
148
+ ),
149
+ EmbeddedProtocol::Value.new(
150
+ number: EmbeddedProtocol::Value::Number.new(
151
+ value: 20
152
+ )
153
+ ),
154
+ EmbeddedProtocol::Value.new(
155
+ number: EmbeddedProtocol::Value::Number.new(
156
+ value: 30
157
+ )
158
+ )
159
+ ]
162
160
  )
163
- }
164
- }
161
+ )
162
+ }
165
163
  })[:css]
166
164
  end
167
165
 
@@ -270,7 +268,7 @@ module Sass
270
268
  end
271
269
 
272
270
  def test_function_with_error
273
- assert_raises(Sass::CompilationError) do
271
+ assert_raises(RenderError) do
274
272
  render('div {url: function_that_raises_errors();}')
275
273
  end
276
274
  end
@@ -322,17 +320,15 @@ module Sass
322
320
  threads = []
323
321
  10.times do |i|
324
322
  threads << Thread.new(i) do |id|
325
- output = @embedded.render({
326
- data: 'div { url: test-function() }',
327
- functions: {
328
- 'test_function()': lambda {
329
- Sass::EmbeddedProtocol::Value.new(
330
- string: Sass::EmbeddedProtocol::Value::String.new(
331
- text: "{test_key1: 'test_value', test_key2: #{id}}",
332
- quoted: true
333
- )
323
+ output = @embedded.render(data: 'div { url: test-function() }',
324
+ functions: {
325
+ 'test_function()': lambda {
326
+ EmbeddedProtocol::Value.new(
327
+ string: EmbeddedProtocol::Value::String.new(
328
+ text: "{test_key1: 'test_value', test_key2: #{id}}",
329
+ quoted: true
334
330
  )
335
- }
331
+ )
336
332
  }
337
333
  })[:css]
338
334
  assert_match(/test_key1/, output)
@@ -346,17 +342,15 @@ module Sass
346
342
  end
347
343
 
348
344
  def test_pass_custom_functions_as_a_parameter
349
- output = @embedded.render({
350
- data: 'div { url: test-function(); }',
351
- functions: {
352
- 'test_function()': lambda {
353
- Sass::EmbeddedProtocol::Value.new(
354
- string: Sass::EmbeddedProtocol::Value::String.new(
355
- text: 'custom_function',
356
- quoted: true
357
- )
345
+ output = @embedded.render(data: 'div { url: test-function(); }',
346
+ functions: {
347
+ 'test_function()': lambda {
348
+ EmbeddedProtocol::Value.new(
349
+ string: EmbeddedProtocol::Value::String.new(
350
+ text: 'custom_function',
351
+ quoted: true
358
352
  )
359
- }
353
+ )
360
354
  }
361
355
  })[:css]
362
356
 
@@ -364,13 +358,11 @@ module Sass
364
358
  end
365
359
 
366
360
  def test_pass_incompatible_type_to_custom_functions
367
- assert_raises(CompilationError) do
368
- @embedded.render({
369
- data: 'div { url: test-function(); }',
370
- functions: {
371
- 'test_function()': lambda {
372
- Class.new
373
- }
361
+ assert_raises(RenderError) do
362
+ @embedded.render(data: 'div { url: test-function(); }',
363
+ functions: {
364
+ 'test_function()': lambda {
365
+ Class.new
374
366
  }
375
367
  })[:css]
376
368
  end