isomorfeus-preact 10.5.0

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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +62 -0
  3. data/lib/browser/delegate_native.rb +70 -0
  4. data/lib/browser/element.rb +176 -0
  5. data/lib/browser/element/canvas.rb +17 -0
  6. data/lib/browser/element/media.rb +78 -0
  7. data/lib/browser/event.rb +92 -0
  8. data/lib/browser/event_target.rb +39 -0
  9. data/lib/browser/file_list.rb +125 -0
  10. data/lib/browser/iterable.rb +15 -0
  11. data/lib/isomorfeus-preact.rb +109 -0
  12. data/lib/isomorfeus/preact/config.rb +189 -0
  13. data/lib/isomorfeus/preact/memcached_component_cache.rb +19 -0
  14. data/lib/isomorfeus/preact/redis_component_cache.rb +19 -0
  15. data/lib/isomorfeus/preact/thread_local_component_cache.rb +17 -0
  16. data/lib/isomorfeus/preact_view_helper.rb +188 -0
  17. data/lib/isomorfeus/props/validate_hash_proxy.rb +186 -0
  18. data/lib/isomorfeus/props/validator.rb +159 -0
  19. data/lib/isomorfeus/top_level.rb +101 -0
  20. data/lib/isomorfeus/top_level_ssr.rb +27 -0
  21. data/lib/isomorfeus_preact/lucid_app/api.rb +22 -0
  22. data/lib/isomorfeus_preact/lucid_app/base.rb +7 -0
  23. data/lib/isomorfeus_preact/lucid_app/mixin.rb +16 -0
  24. data/lib/isomorfeus_preact/lucid_app/native_component_constructor.rb +91 -0
  25. data/lib/isomorfeus_preact/lucid_component/api.rb +68 -0
  26. data/lib/isomorfeus_preact/lucid_component/app_store_proxy.rb +37 -0
  27. data/lib/isomorfeus_preact/lucid_component/base.rb +7 -0
  28. data/lib/isomorfeus_preact/lucid_component/class_store_proxy.rb +44 -0
  29. data/lib/isomorfeus_preact/lucid_component/initializer.rb +14 -0
  30. data/lib/isomorfeus_preact/lucid_component/instance_store_proxy.rb +44 -0
  31. data/lib/isomorfeus_preact/lucid_component/mixin.rb +15 -0
  32. data/lib/isomorfeus_preact/lucid_component/native_component_constructor.rb +84 -0
  33. data/lib/isomorfeus_preact/lucid_component/styles_api.rb +31 -0
  34. data/lib/isomorfeus_preact/lucid_component/styles_wrapper.rb +40 -0
  35. data/lib/isomorfeus_preact/lucid_func/base.rb +7 -0
  36. data/lib/isomorfeus_preact/lucid_func/initializer.rb +11 -0
  37. data/lib/isomorfeus_preact/lucid_func/mixin.rb +12 -0
  38. data/lib/isomorfeus_preact/lucid_func/native_component_constructor.rb +55 -0
  39. data/lib/isomorfeus_preact/preact/function_component/api.rb +123 -0
  40. data/lib/isomorfeus_preact/preact/function_component/base.rb +9 -0
  41. data/lib/isomorfeus_preact/preact/function_component/initializer.rb +10 -0
  42. data/lib/isomorfeus_preact/preact/function_component/mixin.rb +12 -0
  43. data/lib/isomorfeus_preact/preact/function_component/native_component_constructor.rb +48 -0
  44. data/lib/lucid_app/context.rb +24 -0
  45. data/lib/lucid_prop_declaration/mixin.rb +126 -0
  46. data/lib/preact.rb +309 -0
  47. data/lib/preact/component/api.rb +124 -0
  48. data/lib/preact/component/base.rb +9 -0
  49. data/lib/preact/component/callbacks.rb +102 -0
  50. data/lib/preact/component/elements.rb +64 -0
  51. data/lib/preact/component/initializer.rb +11 -0
  52. data/lib/preact/component/mixin.rb +15 -0
  53. data/lib/preact/component/native_component_constructor.rb +65 -0
  54. data/lib/preact/component/params.rb +18 -0
  55. data/lib/preact/component/props.rb +55 -0
  56. data/lib/preact/component/resolution.rb +97 -0
  57. data/lib/preact/component/state.rb +58 -0
  58. data/lib/preact/context_wrapper.rb +46 -0
  59. data/lib/preact/native_constant_wrapper.rb +29 -0
  60. data/lib/preact/options.rb +98 -0
  61. data/lib/preact/ref.rb +17 -0
  62. data/lib/preact/version.rb +3 -0
  63. metadata +301 -0
@@ -0,0 +1,19 @@
1
+ module Isomorfeus
2
+ module Preact
3
+ class MemcachedComponentCache
4
+ def initialize(*args)
5
+ @dalli_client = Dalli::Client.new(*args)
6
+ end
7
+
8
+ def fetch(key)
9
+ json = @dalli_client.get(key)
10
+ Oj.load(json, mode: :strict)
11
+ end
12
+
13
+ def store(key, rendered_tree, response_status, styles)
14
+ json = Oj.dump([rendered_tree, response_status, styles], mode: :strict)
15
+ @dalli_client.set(key, json)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Isomorfeus
2
+ module Preact
3
+ class RedisComponentCache
4
+ def initialize(*args)
5
+ @redis_client = Redis.new(@args)
6
+ end
7
+
8
+ def fetch(key)
9
+ json = @redis_client.get(key)
10
+ Oj.load(json, mode: :strict)
11
+ end
12
+
13
+ def store(key, rendered_tree, response_status, styles)
14
+ json = Oj.dump([rendered_tree, response_status, styles], mode: :strict)
15
+ @redis_client.set(key, json)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ module Isomorfeus
2
+ module Preact
3
+ class ThreadLocalComponentCache
4
+ def initialize
5
+ Thread.current[:local_cache] = {} unless Thread.current.key?(:local_cache)
6
+ end
7
+
8
+ def fetch(key)
9
+ Thread.current[:local_cache][key]
10
+ end
11
+
12
+ def store(key, rendered_tree, response_status, styles)
13
+ Thread.current[:local_cache][key] = [rendered_tree, response_status, styles]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,188 @@
1
+ module Isomorfeus
2
+ module PreactViewHelper
3
+ def cached_mount_component(component_name, props = {}, asset = 'web_ssr.js')
4
+ key = "#{component_name}#{props}#{asset}"
5
+ if Isomorfeus.production?
6
+ render_result, @ssr_response_status, @ssr_styles = component_cache.fetch(key)
7
+ return render_result if render_result
8
+ end
9
+ render_result = mount_component(component_name, props, asset)
10
+ status = ssr_response_status
11
+ component_cache.store(key, render_result, status, ssr_styles) if status >= 200 && status < 300
12
+ render_result
13
+ end
14
+
15
+ def mount_component(component_name, props = {}, asset = 'web_ssr.js')
16
+ @ssr_response_status = nil
17
+ @ssr_styles = nil
18
+ thread_id_asset = "#{Thread.current.object_id}#{asset}"
19
+ render_result = "<div data-iso-env=\"#{Isomorfeus.env}\" data-iso-root=\"#{component_name}\" data-iso-props='#{Oj.dump(props, mode: :strict)}'"
20
+ if Isomorfeus.server_side_rendering
21
+
22
+ if Isomorfeus.development?
23
+ # always create a new context, effectively reloading code
24
+ # delete the existing context first, saves memory
25
+ if Isomorfeus.ssr_contexts.key?(thread_id_asset)
26
+ uuid = Isomorfeus.ssr_contexts[thread_id_asset].instance_variable_get(:@uuid)
27
+ runtime = Isomorfeus.ssr_contexts[thread_id_asset].instance_variable_get(:@runtime)
28
+ runtime.vm.delete_context(uuid)
29
+ end
30
+ asset_path = "#{Isomorfeus.ssr_hot_asset_url}#{asset}"
31
+ begin
32
+ asset = Net::HTTP.get(URI(asset_path))
33
+ rescue Exception => e
34
+ # STDERR.puts "Server Side Rendering: Failed loading asset #{asset_path} from webpack dev server. Error: #{e.message}"
35
+ # STDERR.puts e.backtrace.join("\n")
36
+ Isomorfeus.raise_error(message: "Server Side Rendering: Failed loading asset #{asset_path} from webpack dev server. Error: #{e.message}", stack: e.backtrace )
37
+ end
38
+ if asset.strip.start_with?('<')
39
+ # STDERR.puts "Server Side Rendering: Failed loading asset #{asset_path} from webpack dev server, asset is not javascript. Did the webpack build succeed?"
40
+ Isomorfeus.raise_error(message: "Server Side Rendering: Failed loading asset #{asset_path} from webpack dev server, asset is not javascript. Did the webpack build succeed?")
41
+ end
42
+ begin
43
+ Isomorfeus.ssr_contexts[thread_id_asset] = ExecJS.permissive_compile(asset)
44
+ rescue Exception => e
45
+ # STDERR.puts "Server Side Rendering: Failed creating context for #{asset_path}. Error: #{e.message}"
46
+ # STDERR.puts e.backtrace.join("\n")
47
+ Isomorfeus.raise_error(message: "Server Side Rendering: Failed creating context for #{asset_path}. Error: #{e.message}", stack: e.backtrace)
48
+ end
49
+ else
50
+ # initialize speednode context
51
+ unless Isomorfeus.ssr_contexts.key?(thread_id_asset)
52
+ asset_file_name = OpalWebpackLoader::Manifest.lookup_path_for(asset)
53
+ Isomorfeus.raise_error(message: "Server Side Rendering: Build asset file not found for #{asset}. Has it been build?") unless asset_file_name
54
+ asset_path = File.join('public', asset_file_name)
55
+ Isomorfeus.ssr_contexts[thread_id_asset] = ExecJS.permissive_compile(File.read(asset_path))
56
+ end
57
+ end
58
+
59
+ # build javascript for rendering first pass
60
+ # it will initialize buffers to guard against leaks, maybe caused by previous exceptions
61
+ javascript = <<~JAVASCRIPT
62
+ global.Opal.Preact.render_buffer = [];
63
+ global.Opal.Preact.active_components = [];
64
+ global.Opal.Preact.active_redux_components = [];
65
+ global.FirstPassFinished = false;
66
+ global.Exception = false;
67
+ global.IsomorfeusSessionId = '#{Thread.current[:isomorfeus_session_id]}';
68
+ global.Opal.Isomorfeus['$env=']('#{Isomorfeus.env}');
69
+ if (typeof global.Opal.Isomorfeus.$negotiated_locale === 'function') {
70
+ global.Opal.Isomorfeus["$negotiated_locale="]('#{props[:locale]}');
71
+ }
72
+ global.Opal.Isomorfeus['$force_init!']();
73
+ global.Opal.Isomorfeus['$ssr_response_status='](200);
74
+ global.Opal.Isomorfeus.TopLevel['$ssr_route_path=']('#{props[:location]}');
75
+ JAVASCRIPT
76
+
77
+ # if location_host and scheme are given and if Transport is loaded, connect and then render,
78
+ # otherwise do not render because only one pass is required
79
+ ws_scheme = props[:location_scheme] == 'https:' ? 'wss:' : 'ws:'
80
+ location_host = props[:location_host] ? props[:location_host] : 'localhost'
81
+ api_ws_path = Isomorfeus.respond_to?(:api_websocket_path) ? Isomorfeus.api_websocket_path : ''
82
+ transport_ws_url = ws_scheme + location_host + api_ws_path
83
+ javascript << <<~JAVASCRIPT
84
+ let api_ws_path = '#{api_ws_path}';
85
+ let exception;
86
+ if (typeof global.Opal.Isomorfeus.Transport !== 'undefined' && api_ws_path !== '') {
87
+ global.Opal.Isomorfeus.TopLevel["$transport_ws_url="]("#{transport_ws_url}");
88
+ global.Opal.send(global.Opal.Isomorfeus.Transport.$promise_connect(), 'then', [], ($$1 = function(){
89
+ try {
90
+ global.Opal.Isomorfeus.TopLevel.$render_component_to_string('#{component_name}', #{Oj.dump(props, mode: :strict)});
91
+ global.FirstPassFinished = 'transport';
92
+ } catch (e) {
93
+ global.Exception = e;
94
+ global.FirstPassFinished = 'transport';
95
+ }
96
+ }, $$1.$$s = this, $$1.$$arity = 0, $$1))
97
+ } else { return global.FirstPassFinished = true; };
98
+ JAVASCRIPT
99
+
100
+ # execute first render pass
101
+ first_pass_skipped = Isomorfeus.ssr_contexts[thread_id_asset].exec(javascript)
102
+
103
+ # wait for first pass to finish
104
+ unless first_pass_skipped
105
+ first_pass_finished, exception = Isomorfeus.ssr_contexts[thread_id_asset].exec('return [global.FirstPassFinished, global.Exception ? { message: global.Exception.message, stack: global.Exception.stack } : false ]')
106
+ Isomorfeus.raise_error(message: "Server Side Rendering: #{exception['message']}", stack: exception['stack']) if exception
107
+ unless first_pass_finished
108
+ start_time = Time.now
109
+ while !first_pass_finished
110
+ break if (Time.now - start_time) > 10
111
+ sleep 0.01
112
+ first_pass_finished = Isomorfeus.ssr_contexts[thread_id_asset].exec('return global.FirstPassFinished')
113
+ end
114
+ end
115
+
116
+ # wait for transport requests to finish
117
+ if first_pass_finished == 'transport'
118
+ transport_busy = Isomorfeus.ssr_contexts[thread_id_asset].exec('return global.Opal.Isomorfeus.Transport["$busy?"]()')
119
+ if transport_busy
120
+ start_time = Time.now
121
+ while transport_busy
122
+ break if (Time.now - start_time) > 10
123
+ sleep 0.01
124
+ transport_busy = Isomorfeus.ssr_contexts[thread_id_asset].exec('return global.Opal.Isomorfeus.Transport["$busy?"]()')
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ # build javascript for second render pass
131
+ # guard against leaks from first pass, maybe because of a exception
132
+ javascript = <<~JAVASCRIPT
133
+ global.Opal.Preact.render_buffer = [];
134
+ global.Opal.Preact.active_components = [];
135
+ global.Opal.Preact.active_redux_components = [];
136
+ global.Exception = false;
137
+ let rendered_tree;
138
+ let ssr_styles;
139
+ let component;
140
+ try {
141
+ rendered_tree = global.Opal.Isomorfeus.TopLevel.$render_component_to_string('#{component_name}', #{Oj.dump(props, mode: :strict)});
142
+ } catch (e) {
143
+ global.Exception = e;
144
+ }
145
+ let application_state = global.Opal.Isomorfeus.store.native.getState();
146
+ if (typeof global.Opal.Isomorfeus.Transport !== 'undefined') { global.Opal.Isomorfeus.Transport.$disconnect(); }
147
+ if (typeof global.NanoCSSInstance !== 'undefined') { ssr_styles = global.NanoCSSInstance.raw }
148
+ return [rendered_tree, application_state, ssr_styles, global.Opal.Isomorfeus['$ssr_response_status'](), global.Exception ? { message: global.Exception.message, stack: global.Exception.stack } : false];
149
+ JAVASCRIPT
150
+
151
+ # execute second render pass
152
+ rendered_tree, application_state, @ssr_styles, @ssr_response_status, exception = Isomorfeus.ssr_contexts[thread_id_asset].exec(javascript)
153
+ Isomorfeus.raise_error(message: exception['message'], stack: exception['stack']) if exception
154
+
155
+ render_result << " data-iso-hydrated='true'" if rendered_tree
156
+ if Isomorfeus.respond_to?(:current_user) && Isomorfeus.current_user && !Isomorfeus.current_user.anonymous?
157
+ render_result << " data-iso-usid=#{Oj.dump(Isomorfeus.current_user.to_sid, mode: :strict)}"
158
+ end
159
+ render_result << " data-iso-nloc='#{props[:locale]}'>"
160
+ render_result << (rendered_tree ? rendered_tree : "SSR didn't work")
161
+ else
162
+ if Isomorfeus.respond_to?(:current_user) && Isomorfeus.current_user && !Isomorfeus.current_user.anonymous?
163
+ render_result << " data-iso-usid=#{Oj.dump(Isomorfeus.current_user.to_sid, mode: :strict)}"
164
+ end
165
+ render_result << " data-iso-nloc='#{props[:locale]}'>"
166
+ end
167
+ render_result << '</div>'
168
+ if Isomorfeus.server_side_rendering
169
+ render_result = "<script type='application/javascript'>\nServerSideRenderingStateJSON = #{Oj.dump(application_state, mode: :strict)}\n</script>\n" << render_result
170
+ end
171
+ render_result
172
+ end
173
+
174
+ def ssr_response_status
175
+ @ssr_response_status || 200
176
+ end
177
+
178
+ def ssr_styles
179
+ @ssr_styles || ''
180
+ end
181
+
182
+ private
183
+
184
+ def component_cache
185
+ @_component_cache ||= Isomorfeus.component_cache_init_block.call
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,186 @@
1
+ module Isomorfeus
2
+ module Props
3
+ class ValidateHashProxy
4
+ def initialize
5
+ @validation_hash = { required: true, validate: {} }
6
+ end
7
+
8
+ def is
9
+ self
10
+ end
11
+ alias_method :and, :is
12
+ alias_method :has, :is
13
+ alias_method :with, :is
14
+
15
+ def allow_nil
16
+ @validation_hash[:allow_nil] = true
17
+ self
18
+ end
19
+
20
+ def cast
21
+ @validation_hash[:cast] = true
22
+ self
23
+ end
24
+
25
+ def default(v)
26
+ @validation_hash[:required] = false
27
+ @validation_hash[:default] = v
28
+ self
29
+ end
30
+
31
+ def ensure(v = nil, &block)
32
+ if block_given?
33
+ @validation_hash[:ensure_block] = block
34
+ else
35
+ @validation_hash[:ensure] = v
36
+ end
37
+ self
38
+ end
39
+
40
+ def exact_class(t_class)
41
+ @validation_hash[:class] = t_class
42
+ self
43
+ end
44
+
45
+ def greater_than(v)
46
+ @validation_hash[:validate][:gt] = v
47
+ self
48
+ end
49
+ alias_method :gt, :greater_than
50
+
51
+ def is_a(i_class)
52
+ @validation_hash[:is_a] = i_class
53
+ self
54
+ end
55
+
56
+ def keys(*keys)
57
+ @validation_hash[:validate][:hash_keys] = keys
58
+ self
59
+ end
60
+
61
+ def size(l)
62
+ @validation_hash[:validate][:size] = v
63
+ self
64
+ end
65
+ alias_method :length, :size
66
+
67
+ def less_than(v)
68
+ @validation_hash[:validate][:lt] = v
69
+ self
70
+ end
71
+ alias_method :lt, :less_than
72
+
73
+ def matches(regexp)
74
+ @validation_hash[:validate][:matches] = regexp
75
+ self
76
+ end
77
+
78
+ def max(l)
79
+ @validation_hash[:validate][:max] = l
80
+ self
81
+ end
82
+
83
+ def max_size(l)
84
+ @validation_hash[:validate][:max_size] = l
85
+ self
86
+ end
87
+ alias_method :max_length, :max_size
88
+
89
+ def min(l)
90
+ @validation_hash[:validate][:min] = l
91
+ self
92
+ end
93
+
94
+ def min_size(l)
95
+ @validation_hash[:validate][:min_size] = l
96
+ self
97
+ end
98
+ alias_method :min_length, :min_size
99
+
100
+ def negative
101
+ @validation_hash[:validate][:direction] = :negative
102
+ self
103
+ end
104
+
105
+ def optional
106
+ @validation_hash[:required] = false
107
+ self
108
+ end
109
+
110
+ def positive
111
+ @validation_hash[:validate][:direction] = :positive
112
+ self
113
+ end
114
+
115
+ def required
116
+ @validation_hash[:required] = true
117
+ self
118
+ end
119
+
120
+ def test(&block)
121
+ @validation_hash[:validate][:test] = block
122
+ self
123
+ end
124
+ alias_method :condition, :test
125
+ alias_method :check, :test
126
+
127
+ def validate_block(&block)
128
+ @validation_hash[:validate][:validate_block] = block
129
+ self
130
+ end
131
+
132
+ # types
133
+
134
+ def Array
135
+ @validation_hash[:class] = Array
136
+ self
137
+ end
138
+
139
+ def Boolean
140
+ @validation_hash[:type] = :boolean
141
+ self
142
+ end
143
+
144
+ def Enumerable
145
+ @validation_hash[:is_a] = Enumerable
146
+ self
147
+ end
148
+
149
+ def Float
150
+ @validation_hash[:class] = Float
151
+ self
152
+ end
153
+
154
+ def Hash
155
+ @validation_hash[:class] = Hash
156
+ self
157
+ end
158
+
159
+ def Integer
160
+ @validation_hash[:class] = Integer
161
+ self
162
+ end
163
+
164
+ def String
165
+ @validation_hash[:class] = String
166
+ self
167
+ end
168
+
169
+ # sub types
170
+
171
+ def Email
172
+ @validation_hash[:type] = :email
173
+ self
174
+ end
175
+
176
+ def Url
177
+ @validation_hash[:type] = :uri
178
+ self
179
+ end
180
+
181
+ def to_h
182
+ @validation_hash
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,159 @@
1
+ module Isomorfeus
2
+ module Props
3
+ class Validator
4
+ def initialize(source_class, prop, value, options)
5
+ @c = source_class
6
+ @p = prop
7
+ @v = value
8
+ @o = options
9
+ end
10
+
11
+ def validate!
12
+ ensured = ensure!
13
+ unless ensured
14
+ set_default_value
15
+ cast!
16
+ type!
17
+ end
18
+ run_checks!
19
+ true
20
+ end
21
+
22
+ def validated_value
23
+ validate!
24
+ @v
25
+ end
26
+
27
+ private
28
+
29
+ # basic tests
30
+
31
+ def set_default_value
32
+ return unless @v.nil?
33
+ @v = @o[:default] if @o.key?(:default)
34
+ end
35
+
36
+ def cast!
37
+ if @o.key?(:cast)
38
+ begin
39
+ @v = case @o[:class]
40
+ when Integer then @v.to_i
41
+ when String then @v.to_s
42
+ when Float then @v.to_f
43
+ when Array then @v.to_a
44
+ when Hash then @v.to_h
45
+ end
46
+ @v = !!@v if @o[:type] == :boolean
47
+ rescue
48
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} cast failed") unless @v.class == @o[:class]
49
+ end
50
+ end
51
+ end
52
+
53
+ def ensure!
54
+ if @o.key?(:ensure)
55
+ @v = @o[:ensure] unless @v
56
+ true
57
+ elsif @o.key?(:ensure_block)
58
+ @v = @o[:ensure_block].call(@v)
59
+ true
60
+ else
61
+ false
62
+ end
63
+ end
64
+
65
+ def type!
66
+ return if @o[:allow_nil] && @v.nil?
67
+ if @o.key?(:class)
68
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} class not #{@o[:class]}") unless @v.class == @o[:class]
69
+ elsif @o.key?(:is_a)
70
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} is not a #{@o[:is_a]}") unless @v.is_a?(@o[:is_a])
71
+ elsif @o.key?(:type)
72
+ case @o[:type]
73
+ when :boolean
74
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} is not a boolean") unless @v.class == TrueClass || @v.class == FalseClass
75
+ else
76
+ c_string_sub_types
77
+ end
78
+ end
79
+ end
80
+
81
+ # all other checks
82
+
83
+ def run_checks!
84
+ if @o.key?(:validate)
85
+ @o[:validate].each do |m, l|
86
+ send('c_' + m, l)
87
+ end
88
+ end
89
+ @o[:validate_block].call(@v) if @o.key?(:validate_block)
90
+ end
91
+
92
+ # specific validations
93
+ def c_gt(v)
94
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} not greater than #{v}!") unless @v > v
95
+ end
96
+
97
+ def c_lt(v)
98
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} not less than #{v}!") unless @v < v
99
+ end
100
+
101
+ def c_keys(v)
102
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} keys dont fit!") unless @v.keys.sort == v.sort
103
+ end
104
+
105
+ def c_size(v)
106
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} length/size is not #{v}") unless @v.size == v
107
+ end
108
+
109
+ def c_matches(v)
110
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} does not match #{v}") unless v.match?(@v)
111
+ end
112
+
113
+ def c_max(v)
114
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} is larger than #{v}") unless @v <= v
115
+ end
116
+
117
+ def c_min(v)
118
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} is smaller than #{v}") unless @v >= v
119
+ end
120
+
121
+ def c_max_size(v)
122
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} is larger than #{v}") unless @v.size <= v
123
+ end
124
+
125
+ def c_min_size(v)
126
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} is smaller than #{v}") unless @v.size >= v
127
+ end
128
+
129
+ def c_direction(v)
130
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} is positive") if v == :negative && @v >= 0
131
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} is negative") if v == :positive && @v < 0
132
+ end
133
+
134
+ def c_test
135
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} test condition check failed") unless @o[:test].call(@v)
136
+ end
137
+
138
+ def c_string_sub_types
139
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} must be a String") unless @v.class == String
140
+ case @o[:type]
141
+ when :email
142
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} is not a valid email address") unless @v.match?(/\A[^@\s]+@([^@\s]+\.)+[^@\s]+\z/)
143
+ when :uri
144
+ if RUBY_ENGINE == 'opal'
145
+ %x{
146
+ try {
147
+ new URL(#@v);
148
+ } catch {
149
+ #{Isomorfeus.raise_error(message: "#{@c}: #{@p} is not a valid uri")}
150
+ }
151
+ }
152
+ else
153
+ Isomorfeus.raise_error(message: "#{@c}: #{@p} is not a valid uri") unless @v.match?(/\A#{URI.regexp}\z/)
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end