interfacets 0.1.0 → 0.9.99
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +173 -1
- data/.tmp +5 -0
- data/LICENSE +21 -0
- data/Rakefile +9 -7
- data/lib/interfacets/client/actor.rb +20 -0
- data/lib/interfacets/client/assets.rb +210 -0
- data/lib/interfacets/client/bus.rb +73 -0
- data/lib/interfacets/client/channels/api.rb +102 -0
- data/lib/interfacets/client/channels/audio.rb +101 -0
- data/lib/interfacets/client/channels/base.rb +28 -0
- data/lib/interfacets/client/channels/page_visibility.rb +21 -0
- data/lib/interfacets/client/channels/react/builder.rb +203 -0
- data/lib/interfacets/client/channels/react/channel.rb +61 -0
- data/lib/interfacets/client/channels/react/dom.rb +91 -0
- data/lib/interfacets/client/channels/react/evaluator.rb +33 -0
- data/lib/interfacets/client/channels/speech_to_text.rb +100 -0
- data/lib/interfacets/client/channels/timer.rb +51 -0
- data/lib/interfacets/client/channels/url.rb +52 -0
- data/lib/interfacets/client/config.rb +22 -0
- data/lib/interfacets/client/delegator.rb +37 -0
- data/lib/interfacets/client/registry.rb +23 -0
- data/lib/interfacets/client/system.rb +88 -0
- data/lib/interfacets/client/utils/active_support_concern.rb +220 -0
- data/lib/interfacets/client/utils/mruby_patches.rb +81 -0
- data/lib/interfacets/client/utils/open_struct.rb +115 -0
- data/lib/interfacets/client/utils/securerandom.rb +69 -0
- data/lib/interfacets/client.rb +13 -0
- data/lib/interfacets/component_registry.rb +115 -0
- data/lib/interfacets/component_schema_parser.rb +84 -0
- data/lib/interfacets/mruby/build.dockerfile +66 -0
- data/lib/interfacets/mruby/build_config.rb +20 -0
- data/lib/interfacets/mruby/entrypoint.rb +23 -0
- data/lib/interfacets/mruby/init.c +66 -0
- data/lib/interfacets/server/api.rb +44 -0
- data/lib/interfacets/server/assets/facet.rb +63 -0
- data/lib/interfacets/server/assets.rb +216 -0
- data/lib/interfacets/server/basic_router.rb +79 -0
- data/lib/interfacets/server/bus.rb +34 -0
- data/lib/interfacets/server/config.rb +87 -0
- data/lib/interfacets/server/facets/deserializer.rb +25 -0
- data/lib/interfacets/server/facets/schema/serializer.rb +54 -0
- data/lib/interfacets/server/facets/serializer.rb +50 -0
- data/lib/interfacets/server/registry.rb +51 -0
- data/lib/interfacets/shared/basic_routable.rb +45 -0
- data/lib/interfacets/shared/entities/bus.rb +230 -0
- data/lib/interfacets/shared/entities/collection_proxy.rb +190 -0
- data/lib/interfacets/shared/entities/specs/handlers.rb +133 -0
- data/lib/interfacets/shared/entities/specs.rb +161 -0
- data/lib/interfacets/shared/entity.rb +102 -0
- data/lib/interfacets/shared/entity_dsl.rb +154 -0
- data/lib/interfacets/shared/facet.rb +200 -0
- data/lib/interfacets/shared/generated_store.rb +149 -0
- data/lib/interfacets/shared/utils.rb +54 -0
- data/lib/interfacets/shared/validations.rb +75 -0
- data/lib/interfacets/shared/view.rb +74 -0
- data/lib/interfacets/test/component_registry.rb +63 -0
- data/lib/interfacets/test/js/inline_bus.rb +100 -0
- data/lib/interfacets/test/js/nodo_bus.rb +98 -0
- data/lib/interfacets/test/js/receivers/api.rb +48 -0
- data/lib/interfacets/test/js/receivers/react/node/xml_parser.rb +75 -0
- data/lib/interfacets/test/js/receivers/react/node.rb +133 -0
- data/lib/interfacets/test/js/receivers/react.rb +32 -0
- data/lib/interfacets/test/js/receivers/timer.rb +77 -0
- data/lib/interfacets/test/js/receivers/url.rb +60 -0
- data/lib/interfacets/test/standard_elements.yml +173 -0
- data/lib/interfacets/test/ui_simulator.rb +75 -0
- data/lib/interfacets/test/validation_engine.rb +151 -0
- data/lib/interfacets/test.rb +13 -0
- data/lib/interfacets/version.rb +1 -1
- data/lib/interfacets.rb +29 -2
- metadata +114 -6
- data/README.md +0 -35
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Client
|
|
5
|
+
module Channels
|
|
6
|
+
class Url
|
|
7
|
+
include Channels::Base
|
|
8
|
+
|
|
9
|
+
type("interfacets:url")
|
|
10
|
+
|
|
11
|
+
class Builder
|
|
12
|
+
attr_reader :state
|
|
13
|
+
def initialize
|
|
14
|
+
@state = {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def path(path, query: {})
|
|
18
|
+
state[:urlSpec] = {
|
|
19
|
+
url: (
|
|
20
|
+
System
|
|
21
|
+
.current_bus
|
|
22
|
+
.url_for(path)
|
|
23
|
+
),
|
|
24
|
+
queryParams: query,
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def redirect(url, query: {})
|
|
29
|
+
@redirected = true
|
|
30
|
+
state[:redirectUrlSpec] = { url:, query: }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def redirected?
|
|
34
|
+
@redirected
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def prepare(entity)
|
|
39
|
+
@builder ||= Builder.new
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def builder(stream = "default")
|
|
43
|
+
@builder
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def result
|
|
47
|
+
{ streams: { default: @builder.state } }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Client
|
|
5
|
+
class Config
|
|
6
|
+
attr_accessor(
|
|
7
|
+
:mount_point,
|
|
8
|
+
:root_url
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
def url_for(path:)
|
|
12
|
+
[
|
|
13
|
+
root_url,
|
|
14
|
+
mount_point,
|
|
15
|
+
path,
|
|
16
|
+
]
|
|
17
|
+
.map { _1.sub(%r{/$}, "").sub(%r{^/}, "") }
|
|
18
|
+
.join("/")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Client
|
|
5
|
+
# alias: CrappyDelegator
|
|
6
|
+
class Delegator
|
|
7
|
+
def initialize(obj)
|
|
8
|
+
@__obj__ = obj
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def __getobj__
|
|
12
|
+
@__obj__
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def respond_to_missing?(...)
|
|
16
|
+
@__obj__.send(:respond_to_missing?, ...)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def method_missing(method, *a, **p, &)
|
|
20
|
+
@__obj__.send(method, *a, **p, &)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# delegation helpers
|
|
24
|
+
def inspect
|
|
25
|
+
[self.class.name, __getobj__.inspect].join(" -> ")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_s
|
|
29
|
+
inspect
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def presence
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Client
|
|
5
|
+
class Registry
|
|
6
|
+
def initialize
|
|
7
|
+
@stores = {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def build(name, id)
|
|
11
|
+
entity = Object.const_get(name).client_entity_class
|
|
12
|
+
|
|
13
|
+
@stores[id] ||= entity.store.new
|
|
14
|
+
|
|
15
|
+
entity.new(
|
|
16
|
+
store: @stores[id],
|
|
17
|
+
parent: nil,
|
|
18
|
+
nesting: ["root"]
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interfacets
|
|
4
|
+
module Client
|
|
5
|
+
class System
|
|
6
|
+
module Transmit
|
|
7
|
+
def self.call(data)
|
|
8
|
+
Kernel.js_eval("self.rubyEvent(#{data.to_json})")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
attr_accessor(
|
|
14
|
+
:current_bus,
|
|
15
|
+
:logger,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def logger
|
|
19
|
+
@logger ||= InterfacetsLogger.main
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def start(transmit: Transmit)
|
|
23
|
+
@instance = new(transmit)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
attr_accessor :transmit
|
|
28
|
+
def initialize(transmit)
|
|
29
|
+
@transmit = transmit
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def handle(event)
|
|
33
|
+
if event.fetch("type") == "interfacets:system:create_bus"
|
|
34
|
+
create_bus(event.fetch("payload"))
|
|
35
|
+
else
|
|
36
|
+
bus_id = event.dig("destination", "bus")
|
|
37
|
+
raise("unknown event: #{event.inspect}") unless bus_id
|
|
38
|
+
|
|
39
|
+
System.current_bus = bus_registry.fetch(bus_id)
|
|
40
|
+
payload = System.current_bus.handle(event)
|
|
41
|
+
|
|
42
|
+
transmit.(
|
|
43
|
+
{
|
|
44
|
+
type: "interfacets:system:render",
|
|
45
|
+
payload:,
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
ensure
|
|
50
|
+
GC.start
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# private
|
|
54
|
+
|
|
55
|
+
def config
|
|
56
|
+
@config ||= Config.new
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def create_bus(payload)
|
|
60
|
+
id = payload.fetch("id")
|
|
61
|
+
hydration_event = payload.fetch("hydration")
|
|
62
|
+
|
|
63
|
+
channel_ids = payload.fetch("channel_ids")
|
|
64
|
+
|
|
65
|
+
Bus.new(
|
|
66
|
+
id:,
|
|
67
|
+
channels: [
|
|
68
|
+
Channels::Api.new(id: "interfacets:api"),
|
|
69
|
+
Channels::React::Channel.new(id: "dom"),
|
|
70
|
+
Channels::Url.new(id: "url"),
|
|
71
|
+
(Channels::SpeechToText.new(id: "speechToText") if channel_ids.include?("speechToText")),
|
|
72
|
+
(Channels::Timer.new(id: "timer") if channel_ids.include?("timer")),
|
|
73
|
+
(Channels::Audio.new(id: "audio") if channel_ids.include?("audio")),
|
|
74
|
+
].compact,
|
|
75
|
+
root_url: payload.fetch("config").fetch("root_url"),
|
|
76
|
+
).tap do
|
|
77
|
+
bus_registry[id] = _1
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
handle(hydration_event)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def bus_registry
|
|
84
|
+
@bus_registry ||= {}
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -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(/\A\s*\z/)
|
|
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,115 @@
|
|
|
1
|
+
# rubocop:disable all
|
|
2
|
+
|
|
3
|
+
# copied from github
|
|
4
|
+
|
|
5
|
+
required = (
|
|
6
|
+
begin
|
|
7
|
+
require "ostruct"
|
|
8
|
+
true
|
|
9
|
+
rescue
|
|
10
|
+
false
|
|
11
|
+
end
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
unless required
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class OpenStruct
|
|
18
|
+
def initialize hash=nil
|
|
19
|
+
@table = {}
|
|
20
|
+
if hash
|
|
21
|
+
hash.each do |k,v|
|
|
22
|
+
k = k.to_sym
|
|
23
|
+
@table[k] = v
|
|
24
|
+
new_ostruct_member(k)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def initialize_copy orig
|
|
30
|
+
super
|
|
31
|
+
@table = @table.dup
|
|
32
|
+
@table.each_key{|key| new_ostruct_member(key)}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_h
|
|
36
|
+
@table.dup
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def each_pair
|
|
40
|
+
return to_enum __method__ unless block_given?
|
|
41
|
+
@table.each{|p| yield p}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def new_ostruct_member name
|
|
45
|
+
name = name.to_sym
|
|
46
|
+
unless respond_to?(name)
|
|
47
|
+
define_singleton_method(name){ @table[name] }
|
|
48
|
+
define_singleton_method("#{name}=".to_sym){ |x| @table[name] = x }
|
|
49
|
+
end
|
|
50
|
+
name
|
|
51
|
+
end
|
|
52
|
+
protected :new_ostruct_member
|
|
53
|
+
|
|
54
|
+
def inspect
|
|
55
|
+
str = "#<#{self.class}"
|
|
56
|
+
ary = []
|
|
57
|
+
@table.each do |k,v|
|
|
58
|
+
ary << "#{k}=#{v}"
|
|
59
|
+
end
|
|
60
|
+
if 0 < ary.length
|
|
61
|
+
str << ' '
|
|
62
|
+
str << ary.join(', ')
|
|
63
|
+
end
|
|
64
|
+
str << '>'
|
|
65
|
+
end
|
|
66
|
+
alias :to_s :inspect
|
|
67
|
+
|
|
68
|
+
def delete_field name
|
|
69
|
+
sym = name.to_sym
|
|
70
|
+
singleton_class.__send__ :remove_method, sym, "#{sym}=".to_sym
|
|
71
|
+
@table.delete sym
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def == other
|
|
75
|
+
return false unless other.kind_of?(OpenStruct)
|
|
76
|
+
@table == other.table
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def [] key
|
|
80
|
+
@table[key.to_sym]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def []= key, value
|
|
84
|
+
@table[new_ostruct_member(key)] = value
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def eql? other
|
|
88
|
+
return false unless other.kind_of?(OpenStruct)
|
|
89
|
+
@table.eql?(other.table)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def hash
|
|
93
|
+
@table.hash
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
attr_reader :table
|
|
97
|
+
protected :table
|
|
98
|
+
|
|
99
|
+
def method_missing mid, *args
|
|
100
|
+
mname = mid.to_s
|
|
101
|
+
len = args.length
|
|
102
|
+
if mname.chomp! '='
|
|
103
|
+
if len != 1
|
|
104
|
+
raise ArgumentError, "wrong number of arguments (#{len} for 1)"
|
|
105
|
+
end
|
|
106
|
+
@table[new_ostruct_member(mname)] = args[0]
|
|
107
|
+
elsif len == 0
|
|
108
|
+
@table[mid]
|
|
109
|
+
else
|
|
110
|
+
raise NoMethodError, "undefined method `#{mid}' for #{self}"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
# rubocop:enable all
|
|
115
|
+
end
|
|
@@ -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
|