interfacets 0.1.0 → 0.9.9

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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +173 -1
  3. data/LICENSE +21 -0
  4. data/Rakefile +4 -6
  5. data/lib/interfacets/client/actor.rb +20 -0
  6. data/lib/interfacets/client/assets.rb +209 -0
  7. data/lib/interfacets/client/bus.rb +69 -0
  8. data/lib/interfacets/client/channels/api.rb +95 -0
  9. data/lib/interfacets/client/channels/audio.rb +101 -0
  10. data/lib/interfacets/client/channels/base.rb +28 -0
  11. data/lib/interfacets/client/channels/page_visibility.rb +21 -0
  12. data/lib/interfacets/client/channels/react/builder.rb +203 -0
  13. data/lib/interfacets/client/channels/react/channel.rb +61 -0
  14. data/lib/interfacets/client/channels/react/dom.rb +91 -0
  15. data/lib/interfacets/client/channels/react/evaluator.rb +33 -0
  16. data/lib/interfacets/client/channels/speech_to_text.rb +100 -0
  17. data/lib/interfacets/client/channels/timer.rb +51 -0
  18. data/lib/interfacets/client/channels/url.rb +52 -0
  19. data/lib/interfacets/client/config.rb +22 -0
  20. data/lib/interfacets/client/delegator.rb +37 -0
  21. data/lib/interfacets/client/facet.rb +26 -0
  22. data/lib/interfacets/client/facet2.rb +15 -0
  23. data/lib/interfacets/client/facets/attributes/accessor.rb +28 -0
  24. data/lib/interfacets/client/facets/attributes/association.rb +50 -0
  25. data/lib/interfacets/client/facets/attributes/bind.rb +25 -0
  26. data/lib/interfacets/client/facets/attributes/collection.rb +47 -0
  27. data/lib/interfacets/client/facets/attributes/readonly.rb +19 -0
  28. data/lib/interfacets/client/facets/deserializer.rb +30 -0
  29. data/lib/interfacets/client/facets/schema/deserializer.rb +63 -0
  30. data/lib/interfacets/client/facets/schema.rb +63 -0
  31. data/lib/interfacets/client/facets/serializer.rb +18 -0
  32. data/lib/interfacets/client/registry.rb +84 -0
  33. data/lib/interfacets/client/system.rb +88 -0
  34. data/lib/interfacets/client/utils/active_support_concern.rb +220 -0
  35. data/lib/interfacets/client/utils/mruby_patches.rb +81 -0
  36. data/lib/interfacets/client/utils/open_struct.rb +102 -0
  37. data/lib/interfacets/client/utils/securerandom.rb +69 -0
  38. data/lib/interfacets/client/view.rb +47 -0
  39. data/lib/interfacets/client.rb +13 -0
  40. data/lib/interfacets/mruby/build.dockerfile +66 -0
  41. data/lib/interfacets/mruby/build_config.rb +20 -0
  42. data/lib/interfacets/mruby/entrypoint.rb +23 -0
  43. data/lib/interfacets/mruby/init.c +66 -0
  44. data/lib/interfacets/server/api.rb +64 -0
  45. data/lib/interfacets/server/assets/facet.rb +61 -0
  46. data/lib/interfacets/server/assets.rb +210 -0
  47. data/lib/interfacets/server/basic_routable.rb +40 -0
  48. data/lib/interfacets/server/basic_router.rb +74 -0
  49. data/lib/interfacets/server/bus.rb +39 -0
  50. data/lib/interfacets/server/config.rb +87 -0
  51. data/lib/interfacets/server/facet.rb +51 -0
  52. data/lib/interfacets/server/facets/deserializer.rb +25 -0
  53. data/lib/interfacets/server/facets/schema/serializer.rb +54 -0
  54. data/lib/interfacets/server/facets/serializer.rb +50 -0
  55. data/lib/interfacets/server/registry.rb +212 -0
  56. data/lib/interfacets/shared/entities/bus.rb +218 -0
  57. data/lib/interfacets/shared/entities/collection_proxy.rb +190 -0
  58. data/lib/interfacets/shared/entities/specs/handlers.rb +117 -0
  59. data/lib/interfacets/shared/entities/specs.rb +124 -0
  60. data/lib/interfacets/shared/entity.rb +178 -0
  61. data/lib/interfacets/shared/entity_collection.rb +88 -0
  62. data/lib/interfacets/shared/generated_store.rb +145 -0
  63. data/lib/interfacets/shared/utils.rb +54 -0
  64. data/lib/interfacets/shared/validations.rb +71 -0
  65. data/lib/interfacets/test/browser.rb +63 -0
  66. data/lib/interfacets/test/js/inline_bus.rb +91 -0
  67. data/lib/interfacets/test/js/nodo_bus.rb +81 -0
  68. data/lib/interfacets/test/js/receivers/api.rb +37 -0
  69. data/lib/interfacets/test/js/receivers/react/node.rb +167 -0
  70. data/lib/interfacets/test/js/receivers/react.rb +31 -0
  71. data/lib/interfacets/test/js/receivers/url.rb +55 -0
  72. data/lib/interfacets/test.rb +17 -0
  73. data/lib/interfacets/version.rb +1 -1
  74. data/lib/interfacets.rb +26 -2
  75. metadata +103 -6
  76. data/README.md +0 -35
@@ -0,0 +1,220 @@
1
+ # rubocop:disable all
2
+
3
+ module ActiveSupport
4
+ # = Active Support \Concern
5
+ #
6
+ # A typical module looks like this:
7
+ #
8
+ # module M
9
+ # def self.included(base)
10
+ # base.extend ClassMethods
11
+ # base.class_eval do
12
+ # scope :disabled, -> { where(disabled: true) }
13
+ # end
14
+ # end
15
+ #
16
+ # module ClassMethods
17
+ # ...
18
+ # end
19
+ # end
20
+ #
21
+ # By using +ActiveSupport::Concern+ the above module could instead be
22
+ # written as:
23
+ #
24
+ # # require "active_support/concern"
25
+ #
26
+ # module M
27
+ # extend ActiveSupport::Concern
28
+ #
29
+ # included do
30
+ # scope :disabled, -> { where(disabled: true) }
31
+ # end
32
+ #
33
+ # class_methods do
34
+ # ...
35
+ # end
36
+ # end
37
+ #
38
+ # Moreover, it gracefully handles module dependencies. Given a +Foo+ module
39
+ # and a +Bar+ module which depends on the former, we would typically write the
40
+ # following:
41
+ #
42
+ # module Foo
43
+ # def self.included(base)
44
+ # base.class_eval do
45
+ # def self.method_injected_by_foo
46
+ # ...
47
+ # end
48
+ # end
49
+ # end
50
+ # end
51
+ #
52
+ # module Bar
53
+ # def self.included(base)
54
+ # base.method_injected_by_foo
55
+ # end
56
+ # end
57
+ #
58
+ # class Host
59
+ # include Foo # We need to include this dependency for Bar
60
+ # include Bar # Bar is the module that Host really needs
61
+ # end
62
+ #
63
+ # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
64
+ # could try to hide these from +Host+ directly including +Foo+ in +Bar+:
65
+ #
66
+ # module Bar
67
+ # include Foo
68
+ # def self.included(base)
69
+ # base.method_injected_by_foo
70
+ # end
71
+ # end
72
+ #
73
+ # class Host
74
+ # include Bar
75
+ # end
76
+ #
77
+ # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
78
+ # is the +Bar+ module, not the +Host+ class. With +ActiveSupport::Concern+,
79
+ # module dependencies are properly resolved:
80
+ #
81
+ # # require "active_support/concern"
82
+ #
83
+ # module Foo
84
+ # extend ActiveSupport::Concern
85
+ # included do
86
+ # def self.method_injected_by_foo
87
+ # ...
88
+ # end
89
+ # end
90
+ # end
91
+ #
92
+ # module Bar
93
+ # extend ActiveSupport::Concern
94
+ # include Foo
95
+ #
96
+ # included do
97
+ # self.method_injected_by_foo
98
+ # end
99
+ # end
100
+ #
101
+ # class Host
102
+ # include Bar # It works, now Bar takes care of its dependencies
103
+ # end
104
+ #
105
+ # === Prepending concerns
106
+ #
107
+ # Just like <tt>include</tt>, concerns also support <tt>prepend</tt> with a corresponding
108
+ # <tt>prepended do</tt> callback. <tt>module ClassMethods</tt> or <tt>class_methods do</tt> are
109
+ # prepended as well.
110
+ #
111
+ # <tt>prepend</tt> is also used for any dependencies.
112
+ module Concern
113
+ class MultipleIncludedBlocks < StandardError # :nodoc:
114
+ def initialize
115
+ super "Cannot define multiple 'included' blocks for a Concern"
116
+ end
117
+ end
118
+
119
+ class MultiplePrependBlocks < StandardError # :nodoc:
120
+ def initialize
121
+ super "Cannot define multiple 'prepended' blocks for a Concern"
122
+ end
123
+ end
124
+
125
+ def self.extended(base) # :nodoc:
126
+ base.instance_variable_set(:@_dependencies, [])
127
+ end
128
+
129
+ def append_features(base) # :nodoc:
130
+ if base.instance_variable_defined?(:@_dependencies)
131
+ base.instance_variable_get(:@_dependencies) << self
132
+ false
133
+ else
134
+ return false if base < self
135
+ @_dependencies.each { |dep| base.include(dep) }
136
+ Interfacets::Client::Assets.loader.load(self)
137
+ super
138
+ base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
139
+ base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block)
140
+ end
141
+ end
142
+
143
+ def prepend_features(base) # :nodoc:
144
+ if base.instance_variable_defined?(:@_dependencies)
145
+ base.instance_variable_get(:@_dependencies).unshift self
146
+ false
147
+ else
148
+ return false if base < self
149
+ @_dependencies.each { |dep| base.prepend(dep) }
150
+ super
151
+ base.singleton_class.prepend const_get(:ClassMethods) if const_defined?(:ClassMethods)
152
+ base.class_eval(&@_prepended_block) if instance_variable_defined?(:@_prepended_block)
153
+ end
154
+ end
155
+
156
+ # Evaluate given block in context of base class,
157
+ # so that you can write class macros here.
158
+ # When you define more than one +included+ block, it raises an exception.
159
+ def included(base = nil, &block)
160
+ if base.nil?
161
+ if instance_variable_defined?(:@_included_block)
162
+ if @_included_block.source_location != block.source_location
163
+ raise MultipleIncludedBlocks
164
+ end
165
+ else
166
+ @_included_block = block
167
+ end
168
+ else
169
+ super
170
+ end
171
+ end
172
+
173
+ # Evaluate given block in context of base class,
174
+ # so that you can write class macros here.
175
+ # When you define more than one +prepended+ block, it raises an exception.
176
+ def prepended(base = nil, &block)
177
+ if base.nil?
178
+ if instance_variable_defined?(:@_prepended_block)
179
+ if @_prepended_block.source_location != block.source_location
180
+ raise MultiplePrependBlocks
181
+ end
182
+ else
183
+ @_prepended_block = block
184
+ end
185
+ else
186
+ super
187
+ end
188
+ end
189
+
190
+ # Define class methods from given block.
191
+ # You can define private class methods as well.
192
+ #
193
+ # module Example
194
+ # extend ActiveSupport::Concern
195
+ #
196
+ # class_methods do
197
+ # def foo; puts 'foo'; end
198
+ #
199
+ # private
200
+ # def bar; puts 'bar'; end
201
+ # end
202
+ # end
203
+ #
204
+ # class Buzz
205
+ # include Example
206
+ # end
207
+ #
208
+ # Buzz.foo # => "foo"
209
+ # Buzz.bar # => private method 'bar' called for Buzz:Class(NoMethodError)
210
+ def class_methods(&class_methods_module_definition)
211
+ mod = const_defined?(:ClassMethods, false) ?
212
+ const_get(:ClassMethods) :
213
+ const_set(:ClassMethods, Module.new)
214
+
215
+ mod.module_eval(&class_methods_module_definition)
216
+ end
217
+ end
218
+ end
219
+
220
+ # rubocop:enable all
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ module Utils
6
+ module MrubyPatches
7
+ module HashExt
8
+ NO_DEFAULT = Object.new
9
+
10
+ def fetch(k, default = NO_DEFAULT)
11
+ if key?(k)
12
+ self[k]
13
+ elsif block_given?
14
+ yield
15
+ elsif default == NO_DEFAULT
16
+ raise("#{k} not found in #{inspect}")
17
+ else
18
+ default
19
+ end
20
+ end
21
+ end
22
+
23
+ module InspectPatch
24
+ def inspect(...)
25
+ "<#{self.class}:#{self.object_id}>"
26
+ end
27
+ end
28
+
29
+ module ArrayPatch
30
+ def index_by
31
+ raise(ArgumentError) unless block_given?
32
+
33
+ result = {}
34
+ each { |elem| result[yield(elem)] = elem }
35
+ result
36
+ end
37
+ end
38
+
39
+ module ObjectPresence
40
+ def blank?
41
+ false
42
+ end
43
+
44
+ def present?
45
+ !blank?
46
+ end
47
+ end
48
+
49
+ module EnumerablePresence
50
+ def blank?
51
+ empty?
52
+ end
53
+ end
54
+
55
+ module FalsyPresence
56
+ def blank?
57
+ true
58
+ end
59
+ end
60
+
61
+ module StringPresence
62
+ def blank?
63
+ match?(/^\s*$/)
64
+ end
65
+ end
66
+
67
+ if RUBY_ENGINE == "mruby"
68
+ Object.prepend(InspectPatch)
69
+ Hash.prepend(HashExt)
70
+ Array.prepend(ArrayPatch)
71
+
72
+ Object.include(ObjectPresence)
73
+ Enumerable.include(EnumerablePresence)
74
+ NilClass.include(FalsyPresence)
75
+ FalseClass.include(FalsyPresence)
76
+ String.include(StringPresence)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,102 @@
1
+ # rubocop:disable all
2
+
3
+ # copied from github
4
+
5
+ class OpenStruct
6
+ def initialize hash=nil
7
+ @table = {}
8
+ if hash
9
+ hash.each do |k,v|
10
+ k = k.to_sym
11
+ @table[k] = v
12
+ new_ostruct_member(k)
13
+ end
14
+ end
15
+ end
16
+
17
+ def initialize_copy orig
18
+ super
19
+ @table = @table.dup
20
+ @table.each_key{|key| new_ostruct_member(key)}
21
+ end
22
+
23
+ def to_h
24
+ @table.dup
25
+ end
26
+
27
+ def each_pair
28
+ return to_enum __method__ unless block_given?
29
+ @table.each{|p| yield p}
30
+ end
31
+
32
+ def new_ostruct_member name
33
+ name = name.to_sym
34
+ unless respond_to?(name)
35
+ define_singleton_method(name){ @table[name] }
36
+ define_singleton_method("#{name}=".to_sym){ |x| @table[name] = x }
37
+ end
38
+ name
39
+ end
40
+ protected :new_ostruct_member
41
+
42
+ def inspect
43
+ str = "#<#{self.class}"
44
+ ary = []
45
+ @table.each do |k,v|
46
+ ary << "#{k}=#{v}"
47
+ end
48
+ if 0 < ary.length
49
+ str << ' '
50
+ str << ary.join(', ')
51
+ end
52
+ str << '>'
53
+ end
54
+ alias :to_s :inspect
55
+
56
+ def delete_field name
57
+ sym = name.to_sym
58
+ singleton_class.__send__ :remove_method, sym, "#{sym}=".to_sym
59
+ @table.delete sym
60
+ end
61
+
62
+ def == other
63
+ return false unless other.kind_of?(OpenStruct)
64
+ @table == other.table
65
+ end
66
+
67
+ def [] key
68
+ @table[key.to_sym]
69
+ end
70
+
71
+ def []= key, value
72
+ @table[new_ostruct_member(key)] = value
73
+ end
74
+
75
+ def eql? other
76
+ return false unless other.kind_of?(OpenStruct)
77
+ @table.eql?(other.table)
78
+ end
79
+
80
+ def hash
81
+ @table.hash
82
+ end
83
+
84
+ attr_reader :table
85
+ protected :table
86
+
87
+ def method_missing mid, *args
88
+ mname = mid.to_s
89
+ len = args.length
90
+ if mname.chomp! '='
91
+ if len != 1
92
+ raise ArgumentError, "wrong number of arguments (#{len} for 1)"
93
+ end
94
+ @table[new_ostruct_member(mname)] = args[0]
95
+ elsif len == 0
96
+ @table[mid]
97
+ else
98
+ raise NoMethodError, "undefined method `#{mid}' for #{self}"
99
+ end
100
+ end
101
+ end
102
+ # rubocop:enable all
@@ -0,0 +1,69 @@
1
+ # rubocop:disable all
2
+
3
+ if RUBY_ENGINE == "mruby"
4
+
5
+ class SecureRandom
6
+ class << self
7
+ def random_bytes(n=nil)
8
+ n = n ? n.to_int : 16
9
+
10
+ # Need to read more than the number of bytes because sometimes it comes
11
+ # back as less. Not sure why, but whatever.
12
+ begin
13
+ File.open("/dev/urandom", 'r') {|f|
14
+ ret = f.read(n*2)
15
+ unless ret.length > n
16
+ raise NotImplementedError, "Unexpected partial read from random device: only #{ret.length} for #{n} bytes"
17
+ end
18
+ return ret[0..n]
19
+ }
20
+ rescue
21
+ raise File::NoFileError, "No random device"
22
+ end
23
+
24
+ raise NotImplementedError, "No random device"
25
+ end
26
+
27
+ def hex(n=nil)
28
+ random_bytes(n).unpack("H*")[0]
29
+ end
30
+
31
+ def base64(n=nil)
32
+ [random_bytes(n)].pack("m*").gsub("\n", "")
33
+ end
34
+
35
+ def urlsafe_base64(n=nil, padding=false)
36
+ s = base64.gsub("+", "-").gsub("/", "_")
37
+ s.gsub!("=", "") unless padding
38
+ s
39
+ end
40
+
41
+ def random_number(n=0)
42
+ if 0 < n
43
+ hex = n.to_s(16)
44
+ hex = '0' + hex if (hex.length & 1) == 1
45
+ bin = [hex].pack("H*")
46
+ mask = bin[0].ord
47
+ mask |= mask >> 1
48
+ mask |= mask >> 2
49
+ mask |= mask >> 4
50
+
51
+ loop do
52
+ rnd = random_bytes(bin.length)
53
+ rnd[0] = (rnd[0].ord & mask).chr
54
+ return rnd.unpack("H*")[0].hex if rnd < bin
55
+ end
56
+ else
57
+ raise NotImplementedError
58
+ end
59
+ end
60
+
61
+ def uuid
62
+ ary = random_bytes(16).unpack("nnnnnnnn")
63
+ ary[3] = (ary[3] & 0x0fff) | 0x4000
64
+ ary[4] = (ary[4] & 0x3fff) | 0x8000
65
+ "%04x%04x-%04x-%04x-%04x-%04x%04x%04x" % ary
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ class View
6
+ class Evaluator
7
+ def self.call(...)
8
+ new(...).call
9
+ end
10
+
11
+ attr_reader :entity, :channels, :block
12
+ def initialize(entity:, channels:, block:)
13
+ @entity = entity
14
+ @channels = channels
15
+ @block = block
16
+ @data = Hash.new { |h, k| h[k] = { streams: {} } }
17
+ end
18
+
19
+ def call
20
+ instance_exec(entity, &@block)
21
+ end
22
+
23
+ def render(channel_id, stream: "default", &block)
24
+ channels
25
+ .fetch(channel_id.to_s)
26
+ .builder(stream.to_s)
27
+ .then { instance_exec(_1, &block) }
28
+ end
29
+
30
+ def channel(name)
31
+ @channels.fetch(name)
32
+ end
33
+ end
34
+
35
+ class << self
36
+ def view(&block)
37
+ @view = block if block_given?
38
+ @view
39
+ end
40
+ end
41
+
42
+ def render(entity:, channels:)
43
+ Evaluator.call(entity: entity, channels:, block: self.class.view)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Interfacets
4
+ module Client
5
+ def self.start(...)
6
+ @system = System.start(...)
7
+ end
8
+
9
+ def self.system
10
+ @system
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,66 @@
1
+ FROM emscripten/emsdk:3.1.62
2
+
3
+ RUN apt update
4
+ RUN apt install -y vim git
5
+ RUN apt install -y ruby
6
+ RUN git clone https://github.com/mruby/mruby.git /home/root/interfacets/mruby
7
+ WORKDIR /home/root/interfacets/mruby
8
+ # RUN git fetch origin master && git reset --hard origin/master
9
+ RUN git fetch origin --tags && git reset --hard 3.4.0
10
+ RUN rake all test
11
+ ENV PATH="/home/root/interfacets/mruby/bin:${PATH}"
12
+ COPY ./lib/interfacets/mruby/build_config.rb \
13
+ /home/root/interfacets/
14
+ WORKDIR /home/root/interfacets
15
+ RUN cd mruby && \
16
+ sed -i '/MRB_STR_LENGTH_MAX 1048576/c#define MRB_STR_LENGTH_MAX 0' src/string.c && \
17
+ MRUBY_CONFIG=../build_config.rb rake
18
+ RUN mkdir -p assets/mruby build
19
+ RUN cp -r mruby/include assets/mruby \
20
+ && cp mruby/build/emscripten/lib/libmruby.a assets/mruby
21
+ COPY ./lib/interfacets/mruby/init.c \
22
+ ./lib/interfacets/mruby/entrypoint.rb \
23
+ /home/root/interfacets/
24
+ # RUN mrbc --help
25
+ RUN mrbc -B ruby_app -o build/app.c entrypoint.rb
26
+ RUN echo "\n" >> build/app.c && cat init.c >> build/app.c
27
+ RUN emcc \
28
+ -s EXPORT_NAME=MRuby \
29
+ -s EXPORTED_FUNCTIONS='_ruby_eval,_main,_ruby_call,stringToNewUTF8' \
30
+ -s ENVIRONMENT=web \
31
+ -s MODULARIZE=1 \
32
+ -s EXPORT_ES6=1 \
33
+ -s ALLOW_MEMORY_GROWTH=1 \
34
+ -s SINGLE_FILE=1 \
35
+ -I ./assets/mruby/include \
36
+ build/app.c \
37
+ ./assets/mruby/libmruby.a \
38
+ -o build/ruby.js
39
+
40
+ RUN emcc \
41
+ -s EXPORT_NAME=MRuby \
42
+ # -s EXPORTED_FUNCTIONS='_ruby_eval,_main,_ruby_call,stringToNewUTF8' \
43
+ -s ENVIRONMENT=node \
44
+ -s MODULARIZE=1 \
45
+ -s EXPORT_ES6=1 \
46
+ -s SIDE_MODULE=1 \
47
+ -s ALLOW_MEMORY_GROWTH=1 \
48
+ -I ./assets/mruby/include \
49
+ build/app.c \
50
+ ./assets/mruby/libmruby.a \
51
+ -o build/test.wasm
52
+
53
+ RUN emcc \
54
+ -s EXPORT_NAME=MRuby \
55
+ -s EXPORTED_FUNCTIONS='_ruby_eval,_main,_ruby_call,stringToNewUTF8' \
56
+ -s ENVIRONMENT=node \
57
+ -s MODULARIZE=1 \
58
+ -s EXPORT_ES6=1 \
59
+ -s MAIN_MODULE=1 \
60
+ -s ALLOW_MEMORY_GROWTH=1 \
61
+ -I ./assets/mruby/include \
62
+ build/app.c \
63
+ ./assets/mruby/libmruby.a \
64
+ -o build/test.js
65
+
66
+ RUN mv build /build
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ MRuby::Build.new do |conf|
4
+ toolchain :gcc
5
+ conf.gembox("full-core")
6
+ end
7
+
8
+ MRuby::CrossBuild.new("emscripten") do |conf|
9
+ toolchain :clang
10
+ conf.gembox("full-core")
11
+ conf.cc.command = "emcc"
12
+ conf.cc.flags = ["-Os", "-fPIC"]
13
+ conf.linker.command = "emcc"
14
+ conf.archiver.command = "emar"
15
+
16
+ conf.gem(github: "mattn/mruby-json")
17
+ conf.gem(github: "mattn/mruby-base64")
18
+ conf.gem(github: "monochromegane/mruby-secure-random")
19
+ conf.gem(github: "iij/mruby-regexp-pcre")
20
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kernel
4
+ def wrapped_eval(str, path)
5
+ eval(str, binding, path)
6
+ rescue StandardError, SyntaxError => e
7
+ $stderr.puts(e.class)
8
+ $stderr.puts(e.message)
9
+ $stderr.puts(e.backtrace.join("\n"))
10
+ end
11
+
12
+ def wrapped_call(json_string)
13
+ spec = JSON.parse(json_string)
14
+ eval(spec.fetch("receiver")).send(
15
+ spec.fetch("method"),
16
+ *spec.fetch("args", []),
17
+ )
18
+ rescue StandardError, SyntaxError => e
19
+ $stderr.puts(e.class)
20
+ $stderr.puts(e.message)
21
+ $stderr.puts(e.backtrace.join("\n"))
22
+ end
23
+ end
@@ -0,0 +1,66 @@
1
+ #include <mruby.h>
2
+ #include <mruby/irep.h>
3
+ #include <emscripten.h>
4
+
5
+ mrb_value js_eval(mrb_state* mrb, mrb_value self)
6
+ {
7
+ char *js_code;
8
+ mrb_get_args(mrb, "z", &js_code);
9
+ emscripten_run_script(js_code);
10
+ return self;
11
+ }
12
+
13
+ mrb_state *mrb;
14
+ void EMSCRIPTEN_KEEPALIVE ruby_eval(char* str, char* path) {
15
+ int ai = mrb_gc_arena_save(mrb);
16
+ mrb_funcall(
17
+ mrb,
18
+ mrb_top_self(mrb),
19
+ "wrapped_eval",
20
+ 2,
21
+ mrb_str_new_cstr(mrb, str),
22
+ mrb_str_new_cstr(mrb, path)
23
+ );
24
+
25
+ if (mrb->exc) mrb_print_error(mrb);
26
+ mrb_gc_arena_restore(mrb, ai);
27
+ }
28
+
29
+ // json of the form:
30
+ // {receiver: , method:, args: [ ...] ]
31
+ void EMSCRIPTEN_KEEPALIVE ruby_call(char* json) {
32
+ int ai = mrb_gc_arena_save(mrb);
33
+
34
+ mrb_funcall(
35
+ mrb,
36
+ mrb_top_self(mrb),
37
+ "wrapped_call",
38
+ 1,
39
+ mrb_str_new_cstr(mrb, json)
40
+ );
41
+
42
+ if (mrb->exc) mrb_print_error(mrb);
43
+ mrb_gc_arena_restore(mrb, ai);
44
+ }
45
+
46
+ int main() {
47
+ // Leave this open for all eternity
48
+ mrb = mrb_open();
49
+
50
+ if (!mrb) { /* handle error */ }
51
+
52
+ mrb_define_method(
53
+ mrb,
54
+ mrb->kernel_module,
55
+ "js_eval",
56
+ js_eval,
57
+ MRB_ARGS_REQ(1)
58
+ );
59
+
60
+ mrb_load_irep(mrb, ruby_app);
61
+
62
+ // If an exception, print error
63
+ if (mrb->exc) mrb_print_error(mrb);
64
+
65
+ return 0;
66
+ }