Skip to content

Commit f54838b

Browse files
authored
Merge pull request #141 from ruby/staging
The staging branch
2 parents a730ece + e5cf0c8 commit f54838b

22 files changed

Lines changed: 645 additions & 332 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ build/*
33
.idea
44
.ruby-version
55
.sass-cache
6+
tmp
7+
app/application.js

Gemfile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
source "https://rubygems.org"
22

3-
gem "opal", "~> 1.4a"
4-
gem "opal-sprockets"
3+
gem "opal", "~> 1.7"
54
gem "opal-browser"
65
gem "middleman"
76
gem "middleman-livereload"
87
gem "middleman-syntax"
9-
gem "middleman-sprockets"
108
gem "middleman-blog"
119
gem "middleman-gh-pages"
1210
gem "redcarpet"
1311
gem "sass"
1412
gem "webrick"
1513
gem "terser"
14+
gem "tilt", git: "https://github.com/rtomayko/tilt"
1615

1716
group :test do
1817
gem "rspec"

Gemfile.lock

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
GIT
2+
remote: https://github.com/rtomayko/tilt
3+
revision: b6338e5e6d2be7d57f5d6fdfcb8b1161fe222fb3
4+
specs:
5+
tilt (2.0.11)
6+
17
GEM
28
remote: https://rubygems.org/
39
specs:
@@ -103,44 +109,37 @@ GEM
103109
em-websocket (~> 0.5.1)
104110
middleman-core (>= 3.3)
105111
rack-livereload (~> 0.3.15)
106-
middleman-sprockets (4.1.1)
107-
middleman-core (~> 4.0)
108-
sprockets (>= 3.0)
109112
middleman-syntax (3.3.0)
110113
middleman-core (>= 3.2)
111114
rouge (~> 3.2)
112115
mini_mime (1.1.2)
113-
mini_portile2 (2.8.0)
114-
minitest (5.16.3)
116+
mini_portile2 (2.8.1)
117+
minitest (5.17.0)
115118
nio4r (2.5.8)
116119
nokogiri (1.13.10)
117120
mini_portile2 (~> 2.8.0)
118121
racc (~> 1.4)
119-
opal (1.6.0)
122+
opal (1.7.1)
120123
ast (>= 2.3.0)
121124
parser (~> 3.0, >= 3.0.3.2)
122125
opal-browser (0.3.3)
123126
opal (>= 1.0, < 2.0)
124127
paggio (>= 0.3.0)
125-
opal-sprockets (1.0.3)
126-
opal (>= 1.0, < 2.0)
127-
sprockets (~> 4.0)
128-
tilt (>= 1.4)
129-
padrino-helpers (0.15.1)
128+
padrino-helpers (0.15.2)
130129
i18n (>= 0.6.7, < 2)
131-
padrino-support (= 0.15.1)
130+
padrino-support (= 0.15.2)
132131
tilt (>= 1.4.1, < 3)
133-
padrino-support (0.15.1)
132+
padrino-support (0.15.2)
134133
paggio (0.3.0)
135134
parallel (1.22.1)
136-
parser (3.1.3.0)
135+
parser (3.2.0.0)
137136
ast (~> 2.4.1)
138137
parslet (2.0.0)
139-
public_suffix (5.0.0)
140-
puma (6.0.0)
138+
public_suffix (5.0.1)
139+
puma (6.0.2)
141140
nio4r (~> 2.0)
142-
racc (1.6.1)
143-
rack (2.2.4)
141+
racc (1.6.2)
142+
rack (2.2.5)
144143
rack-livereload (0.3.17)
145144
rack
146145
rack-test (2.0.2)
@@ -159,10 +158,10 @@ GEM
159158
rspec-mocks (~> 3.12.0)
160159
rspec-core (3.12.0)
161160
rspec-support (~> 3.12.0)
162-
rspec-expectations (3.12.0)
161+
rspec-expectations (3.12.2)
163162
diff-lcs (>= 1.2.0, < 2.0)
164163
rspec-support (~> 3.12.0)
165-
rspec-mocks (3.12.0)
164+
rspec-mocks (3.12.2)
166165
diff-lcs (>= 1.2.0, < 2.0)
167166
rspec-support (~> 3.12.0)
168167
rspec-support (3.12.0)
@@ -174,14 +173,10 @@ GEM
174173
sassc (2.4.0)
175174
ffi (~> 1.9)
176175
servolux (0.13.0)
177-
sprockets (4.1.1)
178-
concurrent-ruby (~> 1.0)
179-
rack (> 1, < 3)
180176
temple (0.9.1)
181-
terser (1.1.12)
177+
terser (1.1.13)
182178
execjs (>= 0.3.0, < 3)
183179
thor (1.2.1)
184-
tilt (2.0.11)
185180
toml (0.3.0)
186181
parslet (>= 1.8.0, < 3.0.0)
187182
tzinfo (2.0.5)
@@ -205,16 +200,15 @@ DEPENDENCIES
205200
middleman-blog
206201
middleman-gh-pages
207202
middleman-livereload
208-
middleman-sprockets
209203
middleman-syntax
210-
opal (~> 1.4a)
204+
opal (~> 1.7)
211205
opal-browser
212-
opal-sprockets
213206
puma
214207
redcarpet
215208
rspec
216209
sass
217210
terser
211+
tilt!
218212
webrick
219213

220214
BUNDLED WITH

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ only change the files in folder _translations_,
2828
not the json files in folder _source_.
2929
These files will be updated automatically by the build/publish task.
3030

31+
## Frontend website
32+
The TryRuby is kind of unique in a way, that it is a frontend application created
33+
in Ruby with [Opal](https://opalrb.com/). The source code for this application is
34+
located in `app` directory.
35+
3136
## Docker
3237
You can deploy the website using docker by running the following commands:
3338
```

app/dependencies.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require 'opal'
2+
require 'native'
3+
require 'promise/v2'
4+
require 'browser/setup/mini'
5+
require 'browser/cookies'
6+
require 'browser/http'
7+
require 'browser/location'
8+
require 'browser/history'

app/editor.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Wrapper for CodeMirror objects
2+
class Editor
3+
def initialize(dom_id, options)
4+
@native = `CodeMirror(document.getElementById(dom_id), #{options.to_n})`
5+
end
6+
7+
def value=(str)
8+
`#@native.setValue(str)`
9+
end
10+
11+
def value
12+
`#@native.getValue()`
13+
end
14+
15+
def focus
16+
`#@native.focus()`
17+
end
18+
19+
def mark_ok(line_from, line_to)
20+
`#@native.markText({line: line_from, ch: 0}, {line: line_to, ch: 99}, {className: "tryruby-output-green"})`
21+
end
22+
23+
def mark_error(line_from, line_to)
24+
`#@native.markText({line: line_from, ch: 0}, {line: line_to, ch: 99}, {className: "tryruby-output-red"})`
25+
end
26+
27+
def on(event, &block)
28+
`#@native.on(#{event}, #{block})`
29+
end
30+
end

app/lesson.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Container for individual lessons
2+
class Lesson
3+
attr_reader :lang, :step, :title, :chapter, :answer, :ok, :error, :text, :saved_editor, :saved_output
4+
attr_accessor :load_code
5+
6+
def initialize(key, values)
7+
@lang = values["lang"]
8+
@step = key.to_i
9+
@title = values["title"]
10+
@chapter = values["chapter"]
11+
answer = values["answer"]
12+
@answer = answer && !answer.empty? ? Regexp.new(answer, 'mi') : nil
13+
@ok = values["ok"].split('<br/>')
14+
@error = values["error"].split('<br/>')
15+
@text = values["text"]
16+
load_code = values["load_code"]
17+
@load_code = load_code && !load_code.empty? ? load_code : nil
18+
@saved_editor = ''
19+
@saved_output = ''
20+
end
21+
22+
def update_current_edit(current_editor_value, current_output_value)
23+
@saved_editor = current_editor_value
24+
@saved_output = current_output_value
25+
end
26+
end

app/ruby_engine.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# await: true
2+
3+
# require 'ruby_engine/opal'
4+
require 'ruby_engine/opal_webworker'
5+
require 'ruby_engine/cruby_wasi'
6+
7+
class RubyEngine
8+
def run(source)
9+
raise NotImplementedError
10+
end
11+
12+
def run_with_writer(source, writer, &block)
13+
@writer = writer
14+
@dots = 0
15+
run(source, &block).__await__
16+
end
17+
18+
# Display a message while a block is being executed
19+
def loading(part = nil)
20+
# Debug option:
21+
# @writer.output = "*** Loading... #{"(#{part})" if part}"
22+
@dots += 1
23+
@writer.output = "*** Loading#{"." * @dots}"
24+
result = yield.__await__
25+
@writer.output = ""
26+
result
27+
end
28+
29+
# When you update the engines, ensure that they are tested correctly.
30+
# Update the engine list also in spec/playground_spec.
31+
ENGINES = [
32+
# Opal.new,
33+
OpalWebWorker.new("1.7.1"),
34+
CRubyWASI.new(
35+
"https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0-2022-12-25-a/dist/ruby.wasm",
36+
"3.2.0"
37+
),
38+
CRubyWASI.new(
39+
"https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@next/dist/ruby.wasm",
40+
"3.3.0dev"
41+
),
42+
].each_with_object({}) do |engine, hash|
43+
hash[engine.engine_id] = engine
44+
end
45+
end

app/ruby_engine/cruby_wasi.rb

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# await: *await*, loading
2+
3+
require 'await'
4+
5+
class RubyEngine
6+
class CRubyWASI < RubyEngine
7+
REQUIRED_SCRIPTS = [
8+
{
9+
src: "https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0/dist/index.umd.js",
10+
integrity: "sha256-EVJ2qiXD74E5qZpFm9MMNqOf5Buo+P2VQKiqOzOKGpg=",
11+
crossorigin: "anonymous"
12+
},
13+
{
14+
src: "https://cdn.jsdelivr.net/npm/@wasmer/wasmfs@0.12.0/lib/index.iife.js",
15+
integrity: "sha256-sOd4ekxVsN4PXhR+cn/4uNAxeQOJRcsaW5qalYfvkTw=",
16+
crossorigin: "anonymous"
17+
},
18+
{
19+
src: "https://cdn.jsdelivr.net/npm/@wasmer/wasi@0.12.0/lib/index.iife.js",
20+
integrity: "sha256-FslFp/Vq4bDf2GXu+9QyBEDLtEWO3fkMjpyOaJMHJT8=",
21+
crossorigin: "anonymous"
22+
}
23+
]
24+
25+
def initialize(ruby_wasm_url, version)
26+
@ruby_wasm_url = ruby_wasm_url
27+
@version = version
28+
end
29+
30+
def name
31+
"CRuby #{@version}"
32+
end
33+
34+
def engine_id
35+
"cruby-#{@version}"
36+
end
37+
38+
# Below functions will be compiled as async functions
39+
def self.inject_scripts
40+
@injected ||= begin
41+
REQUIRED_SCRIPTS.map do |script|
42+
promise = PromiseV2.new
43+
script = $document.create_element("script", attrs: script)
44+
script.on("load") { promise.resolve }
45+
script.on("error") { promise.reject(StandardError.new("failed to load #{script[:src]}")) }
46+
$document.head << script
47+
promise
48+
end.each_await(&:itself)
49+
true
50+
end
51+
end
52+
53+
def wasm_module
54+
@module ||= begin
55+
response = `fetch(#{@ruby_wasm_url})`.await
56+
buffer = `response.arrayBuffer()`.await
57+
`WebAssembly.compile(buffer)`.await
58+
end
59+
end
60+
61+
def run(source)
62+
`var $WASI, $WasmFs, $RubyVM`
63+
wasmInstance, wasmModule, vm, wasi, imports = nil
64+
65+
loading("downloading scripts") { CRubyWASI.inject_scripts.await }
66+
67+
loading("early load") do
68+
`$WASI = window["WASI"].WASI`
69+
`$WasmFs = window["WasmFs"].WasmFs`
70+
`$RubyVM = window["ruby-wasm-wasi"].RubyVM`
71+
72+
wasmFs = `new $WasmFs()`
73+
originalWriteSync = `wasmFs.fs.writeSync.bind(wasmFs.fs)`
74+
textDecoder = `new TextDecoder("utf-8")`
75+
%x{
76+
wasmFs.fs.writeSync = (fd, buffer, offset, length, position) => {
77+
const text = textDecoder.decode(buffer);
78+
if (fd == 1 || fd == 2) {
79+
#{@writer.print_to_output(`text`, "")};
80+
}
81+
return originalWriteSync(fd, buffer, offset, length, position);
82+
};
83+
}
84+
85+
vm = `new $RubyVM()`
86+
wasi = `new $WASI({
87+
bindings: { ...$WASI.defaultBindings, fs: wasmFs.fs },
88+
})`
89+
imports = `{ wasi_snapshot_preview1: wasi.wasiImport }`
90+
`vm.addToImports(imports)`
91+
end
92+
93+
loading("downloading ruby") do
94+
wasmModule = wasm_module.await
95+
end
96+
97+
loading("instantiating") do
98+
wasmInstance = `WebAssembly.instantiate(wasmModule, imports)`.await
99+
end
100+
101+
loading("initializing") do await
102+
`vm.setInstance(wasmInstance)`.await
103+
`wasi.setMemory(wasmInstance.exports.memory)`
104+
`vm.initialize()`
105+
end
106+
107+
yield `vm.eval(source).toString()`
108+
rescue JS::Error => err
109+
@writer.log_error(err)
110+
end
111+
end
112+
end

0 commit comments

Comments
 (0)