Skip to content

Commit 931f6b1

Browse files
committed
Refactor the application structure; split classes into separate files
1 parent a730ece commit 931f6b1

File tree

12 files changed

+194
-188
lines changed

12 files changed

+194
-188
lines changed

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 'opal/full'
3+
require 'opal-parser'
4+
require 'native'
5+
require 'promise/v2'
6+
require 'browser/setup/full'
7+
require 'browser/cookies'
8+
require 'browser/form_data'

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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
require 'ruby_engine/opal'
2+
require 'ruby_engine/cruby_wasi'
3+
4+
class RubyEngine
5+
def run(source, instance)
6+
raise NotImplementedError
7+
end
8+
9+
ENGINES = [
10+
Opal.new,
11+
CRubyWASI.new(
12+
"https://cdn.jsdelivr.net/npm/ruby-wasm-wasi@0.1.2/dist/ruby.wasm",
13+
"3.2.0dev"
14+
),
15+
].each_with_object({}) do |engine, hash|
16+
hash[engine.engine_id] = engine
17+
end
18+
end

app/ruby_engine/cruby_wasi.rb

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
class RubyEngine
2+
class CRubyWASI < RubyEngine
3+
def initialize(ruby_wasm_url, version)
4+
@ruby_wasm_url = ruby_wasm_url
5+
@version = version
6+
end
7+
8+
def name
9+
"CRuby #{@version}"
10+
end
11+
12+
def engine_id
13+
"cruby-#{@version}"
14+
end
15+
16+
def wasm_module
17+
return @module if @module
18+
%x{
19+
#{@module} = (async function() {
20+
const response = await fetch(#{@ruby_wasm_url});
21+
const buffer = await response.arrayBuffer();
22+
return await WebAssembly.compile(buffer);
23+
})();
24+
}
25+
@module
26+
end
27+
28+
def run(source, writer)
29+
%x{
30+
async function instantiateVM() {
31+
const $WASI = window["WASI"].WASI;
32+
const $WasmFs = window["WasmFs"].WasmFs;
33+
const $RubyVM = window["ruby-wasm-wasi"].RubyVM;
34+
35+
const wasmFs = new $WasmFs();
36+
const originalWriteSync = wasmFs.fs.writeSync.bind(wasmFs.fs);
37+
const textDecoder = new TextDecoder("utf-8");
38+
wasmFs.fs.writeSync = (fd, buffer, offset, length, position) => {
39+
const text = textDecoder.decode(buffer);
40+
if (fd == 1 || fd == 2) {
41+
#{writer.print_to_output(`text`, "")};
42+
}
43+
return originalWriteSync(fd, buffer, offset, length, position);
44+
};
45+
46+
const vm = new $RubyVM();
47+
const wasi = new $WASI({
48+
bindings: { ...$WASI.defaultBindings, fs: wasmFs.fs },
49+
});
50+
const imports = { wasi_snapshot_preview1: wasi.wasiImport };
51+
vm.addToImports(imports);
52+
const wasmInstance = await WebAssembly.instantiate(await #{wasm_module}, imports);
53+
await vm.setInstance(wasmInstance);
54+
wasi.setMemory(wasmInstance.exports.memory);
55+
vm.initialize();
56+
return vm;
57+
}
58+
59+
instantiateVM()
60+
.then((vm) => { #{yield `vm.eval(source).toString()`} })
61+
.catch((err) => { #{writer.log_error(`err`)} })
62+
}
63+
end
64+
end
65+
end

app/ruby_engine/opal.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
class RubyEngine
2+
class Opal < RubyEngine
3+
def name
4+
"Opal #{Opal::VERSION}"
5+
end
6+
7+
def engine_id
8+
"opal"
9+
end
10+
11+
def run(source, writer)
12+
# Compile
13+
js_code = ::Opal.compile(source)
14+
15+
# Bind puts and print methods.
16+
$stdout.write_proc = $stderr.write_proc = ->(str) do
17+
writer.print_to_output str, ""
18+
end
19+
20+
# Run
21+
retval = nil
22+
error = nil
23+
24+
retval = `eval(js_code)`
25+
yield(retval ? retval.to_s : '')
26+
$stdout.write_proc = $stderr.write_proc = nil
27+
end
28+
end
29+
end

source/javascripts/try_ruby.js.rb renamed to app/try_ruby.rb

Lines changed: 6 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -1,178 +1,7 @@
1-
require 'opal'
2-
require 'opal/full'
3-
require 'opal-parser'
4-
require 'native'
5-
require 'promise/v2'
6-
require 'browser/setup/full'
7-
require 'browser/cookies'
8-
require 'browser/form_data'
9-
10-
# Container for individual lessons
11-
class TryRubyItem
12-
attr_reader :lang, :step, :title, :chapter, :answer, :ok, :error, :text, :saved_editor, :saved_output
13-
attr_accessor :load_code
14-
15-
def initialize(key, values)
16-
@lang = values["lang"]
17-
@step = key.to_i
18-
@title = values["title"]
19-
@chapter = values["chapter"]
20-
answer = values["answer"]
21-
@answer = answer && !answer.empty? ? Regexp.new(answer, 'mi') : nil
22-
@ok = values["ok"].split('<br/>')
23-
@error = values["error"].split('<br/>')
24-
@text = values["text"]
25-
load_code = values["load_code"]
26-
@load_code = load_code && !load_code.empty? ? load_code : nil
27-
@saved_editor = ''
28-
@saved_output = ''
29-
end
30-
31-
def update_current_edit(current_editor_value, current_output_value)
32-
@saved_editor = current_editor_value
33-
@saved_output = current_output_value
34-
end
35-
end
36-
37-
# Wrapper for CodeMirror objects
38-
class Editor
39-
def initialize(dom_id, options)
40-
@native = `CodeMirror(document.getElementById(dom_id), #{options.to_n})`
41-
end
42-
43-
def value=(str)
44-
`#@native.setValue(str)`
45-
end
46-
47-
def value
48-
`#@native.getValue()`
49-
end
50-
51-
def focus
52-
`#@native.focus()`
53-
end
54-
55-
def mark_ok(line_from, line_to)
56-
`#@native.markText({line: line_from, ch: 0}, {line: line_to, ch: 99}, {className: "tryruby-output-green"})`
57-
end
58-
59-
def mark_error(line_from, line_to)
60-
`#@native.markText({line: line_from, ch: 0}, {line: line_to, ch: 99}, {className: "tryruby-output-red"})`
61-
end
62-
63-
def on(event, &block)
64-
`#@native.on(#{event}, #{block})`
65-
end
66-
end
67-
68-
class RubyEngine
69-
def run(source, instance)
70-
end
71-
72-
class OpalEngine < RubyEngine
73-
def name
74-
"Opal #{Opal::VERSION}"
75-
end
76-
77-
def engine_id
78-
"opal"
79-
end
80-
81-
def run(source, writer)
82-
# Compile
83-
js_code = Opal.compile(source)
84-
85-
# Bind puts and print methods.
86-
$stdout.write_proc = $stderr.write_proc = ->(str) do
87-
writer.print_to_output str, ""
88-
end
89-
90-
# Run
91-
retval = nil
92-
error = nil
93-
94-
retval = `eval(js_code)`
95-
yield(retval ? retval.to_s : '')
96-
$stdout.write_proc = $stderr.write_proc = nil
97-
end
98-
end
99-
100-
class CRubyEngine < RubyEngine
101-
102-
def initialize(ruby_wasm_url, version)
103-
@ruby_wasm_url = ruby_wasm_url
104-
@version = version
105-
end
106-
107-
def name
108-
"CRuby #{@version}"
109-
end
110-
111-
def engine_id
112-
"cruby-#{@version}"
113-
end
114-
115-
def wasm_module
116-
return @module if @module
117-
%x{
118-
#{@module} = (async function() {
119-
const response = await fetch(#{@ruby_wasm_url});
120-
const buffer = await response.arrayBuffer();
121-
return await WebAssembly.compile(buffer);
122-
})();
123-
}
124-
@module
125-
end
126-
127-
def run(source, writer)
128-
%x{
129-
async function instantiateVM() {
130-
const $WASI = window["WASI"].WASI;
131-
const $WasmFs = window["WasmFs"].WasmFs;
132-
const $RubyVM = window["ruby-wasm-wasi"].RubyVM;
133-
134-
const wasmFs = new $WasmFs();
135-
const originalWriteSync = wasmFs.fs.writeSync.bind(wasmFs.fs);
136-
const textDecoder = new TextDecoder("utf-8");
137-
wasmFs.fs.writeSync = (fd, buffer, offset, length, position) => {
138-
const text = textDecoder.decode(buffer);
139-
if (fd == 1 || fd == 2) {
140-
#{writer.print_to_output(`text`, "")};
141-
}
142-
return originalWriteSync(fd, buffer, offset, length, position);
143-
};
144-
145-
const vm = new $RubyVM();
146-
const wasi = new $WASI({
147-
bindings: { ...$WASI.defaultBindings, fs: wasmFs.fs },
148-
});
149-
const imports = { wasi_snapshot_preview1: wasi.wasiImport };
150-
vm.addToImports(imports);
151-
const wasmInstance = await WebAssembly.instantiate(await #{wasm_module}, imports);
152-
await vm.setInstance(wasmInstance);
153-
wasi.setMemory(wasmInstance.exports.memory);
154-
vm.initialize();
155-
return vm;
156-
}
157-
158-
instantiateVM()
159-
.then((vm) => { #{yield `vm.eval(source).toString()`} })
160-
.catch((err) => { #{writer.log_error(`err`)} })
161-
}
162-
end
163-
end
164-
165-
ENGINES = [
166-
OpalEngine.new,
167-
CRubyEngine.new(
168-
"https://cdn.jsdelivr.net/npm/ruby-wasm-wasi@0.1.2/dist/ruby.wasm",
169-
"3.2.0dev"
170-
),
171-
].each_with_object({}) do |engine, hash|
172-
hash[engine.engine_id] = engine
173-
end
174-
end
175-
1+
require 'dependencies'
2+
require 'editor'
3+
require 'lesson'
4+
require 'ruby_engine'
1765

1776
# The TryRuby application
1787
class TryRuby
@@ -318,7 +147,7 @@ def get_content_from_server(language)
318147
def update_json(items)
319148
@items = {}
320149
items.each do |k, v|
321-
@items[k.to_i] = TryRubyItem.new(k, v)
150+
@items[k.to_i] = Lesson.new(k, v)
322151
end
323152
@loaded = true
324153

@@ -601,4 +430,4 @@ def print_to_output(str, term = "\n")
601430
end
602431
end
603432

604-
TryRuby.start
433+
$window.on("dom:load") { TryRuby.start }

0 commit comments

Comments
 (0)