opal 1.7.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +12 -4
  3. data/CHANGELOG.md +29 -1
  4. data/exe/opal +5 -7
  5. data/lib/opal/builder.rb +13 -13
  6. data/lib/opal/builder_processors.rb +8 -2
  7. data/lib/opal/builder_scheduler/prefork.rb +60 -6
  8. data/lib/opal/cli.rb +59 -43
  9. data/lib/opal/cli_runners/compiler.rb +7 -6
  10. data/lib/opal/cli_runners/safari.rb +208 -0
  11. data/lib/opal/cli_runners.rb +1 -0
  12. data/lib/opal/nodes/literal.rb +16 -0
  13. data/lib/opal/version.rb +1 -1
  14. data/opal/corelib/constants.rb +2 -2
  15. data/spec/filters/platform/.keep +0 -0
  16. data/spec/filters/platform/firefox/exception.rb +8 -0
  17. data/spec/filters/platform/firefox/kernel.rb +3 -0
  18. data/spec/filters/platform/safari/exception.rb +8 -0
  19. data/spec/filters/platform/safari/float.rb +4 -0
  20. data/spec/filters/platform/safari/kernel.rb +3 -0
  21. data/spec/filters/platform/safari/literal_regexp.rb +6 -0
  22. data/spec/lib/builder_spec.rb +32 -0
  23. data/spec/lib/cli_spec.rb +18 -6
  24. data/spec/lib/fixtures/build_order/file1.js +1 -0
  25. data/spec/lib/fixtures/build_order/file2.js +1 -0
  26. data/spec/lib/fixtures/build_order/file3.js +1 -0
  27. data/spec/lib/fixtures/build_order/file4.js +1 -0
  28. data/spec/lib/fixtures/build_order/file5.rb.erb +4 -0
  29. data/spec/lib/fixtures/build_order/file51.js +1 -0
  30. data/spec/lib/fixtures/build_order/file6.rb +10 -0
  31. data/spec/lib/fixtures/build_order/file61.rb +1 -0
  32. data/spec/lib/fixtures/build_order/file62.rb +4 -0
  33. data/spec/lib/fixtures/build_order/file63.rb +4 -0
  34. data/spec/lib/fixtures/build_order/file64.rb +1 -0
  35. data/spec/lib/fixtures/build_order/file7.rb +1 -0
  36. data/spec/lib/fixtures/build_order.rb +9 -0
  37. data/spec/lib/rake_dist_spec.rb +69 -0
  38. data/spec/mspec-opal/runner.rb +1 -0
  39. data/spec/opal/core/io/read_spec.rb +12 -3
  40. data/stdlib/opal/platform.rb +1 -0
  41. data/stdlib/opal-platform.rb +3 -0
  42. data/stdlib/time.rb +21 -1
  43. data/tasks/building.rake +1 -1
  44. data/tasks/testing.rake +3 -4
  45. metadata +29 -6
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+ require 'socket'
5
+ require 'timeout'
6
+ require 'tmpdir'
7
+ require 'rbconfig'
8
+ require 'opal/os'
9
+ require 'net/http'
10
+ require 'webrick'
11
+
12
+ module Opal
13
+ module CliRunners
14
+ class Safari
15
+ EXECUTION_TIMEOUT = 600 # seconds
16
+ DEFAULT_SAFARI_DRIVER_HOST = 'localhost'
17
+ DEFAULT_SAFARI_DRIVER_PORT = 9444 # in addition safari_driver_port + 1 is used for the http server
18
+
19
+ def self.call(data)
20
+ runner = new(data)
21
+ runner.run
22
+ end
23
+
24
+ def initialize(data)
25
+ argv = data[:argv]
26
+ if argv && argv.any?
27
+ warn "warning: ARGV is not supported by the Safari runner #{argv.inspect}"
28
+ end
29
+
30
+ options = data[:options]
31
+ @output = options.fetch(:output, $stdout)
32
+ @builder = data[:builder].call
33
+ end
34
+
35
+ attr_reader :output, :exit_status, :builder
36
+
37
+ def run
38
+ mktmpdir do |dir|
39
+ with_http_server(dir) do |http_port, server_thread|
40
+ with_safari_driver do
41
+ prepare_files_in(dir)
42
+
43
+ # Safaridriver commands are very limitied, for supported commands see:
44
+ # https://developer.apple.com/documentation/webkit/macos_webdriver_commands_for_safari_12_and_later
45
+ Net::HTTP.start(safari_driver_host, safari_driver_port) do |con|
46
+ con.read_timeout = EXECUTION_TIMEOUT
47
+ res = con.post('/session', { capabilities: { browserName: 'Safari' } }.to_json, 'Content-Type' => 'application/json')
48
+ session_id = JSON.parse(res.body).dig('value', 'sessionId')
49
+ if session_id
50
+ session_path = "/session/#{session_id}"
51
+ con.post("#{session_path}/url", { url: "http://#{safari_driver_host}:#{http_port}/index.html" }.to_json, 'Content-Type' => 'application/json')
52
+ server_thread.join(EXECUTION_TIMEOUT)
53
+ else
54
+ STDERR.puts "Could not create session: #{res.body}"
55
+ end
56
+ end
57
+ 0
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def prepare_files_in(dir)
66
+ # The safaridriver is very limited in capabilities, basically it can trigger visiting sites
67
+ # and interact a bit with the page. So this runner starts its own server, overwrites the
68
+ # console log, warn, error functions of the browser and triggers a request after execution
69
+ # to exit. Certain exceptions cannot be caught that way and everything may fail in between,
70
+ # thats why execution is timed out after EXECUTION_TIMEOUT (10 minutes).
71
+ # As a side effect, console messages may arrive out of order and timing anything may be inaccurate.
72
+
73
+ builder.build_str <<~RUBY, '(exit)', no_export: true
74
+ %x{
75
+ var req = new XMLHttpRequest();
76
+ req.open("GET", '/exit');
77
+ req.send();
78
+ }
79
+ RUBY
80
+
81
+ js = builder.to_s
82
+ map = builder.source_map.to_json
83
+ ext = builder.output_extension
84
+ module_type = ' type="module"' if builder.esm?
85
+
86
+ File.binwrite("#{dir}/index.#{ext}", js)
87
+ File.binwrite("#{dir}/index.map", map)
88
+ File.binwrite("#{dir}/index.html", <<~HTML)
89
+ <html><head>
90
+ <meta charset='utf-8'>
91
+ <link rel="icon" href="data:;base64,=">
92
+ </head><body>
93
+ <script>
94
+ var orig_log = console.log;
95
+ var orig_err = console.error;
96
+ var orig_warn = console.warn;
97
+ function send_log_request(args) {
98
+ var req = new XMLHttpRequest();
99
+ req.open("POST", '/log');
100
+ req.setRequestHeader("Content-Type", "application/json");
101
+ req.send(JSON.stringify(args));
102
+ }
103
+ console.log = function() {
104
+ orig_log.apply(null, arguments);
105
+ send_log_request(arguments);
106
+ }
107
+ console.error = function() {
108
+ orig_err.apply(null, arguments);
109
+ send_log_request(arguments);
110
+ }
111
+ console.warn = function() {
112
+ orig_warn.apply(null, arguments);
113
+ send_log_request(arguments);
114
+ }
115
+
116
+ </script>
117
+ <script src='./index.#{ext}'#{module_type}></script>
118
+ </body></html>
119
+ HTML
120
+
121
+ # <script src='./index.#{ext}'#{module_type}></script>
122
+ end
123
+
124
+ def safari_driver_host
125
+ ENV['SAFARI_DRIVER_HOST'] || DEFAULT_SAFARI_DRIVER_HOST
126
+ end
127
+
128
+ def safari_driver_port
129
+ ENV['SAFARI_DRIVER_PORT'] || DEFAULT_SAFARI_DRIVER_PORT
130
+ end
131
+
132
+ def with_http_server(dir)
133
+ port = safari_driver_port.to_i + 1
134
+ server_thread = Thread.new do
135
+ server = WEBrick::HTTPServer.new(Port: port, DocumentRoot: dir, Logger: WEBrick::Log.new('/dev/null'), AccessLog: [])
136
+ server.mount_proc('/log') do |req, res|
137
+ if req.body
138
+ par = JSON.parse(req.body)
139
+ par.each_value do |value|
140
+ print value.to_s
141
+ end
142
+ end
143
+ res.header['Content-Type'] = 'text/plain'
144
+ res.body = ''
145
+ end
146
+ server.mount_proc('/exit') do
147
+ server_thread.kill
148
+ end
149
+ server.start
150
+ end
151
+
152
+ yield port, server_thread
153
+ rescue
154
+ exit(1)
155
+ ensure
156
+ server_thread.kill if server_thread
157
+ end
158
+
159
+ def with_safari_driver
160
+ if safari_driver_running?
161
+ yield
162
+ else
163
+ run_safari_driver { yield }
164
+ end
165
+ end
166
+
167
+ def run_safari_driver
168
+ raise 'Safari driver can be started only on localhost' if safari_driver_host != DEFAULT_SAFARI_DRIVER_HOST
169
+
170
+ started = false
171
+
172
+ safari_driver_cmd = %{/usr/bin/safaridriver \
173
+ -p #{safari_driver_port} \
174
+ #{ENV['SAFARI_DRIVER_OPTS']}}
175
+
176
+ safari_driver_pid = Process.spawn(safari_driver_cmd, in: OS.dev_null, out: OS.dev_null, err: OS.dev_null)
177
+
178
+ Timeout.timeout(30) do
179
+ loop do
180
+ break if safari_driver_running?
181
+ sleep 0.5
182
+ end
183
+ end
184
+
185
+ started = true
186
+
187
+ yield
188
+ rescue Timeout::Error => e
189
+ puts started ? 'Execution timed out' : 'Failed to start Safari driver'
190
+ raise e
191
+ ensure
192
+ Process.kill('HUP', safari_driver_pid) if safari_driver_pid
193
+ end
194
+
195
+ def safari_driver_running?
196
+ puts "Connecting to #{safari_driver_host}:#{safari_driver_port}..."
197
+ TCPSocket.new(safari_driver_host, safari_driver_port).close
198
+ true
199
+ rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL
200
+ false
201
+ end
202
+
203
+ def mktmpdir(&block)
204
+ Dir.mktmpdir('safari-opal-', &block)
205
+ end
206
+ end
207
+ end
208
+ end
@@ -93,6 +93,7 @@ module Opal
93
93
 
94
94
  if OS.macos?
95
95
  register_runner :applescript, :Applescript, 'opal/cli_runners/applescript'
96
+ register_runner :safari, :Safari, 'opal/cli_runners/safari'
96
97
  alias_runner :osascript, :applescript
97
98
  end
98
99
  end
@@ -158,11 +158,27 @@ module Opal
158
158
  case value
159
159
  when ''
160
160
  push('/(?:)/')
161
+ when /\(\?<=|\(\?<!/
162
+ # Safari/WebKit will not execute javascript code if it contains a lookbehind literal RegExp
163
+ # and they fail with "Syntax Error". This tricks their parser by disguising the literal RegExp
164
+ # as string for the dynamic $regexp helper. Safari/Webkit will still fail to execute the RegExp,
165
+ # but at least they will parse and run everything else.
166
+ static_as_dynamic(value)
161
167
  else
162
168
  push "#{Regexp.new(value).inspect}#{flags.join}"
163
169
  end
164
170
  end
165
171
 
172
+ def static_as_dynamic(value)
173
+ helper :regexp
174
+
175
+ push '$regexp(["'
176
+ push value.gsub('\\', '\\\\\\\\')
177
+ push '"]'
178
+ push ", '#{flags.join}'" if flags.any?
179
+ push ")"
180
+ end
181
+
166
182
  def extract_flags_and_value
167
183
  *values, flags_sexp = *children
168
184
  self.flags = flags_sexp.children.map(&:to_s)
data/lib/opal/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  module Opal
4
4
  # WHEN RELEASING:
5
5
  # Remember to update RUBY_ENGINE_VERSION in opal/corelib/constants.rb too!
6
- VERSION = '1.7.0'
6
+ VERSION = '1.7.1'
7
7
  end
@@ -1,8 +1,8 @@
1
1
  ::RUBY_PLATFORM = 'opal'
2
2
  ::RUBY_ENGINE = 'opal'
3
3
  ::RUBY_VERSION = '3.2.0'
4
- ::RUBY_ENGINE_VERSION = '1.7.0'
5
- ::RUBY_RELEASE_DATE = '2022-12-26'
4
+ ::RUBY_ENGINE_VERSION = '1.7.1'
5
+ ::RUBY_RELEASE_DATE = '2023-01-06'
6
6
  ::RUBY_PATCHLEVEL = 0
7
7
  ::RUBY_REVISION = '0'
8
8
  ::RUBY_COPYRIGHT = 'opal - Copyright (C) 2013-2022 Adam Beynon and the Opal contributors'
File without changes
@@ -0,0 +1,8 @@
1
+ # Source map support currently is only available for Chrome and Nodejs
2
+ opal_unsupported_filter "Exception" do
3
+ fails "Exception#backtrace_locations sets each element to a Thread::Backtrace::Location"
4
+ fails "Exception#backtrace contains lines of the same format for each prior position in the stack"
5
+ fails "Exception#backtrace sets each element to a String"
6
+ fails "Invoking a method when the method is not available should omit the method_missing call from the backtrace for NoMethodError"
7
+ fails "Invoking a method when the method is not available should omit the method_missing call from the backtrace for NameError"
8
+ end
@@ -0,0 +1,3 @@
1
+ opal_unsupported_filter "Kernel" do
2
+ fails "Kernel#caller includes core library methods defined in Ruby"
3
+ end
@@ -0,0 +1,8 @@
1
+ # Source map support currently is only available for Chrome and Nodejs
2
+ opal_unsupported_filter "Exception" do
3
+ fails "Exception#backtrace contains lines of the same format for each prior position in the stack"
4
+ fails "Exception#backtrace_locations sets each element to a Thread::Backtrace::Location"
5
+ fails "Exception#backtrace returns an Array that can be updated"
6
+ fails "Exception#backtrace returns the same array after duping"
7
+ fails "Exception#backtrace sets each element to a String"
8
+ end
@@ -0,0 +1,4 @@
1
+ opal_filter "Float" do
2
+ fails "Float#fdiv performs floating-point division between self and an Integer" # Expected 8.900000000008007e-117 == 8.900000000008011e-117 to be truthy but was false
3
+ fails "Float#quo performs floating-point division between self and an Integer" # Expected 8.900000000008007e-117 == 8.900000000008011e-117 to be truthy but was false
4
+ end
@@ -0,0 +1,3 @@
1
+ opal_unsupported_filter "Kernel" do
2
+ fails "Kernel#caller includes core library methods defined in Ruby"
3
+ end
@@ -0,0 +1,6 @@
1
+ opal_unsupported_filter "Literal Regexp" do
2
+ # Safari and WebKit do not support lookbehind, but may in the future see https://github.com/WebKit/WebKit/pull/7109
3
+ fails "Literal Regexps handles a lookbehind with ss characters"
4
+ fails "Literal Regexps supports (?<= ) (positive lookbehind)"
5
+ fails "Literal Regexps supports (?<! ) (negative lookbehind)"
6
+ end
@@ -154,4 +154,36 @@ RSpec.describe Opal::Builder do
154
154
  expect(builder.build('opal/platform', requirable: true).to_s).to include(%{Opal.modules["opal/platform"]})
155
155
  end
156
156
  end
157
+
158
+ describe 'output order' do
159
+ it 'is preserved with a prefork scheduler' do
160
+ my_builder = builder.dup
161
+ my_builder.append_paths(File.expand_path('..', __FILE__))
162
+ my_builder.cache = Opal::Cache::NullCache.new
163
+ 10.times do |i| # Increase entropy
164
+ expect(
165
+ my_builder.dup.build('fixtures/build_order').to_s.scan(/(FILE_[0-9]+)/).map(&:first)
166
+ ).to eq(%w[
167
+ FILE_1 FILE_2 FILE_3 FILE_4
168
+ FILE_51 FILE_5
169
+ FILE_61 FILE_62 FILE_63 FILE_64 FILE_6
170
+ FILE_7
171
+ ])
172
+ end
173
+ end
174
+
175
+ it 'is preserved with a sequential scheduler' do
176
+ temporarily_with_sequential_scheduler do
177
+ builder.append_paths(File.expand_path('..', __FILE__))
178
+ expect(
179
+ builder.build('fixtures/build_order').to_s.scan(/(FILE_[0-9]+)/).map(&:first)
180
+ ).to eq(%w[
181
+ FILE_1 FILE_2 FILE_3 FILE_4
182
+ FILE_51 FILE_5
183
+ FILE_61 FILE_62 FILE_63 FILE_64 FILE_6
184
+ FILE_7
185
+ ])
186
+ end
187
+ end
188
+ end
157
189
  end
data/spec/lib/cli_spec.rb CHANGED
@@ -9,7 +9,7 @@ RSpec.describe Opal::CLI do
9
9
  subject(:cli) { described_class.new(options) }
10
10
 
11
11
  context 'with a file' do
12
- let(:options) { {:file => File.open(file)} }
12
+ let(:options) { {argv: [file]} }
13
13
 
14
14
  it 'runs the file' do
15
15
  expect_output_of{ subject.run }.to eq("hi from opal!\n")
@@ -26,8 +26,10 @@ RSpec.describe Opal::CLI do
26
26
 
27
27
  describe ':evals option' do
28
28
  context 'without evals and paths' do
29
- it 'raises ArgumentError' do
30
- expect { subject.run }.to raise_error(ArgumentError)
29
+ it 'uses stdin' do
30
+ expect(cli.file).to eq($stdin)
31
+ expect(cli.filename).to eq('-')
32
+ expect(cli.argv).to eq([])
31
33
  end
32
34
 
33
35
  context 'with lib_only: true and opal require' do
@@ -40,7 +42,7 @@ RSpec.describe Opal::CLI do
40
42
  end
41
43
 
42
44
  context 'with one eval' do
43
- let(:options) { {:evals => ['puts "hello"']} }
45
+ let(:options) { {evals: ['puts "hello"']} }
44
46
 
45
47
  it 'executes the code' do
46
48
  expect_output_of{ subject.run }.to eq("hello\n")
@@ -56,12 +58,22 @@ RSpec.describe Opal::CLI do
56
58
  end
57
59
 
58
60
  context 'with many evals' do
59
- let(:options) { {:evals => ['puts "hello"', 'puts "ciao"']} }
61
+ let(:options) { {evals: ['puts "hello"', 'puts "ciao"']} }
60
62
 
61
63
  it 'executes the code' do
62
64
  expect_output_of{ subject.run }.to eq("hello\nciao\n")
63
65
  end
64
66
  end
67
+
68
+ context 'with a file path arg' do
69
+ let(:options) { {:evals => ['puts "hello"'], argv: [file]} }
70
+
71
+ it 'considers it as part of ARGV' do
72
+ expect(cli.argv).to eq([file])
73
+ expect(cli.file.tap(&:rewind).read).to eq('puts "hello"')
74
+ expect(cli.filename).to eq('-e')
75
+ end
76
+ end
65
77
  end
66
78
 
67
79
  describe ':no_exit option' do
@@ -229,7 +241,7 @@ RSpec.describe Opal::CLI do
229
241
 
230
242
  describe ':enable_source_location' do
231
243
  let(:file) { File.expand_path('../fixtures/source_location_test.rb', __FILE__) }
232
- let(:options) { { enable_source_location: true, runner: :compiler, file: File.open(file) } }
244
+ let(:options) { { enable_source_location: true, runner: :compiler, argv: [file] } }
233
245
 
234
246
  it 'sets $$source_location prop for compiled methods' do
235
247
  expect_output_of { subject.run }.to include("source_location_test.rb', 6]")
@@ -0,0 +1 @@
1
+ var FILE_1;
@@ -0,0 +1 @@
1
+ var FILE_2;
@@ -0,0 +1 @@
1
+ var FILE_3;
@@ -0,0 +1 @@
1
+ var FILE_4;
@@ -0,0 +1,4 @@
1
+ # Try loading a file with an absolute path.
2
+ require "<%= p __dir__.inspect[1..-2] %>/file51.js"
3
+
4
+ FILE_5 = true
@@ -0,0 +1 @@
1
+ var FILE_51;
@@ -0,0 +1,10 @@
1
+ require_relative 'file61'
2
+
3
+ # There's a circular require between file63 and 62
4
+ # file62 should be in output file before 63
5
+ require 'fixtures/build_order/file63'
6
+ require_relative './file62'
7
+
8
+ require 'fixtures/build_order/file64.rb'
9
+
10
+ FILE_6 = true
@@ -0,0 +1 @@
1
+ FILE_61 = true
@@ -0,0 +1,4 @@
1
+ # A circular require
2
+ require_relative 'file63'
3
+
4
+ FILE_62 = true
@@ -0,0 +1,4 @@
1
+ # A circular require
2
+ require_relative 'file62'
3
+
4
+ FILE_63 = true
@@ -0,0 +1 @@
1
+ FILE_64 = true
@@ -0,0 +1 @@
1
+ FILE_7 = true
@@ -0,0 +1,9 @@
1
+ # This fixture tests build order of required JS files
2
+
3
+ require 'fixtures/build_order/file1'
4
+ require 'fixtures/build_order/file2'
5
+ require 'fixtures/build_order/file3'
6
+ require 'fixtures/build_order/file4'
7
+ require 'fixtures/build_order/file5'
8
+ require 'fixtures/build_order/file6'
9
+ require 'fixtures/build_order/file7'
@@ -0,0 +1,69 @@
1
+ require 'lib/spec_helper'
2
+ require 'open3'
3
+ require 'opal/os'
4
+
5
+ RSpec.describe "rake dist" do
6
+ before :all do
7
+ system "rake dist >#{Opal::OS.dev_null}"
8
+ end
9
+
10
+ def run_with_node(code, precode:, requires:)
11
+ requires = requires.map do |i|
12
+ "require('./build/#{i}');"
13
+ end.join
14
+
15
+ code = "#{requires};#{precode};console.log(#{code});"
16
+
17
+ stdout, _, status = Open3.capture3('node', '-e', code)
18
+
19
+ expect(status).to eq(0)
20
+
21
+ stdout.chomp
22
+ end
23
+
24
+ let(:output) { run_with_node(code, precode: precode, requires: requires) }
25
+ let(:requires) { ['opal'] }
26
+ let(:precode) { '' }
27
+ let(:code) { 'typeof Opal' }
28
+
29
+ it 'should provide a working Opal environment' do
30
+ expect(output).to eq('object')
31
+ end
32
+
33
+ context do
34
+ let(:requires) { ['opal/mini'] }
35
+
36
+ it 'should provide a working Opal mini environment' do
37
+ expect(output).to eq('object')
38
+ end
39
+ end
40
+
41
+ context do
42
+ let(:requires) { ['opal', 'opal/full'] }
43
+ let(:precode) { 'Opal.require("corelib/pattern_matching")' }
44
+ let(:code) { 'typeof Opal.PatternMatching' }
45
+
46
+ it 'should provide a working Opal full environment' do
47
+ expect(output).to eq('function')
48
+ end
49
+ end
50
+
51
+ context do
52
+ let(:requires) { %w[opal opal-replutils] }
53
+ let(:code) { 'typeof Opal.REPLUtils' }
54
+
55
+ it 'should not require requirable files by default' do
56
+ expect(output).to eq('undefined')
57
+ end
58
+ end
59
+
60
+ context do
61
+ let(:requires) { %w[opal opal-replutils] }
62
+ let(:precode) { 'Opal.require("opal-replutils")' }
63
+ let(:code) { 'typeof Opal.REPLUtils' }
64
+
65
+ it 'should allow user to require requirable files to provide missing functionality' do
66
+ expect(output).to eq('function')
67
+ end
68
+ end
69
+ end
@@ -80,6 +80,7 @@ class OSpecFormatter
80
80
  'server' => BrowserFormatter,
81
81
  'chrome' => DottedFormatter,
82
82
  'firefox' => DottedFormatter,
83
+ 'safari' => DottedFormatter,
83
84
  'deno' => NodeJSFormatter,
84
85
  'node' => NodeJSFormatter,
85
86
  'nodejs' => NodeJSFormatter,
@@ -32,7 +32,10 @@ describe "IO reading methods" do
32
32
  examples.each do |example|
33
33
  it "correctly splits messages for input #{example.inspect}" do
34
34
  io = prepare_io_for.(example)
35
- expected_output = example.gsub("|", "").split(/(?<=\n)/)
35
+ expected_output = example.gsub("|", "").split(/\n/).map { |e| e + "\n" }
36
+ len = expected_output.length
37
+ last = expected_output.last
38
+ expected_output[len-1] = last.chop if !example.end_with?("\n") && last
36
39
  io.readlines.should == expected_output
37
40
  end
38
41
  end
@@ -42,7 +45,10 @@ describe "IO reading methods" do
42
45
  examples.each do |example|
43
46
  it "correctly splits messages for input #{example.inspect}" do
44
47
  io = prepare_io_for.(example)
45
- expected_output = example.gsub("|", "").split(/(?<=\n)/)
48
+ expected_output = example.gsub("|", "").split(/\n/).map { |e| e + "\n" }
49
+ len = expected_output.length
50
+ last = expected_output.last
51
+ expected_output[len-1] = last.chop if !example.end_with?("\n") && last
46
52
  loop do
47
53
  expected_output.shift.should == io.readline
48
54
  rescue EOFError
@@ -57,7 +63,10 @@ describe "IO reading methods" do
57
63
  examples.each do |example|
58
64
  it "correctly splits messages for input #{example.inspect}" do
59
65
  io = prepare_io_for.(example)
60
- expected_output = example.gsub("|", "").split(/(?<=\n)/)
66
+ expected_output = example.gsub("|", "").split(/\n/).map { |e| e + "\n" }
67
+ len = expected_output.length
68
+ last = expected_output.last
69
+ expected_output[len-1] = last.chop if !example.end_with?("\n") && last
61
70
  loop do
62
71
  line = io.gets
63
72
  expected_output.shift.should == line
@@ -8,5 +8,6 @@ when 'deno' then require 'deno/base'
8
8
  when 'nodejs' then require 'nodejs/base'
9
9
  when 'headless-chrome' then require 'headless_browser/base'
10
10
  when 'headless-firefox' then require 'headless_browser/base'
11
+ when 'safari' then require 'headless_browser/base'
11
12
  when 'opal-miniracer' then require 'opal/miniracer'
12
13
  end
@@ -6,6 +6,7 @@ node = `typeof(process) !== "undefined" && process.versions && proce
6
6
  nashorn = `typeof(Java) !== "undefined" && Java.type`
7
7
  headless_chrome = `typeof(opalheadlesschrome) !== "undefined"`
8
8
  headless_firefox = `typeof(opalheadlessfirefox) !== "undefined"`
9
+ safari = `typeof(opalsafari) !== "undefined"`
9
10
  gjs = `typeof(window) !== "undefined" && typeof(GjsFileImporter) !== "undefined"`
10
11
  quickjs = `typeof(window) === "undefined" && typeof(__loadScript) !== "undefined"`
11
12
  opal_miniracer = `typeof(opalminiracer) !== "undefined"`
@@ -20,6 +21,8 @@ OPAL_PLATFORM = if nashorn
20
21
  'headless-chrome'
21
22
  elsif headless_firefox
22
23
  'headless-firefox'
24
+ elsif safari
25
+ 'safari'
23
26
  elsif gjs
24
27
  'gjs'
25
28
  elsif quickjs
data/stdlib/time.rb CHANGED
@@ -1,6 +1,26 @@
1
1
  class Time
2
2
  def self.parse(str)
3
- `new Date(Date.parse(str))`
3
+ %x{
4
+ var d = Date.parse(str);
5
+ if (d !== d) {
6
+ // parsing failed, d is a NaN
7
+ // probably str is not in ISO 8601 format, which is the only format, required to be supported by Javascript
8
+ // try to make the format more like ISO or more like Chrome and parse again
9
+ str = str.replace(/^(\d+)([\./])(\d+)([\./])?(\d+)?/, function(matched_sub, c1, c2, c3, c4, c5, offset, orig_string) {
10
+ if ((c2 === c4) && c5) {
11
+ // 2007.10.1 or 2007/10/1 are ok, but 2007/10.1 is not, convert to 2007-10-1
12
+ return c1 + '-' + c3 + '-' + c5;
13
+ } else if (c3 && !c4) {
14
+ // 2007.10 or 2007/10
15
+ // Chrome and Ruby can parse "2007/10", assuming its "2007-10-01", do the same
16
+ return c1 + '-' + c3 + '-01';
17
+ };
18
+ return matched_sub;
19
+ });
20
+ d = Date.parse(str);
21
+ }
22
+ return new Date(d);
23
+ }
4
24
  end
5
25
 
6
26
  def self.def_formatter(name, format, on_utc: false, utc_tz: nil, tz_format: nil, fractions: false, on: self)
data/tasks/building.rake CHANGED
@@ -38,7 +38,7 @@ task :dist do
38
38
  $stdout.flush
39
39
 
40
40
  # Set requirable to true, unless building opal. This allows opal to be auto-loaded.
41
- requirable = (%w[opal opal/mini opal/base].include? lib)
41
+ requirable = !(%w[opal opal/mini opal/base].include? lib)
42
42
  builder = Opal::Builder.build(lib, requirable: requirable)
43
43
 
44
44
  src = builder.to_s if (formats & %w[js min gz]).any?