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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +173 -1
  3. data/.tmp +5 -0
  4. data/LICENSE +21 -0
  5. data/Rakefile +9 -7
  6. data/lib/interfacets/client/actor.rb +20 -0
  7. data/lib/interfacets/client/assets.rb +210 -0
  8. data/lib/interfacets/client/bus.rb +73 -0
  9. data/lib/interfacets/client/channels/api.rb +102 -0
  10. data/lib/interfacets/client/channels/audio.rb +101 -0
  11. data/lib/interfacets/client/channels/base.rb +28 -0
  12. data/lib/interfacets/client/channels/page_visibility.rb +21 -0
  13. data/lib/interfacets/client/channels/react/builder.rb +203 -0
  14. data/lib/interfacets/client/channels/react/channel.rb +61 -0
  15. data/lib/interfacets/client/channels/react/dom.rb +91 -0
  16. data/lib/interfacets/client/channels/react/evaluator.rb +33 -0
  17. data/lib/interfacets/client/channels/speech_to_text.rb +100 -0
  18. data/lib/interfacets/client/channels/timer.rb +51 -0
  19. data/lib/interfacets/client/channels/url.rb +52 -0
  20. data/lib/interfacets/client/config.rb +22 -0
  21. data/lib/interfacets/client/delegator.rb +37 -0
  22. data/lib/interfacets/client/registry.rb +23 -0
  23. data/lib/interfacets/client/system.rb +88 -0
  24. data/lib/interfacets/client/utils/active_support_concern.rb +220 -0
  25. data/lib/interfacets/client/utils/mruby_patches.rb +81 -0
  26. data/lib/interfacets/client/utils/open_struct.rb +115 -0
  27. data/lib/interfacets/client/utils/securerandom.rb +69 -0
  28. data/lib/interfacets/client.rb +13 -0
  29. data/lib/interfacets/component_registry.rb +115 -0
  30. data/lib/interfacets/component_schema_parser.rb +84 -0
  31. data/lib/interfacets/mruby/build.dockerfile +66 -0
  32. data/lib/interfacets/mruby/build_config.rb +20 -0
  33. data/lib/interfacets/mruby/entrypoint.rb +23 -0
  34. data/lib/interfacets/mruby/init.c +66 -0
  35. data/lib/interfacets/server/api.rb +44 -0
  36. data/lib/interfacets/server/assets/facet.rb +63 -0
  37. data/lib/interfacets/server/assets.rb +216 -0
  38. data/lib/interfacets/server/basic_router.rb +79 -0
  39. data/lib/interfacets/server/bus.rb +34 -0
  40. data/lib/interfacets/server/config.rb +87 -0
  41. data/lib/interfacets/server/facets/deserializer.rb +25 -0
  42. data/lib/interfacets/server/facets/schema/serializer.rb +54 -0
  43. data/lib/interfacets/server/facets/serializer.rb +50 -0
  44. data/lib/interfacets/server/registry.rb +51 -0
  45. data/lib/interfacets/shared/basic_routable.rb +45 -0
  46. data/lib/interfacets/shared/entities/bus.rb +230 -0
  47. data/lib/interfacets/shared/entities/collection_proxy.rb +190 -0
  48. data/lib/interfacets/shared/entities/specs/handlers.rb +133 -0
  49. data/lib/interfacets/shared/entities/specs.rb +161 -0
  50. data/lib/interfacets/shared/entity.rb +102 -0
  51. data/lib/interfacets/shared/entity_dsl.rb +154 -0
  52. data/lib/interfacets/shared/facet.rb +200 -0
  53. data/lib/interfacets/shared/generated_store.rb +149 -0
  54. data/lib/interfacets/shared/utils.rb +54 -0
  55. data/lib/interfacets/shared/validations.rb +75 -0
  56. data/lib/interfacets/shared/view.rb +74 -0
  57. data/lib/interfacets/test/component_registry.rb +63 -0
  58. data/lib/interfacets/test/js/inline_bus.rb +100 -0
  59. data/lib/interfacets/test/js/nodo_bus.rb +98 -0
  60. data/lib/interfacets/test/js/receivers/api.rb +48 -0
  61. data/lib/interfacets/test/js/receivers/react/node/xml_parser.rb +75 -0
  62. data/lib/interfacets/test/js/receivers/react/node.rb +133 -0
  63. data/lib/interfacets/test/js/receivers/react.rb +32 -0
  64. data/lib/interfacets/test/js/receivers/timer.rb +77 -0
  65. data/lib/interfacets/test/js/receivers/url.rb +60 -0
  66. data/lib/interfacets/test/standard_elements.yml +173 -0
  67. data/lib/interfacets/test/ui_simulator.rb +75 -0
  68. data/lib/interfacets/test/validation_engine.rb +151 -0
  69. data/lib/interfacets/test.rb +13 -0
  70. data/lib/interfacets/version.rb +1 -1
  71. data/lib/interfacets.rb +29 -2
  72. metadata +114 -6
  73. 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