syntropy 0.3 → 0.4
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 +10 -2
- data/CHANGELOG.md +8 -0
- data/README.md +30 -11
- data/TODO.md +89 -0
- data/bin/syntropy +2 -2
- data/cmd/setup/template/site/.gitignore +57 -0
- data/cmd/setup/template/site/Dockerfile +32 -0
- data/cmd/setup/template/site/Gemfile +3 -0
- data/cmd/setup/template/site/README.md +0 -0
- data/cmd/setup/template/site/bin/console +0 -0
- data/cmd/setup/template/site/bin/restart +0 -0
- data/cmd/setup/template/site/bin/server +0 -0
- data/cmd/setup/template/site/bin/start +0 -0
- data/cmd/setup/template/site/bin/stop +0 -0
- data/cmd/setup/template/site/docker-compose.yml +51 -0
- data/cmd/setup/template/site/proxy/Dockerfile +5 -0
- data/cmd/setup/template/site/proxy/etc/Caddyfile +7 -0
- data/cmd/setup/template/site/proxy/etc/tls_auto +2 -0
- data/cmd/setup/template/site/proxy/etc/tls_cloudflare +4 -0
- data/cmd/setup/template/site/proxy/etc/tls_custom +1 -0
- data/cmd/setup/template/site/proxy/etc/tls_selfsigned +1 -0
- data/cmd/setup/template/site/site/_layout/default.rb +11 -0
- data/cmd/setup/template/site/site/about.md +6 -0
- data/cmd/setup/template/site/site/articles/cage.rb +29 -0
- data/cmd/setup/template/site/site/articles/index.rb +3 -0
- data/cmd/setup/template/site/site/assets/css/style.css +40 -0
- data/cmd/setup/template/site/site/assets/img/syntropy.png +0 -0
- data/cmd/setup/template/site/site/index.rb +15 -0
- data/docker-compose.yml +51 -0
- data/lib/syntropy/app.rb +93 -24
- data/lib/syntropy/errors.rb +16 -2
- data/lib/syntropy/module.rb +26 -5
- data/lib/syntropy/request_extensions.rb +96 -0
- data/lib/syntropy/rpc_api.rb +26 -9
- data/lib/syntropy/side_run.rb +46 -0
- data/lib/syntropy/version.rb +1 -1
- data/lib/syntropy.rb +14 -49
- data/syntropy.gemspec +1 -1
- data/test/app/baz.rb +3 -0
- data/test/test_app.rb +57 -7
- data/test/test_side_run.rb +43 -0
- data/test/test_validation.rb +1 -1
- metadata +31 -3
data/lib/syntropy/app.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'qeweney'
|
4
3
|
require 'json'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
require 'qeweney'
|
5
7
|
require 'papercraft'
|
6
8
|
|
7
9
|
require 'syntropy/errors'
|
@@ -23,10 +25,12 @@ module Syntropy
|
|
23
25
|
@machine.spin do
|
24
26
|
# we do startup stuff asynchronously, in order to first let TP2 do its
|
25
27
|
# setup tasks
|
26
|
-
@machine.sleep 0.
|
28
|
+
@machine.sleep 0.15
|
27
29
|
@opts[:logger]&.call("Serving from #{File.expand_path(@src_path)}")
|
28
30
|
start_file_watcher if opts[:watch_files]
|
29
31
|
end
|
32
|
+
|
33
|
+
@module_loader ||= Syntropy::ModuleLoader.new(@src_path, @opts)
|
30
34
|
end
|
31
35
|
|
32
36
|
def find_route(path, cache: true)
|
@@ -34,15 +38,16 @@ module Syntropy
|
|
34
38
|
return cached if cached
|
35
39
|
|
36
40
|
entry = calculate_route(path)
|
37
|
-
if entry[:kind] != :not_found
|
38
|
-
@route_cache[path] = entry if cache
|
39
|
-
end
|
41
|
+
@route_cache[path] = entry if entry[:kind] != :not_found && cache
|
40
42
|
entry
|
41
43
|
end
|
42
44
|
|
43
45
|
def call(req)
|
44
46
|
entry = find_route(req.path)
|
45
47
|
render_entry(req, entry)
|
48
|
+
rescue Syntropy::Error => e
|
49
|
+
msg = e.message
|
50
|
+
req.respond(msg.empty? ? nil : msg, ':status' => e.http_status)
|
46
51
|
rescue StandardError => e
|
47
52
|
p e
|
48
53
|
p e.backtrace
|
@@ -68,6 +73,8 @@ module Syntropy
|
|
68
73
|
end
|
69
74
|
|
70
75
|
def invalidate_cache(fn)
|
76
|
+
@module_loader.unload(fn)
|
77
|
+
|
71
78
|
invalidated_keys = []
|
72
79
|
@route_cache.each do |k, v|
|
73
80
|
@opts[:logger]&.call("Invalidate cache for #{k}", nil)
|
@@ -79,7 +86,7 @@ module Syntropy
|
|
79
86
|
|
80
87
|
def calculate_relative_path_re(mount_path)
|
81
88
|
mount_path = '' if mount_path == '/'
|
82
|
-
|
89
|
+
%r{^#{mount_path}(?:/(.*))?$}
|
83
90
|
end
|
84
91
|
|
85
92
|
FILE_KINDS = {
|
@@ -89,7 +96,7 @@ module Syntropy
|
|
89
96
|
NOT_FOUND = { kind: :not_found }
|
90
97
|
|
91
98
|
# We don't allow access to path with /.., or entries that start with _
|
92
|
-
FORBIDDEN_RE = /
|
99
|
+
FORBIDDEN_RE = %r{(/_)|((/\.\.)/?)}
|
93
100
|
|
94
101
|
def calculate_route(path)
|
95
102
|
return NOT_FOUND if path =~ FORBIDDEN_RE
|
@@ -141,7 +148,7 @@ module Syntropy
|
|
141
148
|
entry[:kind] == :module ? entry : NOT_FOUND
|
142
149
|
end
|
143
150
|
|
144
|
-
UP_TREE_PATH_RE =
|
151
|
+
UP_TREE_PATH_RE = %r{^(.+)?/[^/]+$}
|
145
152
|
|
146
153
|
def parent_path(path)
|
147
154
|
m = path.match(UP_TREE_PATH_RE)
|
@@ -151,25 +158,47 @@ module Syntropy
|
|
151
158
|
def render_entry(req, entry)
|
152
159
|
case entry[:kind]
|
153
160
|
when :not_found
|
154
|
-
req
|
161
|
+
respond_not_found(req, entry)
|
155
162
|
when :static
|
156
163
|
respond_static(req, entry)
|
157
164
|
when :markdown
|
158
|
-
|
159
|
-
req.respond(body, 'Content-Type' => 'text/html')
|
165
|
+
respond_markdown(req, entry)
|
160
166
|
when :module
|
161
|
-
|
167
|
+
respond_module(req, entry)
|
162
168
|
else
|
163
169
|
raise 'Invalid entry kind'
|
164
170
|
end
|
165
171
|
end
|
166
172
|
|
173
|
+
def respond_not_found(req, _entry)
|
174
|
+
headers = { ':status' => Qeweney::Status::NOT_FOUND }
|
175
|
+
case req.method
|
176
|
+
when 'head'
|
177
|
+
req.respond(nil, headers)
|
178
|
+
else
|
179
|
+
req.respond('Not found', headers)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
167
183
|
def respond_static(req, entry)
|
168
184
|
entry[:mime_type] ||= Qeweney::MimeTypes[File.extname(entry[:fn])]
|
169
|
-
|
185
|
+
headers = { 'Content-Type' => entry[:mime_type] }
|
186
|
+
req.respond_by_http_method(
|
187
|
+
'head' => [nil, headers],
|
188
|
+
'get' => -> { [IO.read(entry[:fn]), headers] }
|
189
|
+
)
|
190
|
+
end
|
191
|
+
|
192
|
+
def respond_markdown(req, entry)
|
193
|
+
entry[:mime_type] ||= Qeweney::MimeTypes[File.extname(entry[:fn])]
|
194
|
+
headers = { 'Content-Type' => entry[:mime_type] }
|
195
|
+
req.respond_by_http_method(
|
196
|
+
'head' => [nil, headers],
|
197
|
+
'get' => -> { [render_markdown(entry[:fn]), headers] }
|
198
|
+
)
|
170
199
|
end
|
171
200
|
|
172
|
-
def
|
201
|
+
def respond_module(req, entry)
|
173
202
|
entry[:code] ||= load_module(entry)
|
174
203
|
if entry[:code] == :invalid
|
175
204
|
req.respond(nil, ':status' => Qeweney::Status::INTERNAL_SERVER_ERROR)
|
@@ -177,6 +206,8 @@ module Syntropy
|
|
177
206
|
end
|
178
207
|
|
179
208
|
entry[:code].call(req)
|
209
|
+
rescue Syntropy::Error => e
|
210
|
+
req.respond(nil, ':status' => e.http_status)
|
180
211
|
rescue StandardError => e
|
181
212
|
p e
|
182
213
|
p e.backtrace
|
@@ -184,24 +215,62 @@ module Syntropy
|
|
184
215
|
end
|
185
216
|
|
186
217
|
def load_module(entry)
|
187
|
-
|
188
|
-
|
189
|
-
o
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
o.is_a?(Papercraft::HTML) ? wrap_template(o) : o
|
218
|
+
ref = entry[:fn].gsub(%r{^#{@src_path}/}, '').gsub(/\.rb$/, '')
|
219
|
+
o = @module_loader.load(ref)
|
220
|
+
o.is_a?(Papercraft::Template) ? wrap_template(o) : o
|
221
|
+
rescue Exception => e
|
222
|
+
@opts[:logger]&.call("Error while loading module #{ref}: #{e.message}")
|
223
|
+
:invalid
|
194
224
|
end
|
195
225
|
|
196
226
|
def wrap_template(templ)
|
197
|
-
|
227
|
+
lambda { |req|
|
198
228
|
body = templ.render
|
199
229
|
req.respond(body, 'Content-Type' => 'text/html')
|
200
230
|
}
|
201
231
|
end
|
202
232
|
|
203
|
-
def render_markdown(
|
204
|
-
|
233
|
+
def render_markdown(fn)
|
234
|
+
atts, md = parse_markdown_file(fn)
|
235
|
+
|
236
|
+
if atts[:layout]
|
237
|
+
layout = @module_loader.load("_layout/#{atts[:layout]}")
|
238
|
+
html = layout.apply { emit_markdown(md) }.render
|
239
|
+
else
|
240
|
+
html = Papercraft.markdown(md)
|
241
|
+
end
|
242
|
+
html
|
243
|
+
end
|
244
|
+
|
245
|
+
DATE_REGEXP = /(\d{4}\-\d{2}\-\d{2})/
|
246
|
+
FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
|
247
|
+
YAML_OPTS = {
|
248
|
+
permitted_classes: [Date],
|
249
|
+
symbolize_names: true
|
250
|
+
}
|
251
|
+
|
252
|
+
# Parses the markdown file at the given path.
|
253
|
+
#
|
254
|
+
# @param path [String] file path
|
255
|
+
# @return [Array] an tuple containing properties<Hash>, contents<String>
|
256
|
+
def parse_markdown_file(path)
|
257
|
+
content = IO.read(path) || ''
|
258
|
+
atts = {}
|
259
|
+
|
260
|
+
# Parse date from file name
|
261
|
+
if (m = path.match(DATE_REGEXP))
|
262
|
+
atts[:date] ||= Date.parse(m[1])
|
263
|
+
end
|
264
|
+
|
265
|
+
if (m = content.match(FRONT_MATTER_REGEXP))
|
266
|
+
front_matter = m[1]
|
267
|
+
content = m.post_match
|
268
|
+
|
269
|
+
yaml = YAML.safe_load(front_matter, **YAML_OPTS)
|
270
|
+
atts = atts.merge(yaml)
|
271
|
+
end
|
272
|
+
|
273
|
+
[atts, content]
|
205
274
|
end
|
206
275
|
end
|
207
276
|
end
|
data/lib/syntropy/errors.rb
CHANGED
@@ -4,17 +4,31 @@ require 'qeweney'
|
|
4
4
|
|
5
5
|
module Syntropy
|
6
6
|
class Error < StandardError
|
7
|
+
Status = Qeweney::Status
|
8
|
+
|
7
9
|
attr_reader :http_status
|
8
10
|
|
9
11
|
def initialize(status, msg = '')
|
10
|
-
@http_status = status || Qeweney::Status::INTERNAL_SERVER_ERROR
|
11
12
|
super(msg)
|
13
|
+
@http_status = status || Qeweney::Status::INTERNAL_SERVER_ERROR
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
# Create class methods for common errors
|
18
|
+
{
|
19
|
+
not_found: Status::NOT_FOUND,
|
20
|
+
method_not_allowed: Status::METHOD_NOT_ALLOWED,
|
21
|
+
teapot: Status::TEAPOT
|
22
|
+
}
|
23
|
+
.each { |k, v|
|
24
|
+
define_method(k) { |msg = ''| new(v, msg) }
|
25
|
+
}
|
12
26
|
end
|
13
27
|
end
|
14
28
|
|
15
29
|
class ValidationError < Error
|
16
30
|
def initialize(msg)
|
17
|
-
|
31
|
+
super(Qeweney::Status::BAD_REQUEST, msg)
|
18
32
|
end
|
19
33
|
end
|
20
34
|
end
|
data/lib/syntropy/module.rb
CHANGED
@@ -1,37 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'papercraft'
|
4
|
+
|
3
5
|
module Syntropy
|
4
6
|
class ModuleLoader
|
5
7
|
def initialize(root, env)
|
6
8
|
@root = root
|
7
9
|
@env = env
|
8
|
-
@loaded = {}
|
10
|
+
@loaded = {} # maps ref to code
|
11
|
+
@fn_map = {} # maps filename to ref
|
9
12
|
end
|
10
13
|
|
11
14
|
def load(ref)
|
12
15
|
@loaded[ref] ||= load_module(ref)
|
13
16
|
end
|
14
17
|
|
18
|
+
def unload(fn)
|
19
|
+
ref = @fn_map[fn]
|
20
|
+
return if !ref
|
21
|
+
|
22
|
+
@loaded.delete(ref)
|
23
|
+
@fn_map.delete(fn)
|
24
|
+
end
|
25
|
+
|
15
26
|
private
|
16
27
|
|
17
28
|
def load_module(ref)
|
18
|
-
fn = File.join(@root, "#{ref}.rb")
|
19
|
-
|
29
|
+
fn = File.expand_path(File.join(@root, "#{ref}.rb"))
|
30
|
+
@fn_map[fn] = ref
|
31
|
+
raise "File not found #{fn}" if !File.file?(fn)
|
20
32
|
|
21
33
|
mod_body = IO.read(fn)
|
22
34
|
mod_ctx = Class.new(Syntropy::Module)
|
23
35
|
mod_ctx.loader = self
|
24
|
-
# mod_ctx = .new(self, @env)
|
25
36
|
mod_ctx.module_eval(mod_body, fn, 1)
|
26
37
|
|
27
38
|
export_value = mod_ctx.__export_value__
|
28
39
|
|
40
|
+
wrap_module(mod_ctx, export_value)
|
41
|
+
end
|
42
|
+
|
43
|
+
def wrap_module(mod_ctx, export_value)
|
29
44
|
case export_value
|
30
45
|
when nil
|
31
|
-
raise
|
46
|
+
raise 'No export found'
|
32
47
|
when Symbol
|
33
48
|
# TODO: verify export_value denotes a valid method
|
34
49
|
mod_ctx.new(@env)
|
50
|
+
when String
|
51
|
+
->(req) { req.respond(export_value) }
|
35
52
|
when Proc
|
36
53
|
export_value
|
37
54
|
else
|
@@ -57,6 +74,10 @@ module Syntropy
|
|
57
74
|
@__export_value__ = ref
|
58
75
|
end
|
59
76
|
|
77
|
+
def self.templ(&block)
|
78
|
+
Papercraft.html(&block)
|
79
|
+
end
|
80
|
+
|
60
81
|
def self.__export_value__
|
61
82
|
@__export_value__
|
62
83
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'qeweney'
|
4
|
+
|
5
|
+
module Syntropy
|
6
|
+
module RequestExtensions
|
7
|
+
def ctx
|
8
|
+
@ctx ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate_http_method(*accepted)
|
12
|
+
raise Syntropy::Error.method_not_allowed if !accepted.include?(method)
|
13
|
+
end
|
14
|
+
|
15
|
+
def respond_by_http_method(map)
|
16
|
+
value = map[self.method]
|
17
|
+
raise Syntropy::Error.method_not_allowed if !value
|
18
|
+
|
19
|
+
value = value.() if value.is_a?(Proc)
|
20
|
+
(body, headers) = value
|
21
|
+
respond(body, headers)
|
22
|
+
end
|
23
|
+
|
24
|
+
def respond_on_get(body, headers = {})
|
25
|
+
case self.method
|
26
|
+
when 'head'
|
27
|
+
respond(nil, headers)
|
28
|
+
when 'get'
|
29
|
+
respond(body, headers)
|
30
|
+
else
|
31
|
+
raise Syntropy::Error.method_not_allowed
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def respond_on_post(body, headers = {})
|
36
|
+
case self.method
|
37
|
+
when 'head'
|
38
|
+
respond(nil, headers)
|
39
|
+
when 'post'
|
40
|
+
respond(body, headers)
|
41
|
+
else
|
42
|
+
raise Syntropy::Error.method_not_allowed
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate_param(name, *clauses)
|
47
|
+
value = query[name]
|
48
|
+
clauses.each do |c|
|
49
|
+
valid = param_is_valid?(value, c)
|
50
|
+
raise(Syntropy::ValidationError, 'Validation error') if !valid
|
51
|
+
|
52
|
+
value = param_convert(value, c)
|
53
|
+
end
|
54
|
+
value
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
BOOL_REGEXP = /^(t|f|true|false|on|off|1|0|yes|no)$/
|
60
|
+
BOOL_TRUE_REGEXP = /^(t|true|on|1|yes)$/
|
61
|
+
INTEGER_REGEXP = /^[+-]?[0-9]+$/
|
62
|
+
FLOAT_REGEXP = /^[+-]?[0-9]+(\.[0-9]+)?$/
|
63
|
+
|
64
|
+
def param_is_valid?(value, cond)
|
65
|
+
return cond.any? { |c| param_is_valid?(value, c) } if cond.is_a?(Array)
|
66
|
+
|
67
|
+
if value
|
68
|
+
if cond == :bool
|
69
|
+
return value =~ BOOL_REGEXP
|
70
|
+
elsif cond == Integer
|
71
|
+
return value =~ INTEGER_REGEXP
|
72
|
+
elsif cond == Float
|
73
|
+
return value =~ FLOAT_REGEXP
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
cond === value
|
78
|
+
end
|
79
|
+
|
80
|
+
def param_convert(value, klass)
|
81
|
+
if klass == :bool
|
82
|
+
value =~ BOOL_TRUE_REGEXP ? true : false
|
83
|
+
elsif klass == Integer
|
84
|
+
value.to_i
|
85
|
+
elsif klass == Float
|
86
|
+
value.to_f
|
87
|
+
elsif klass == Symbol
|
88
|
+
value.to_sym
|
89
|
+
else
|
90
|
+
value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
Qeweney::Request.include(Syntropy::RequestExtensions)
|
data/lib/syntropy/rpc_api.rb
CHANGED
@@ -11,7 +11,7 @@ module Syntropy
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def call(req)
|
14
|
-
response, status =
|
14
|
+
response, status = __invoke__(req)
|
15
15
|
req.respond(
|
16
16
|
response.to_json,
|
17
17
|
':status' => status,
|
@@ -19,15 +19,17 @@ module Syntropy
|
|
19
19
|
)
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
private
|
23
|
+
|
24
|
+
def __invoke__(req)
|
25
|
+
q = req.validate_param(:q, String).to_sym
|
24
26
|
response = case req.method
|
25
27
|
when 'get'
|
26
|
-
|
28
|
+
__invoke_get__(q, req)
|
27
29
|
when 'post'
|
28
|
-
|
30
|
+
__invoke_post__(q, req)
|
29
31
|
else
|
30
|
-
raise Syntropy::Error.
|
32
|
+
raise Syntropy::Error.method_not_allowed
|
31
33
|
end
|
32
34
|
[{ status: 'OK', response: response }, Qeweney::Status::OK]
|
33
35
|
rescue => e
|
@@ -35,14 +37,29 @@ module Syntropy
|
|
35
37
|
p e
|
36
38
|
p e.backtrace
|
37
39
|
end
|
38
|
-
|
40
|
+
__error_response__(e)
|
41
|
+
end
|
42
|
+
|
43
|
+
def __invoke_get__(sym, req)
|
44
|
+
return send(sym, req) if respond_to?(sym)
|
45
|
+
|
46
|
+
err = respond_to?(:"#{sym}!") ? Syntropy::Error.method_not_allowed : Syntropy::Error.not_found
|
47
|
+
raise err
|
48
|
+
end
|
49
|
+
|
50
|
+
def __invoke_post__(sym, req)
|
51
|
+
sym_post = :"#{sym}!"
|
52
|
+
return send(sym_post, req) if respond_to?(sym_post)
|
53
|
+
|
54
|
+
err = respond_to?(sym) ? Syntropy::Error.method_not_allowed : Syntropy::Error.not_found
|
55
|
+
raise err
|
39
56
|
end
|
40
57
|
|
41
58
|
INTERNAL_SERVER_ERROR = Qeweney::Status::INTERNAL_SERVER_ERROR
|
42
59
|
|
43
|
-
def
|
60
|
+
def __error_response__(err)
|
44
61
|
http_status = err.respond_to?(:http_status) ? err.http_status : INTERNAL_SERVER_ERROR
|
45
|
-
error_name = err.class.name
|
62
|
+
error_name = err.class.name.split('::').last
|
46
63
|
[{ status: error_name, message: err.message }, http_status]
|
47
64
|
end
|
48
65
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'etc'
|
4
|
+
|
5
|
+
module Syntropy
|
6
|
+
module SideRun
|
7
|
+
class << self
|
8
|
+
def call(machine, &block)
|
9
|
+
setup if !@queue
|
10
|
+
|
11
|
+
# TODO: share mailboxes, acquire them with e.g. with_mailbox { |mbox| ... }
|
12
|
+
mailbox = Thread.current[:fiber_mailbox] ||= UM::Queue.new
|
13
|
+
machine.push(@queue, [mailbox, block])
|
14
|
+
result = machine.shift(mailbox)
|
15
|
+
result.is_a?(Exception) ? (raise result) : result
|
16
|
+
end
|
17
|
+
|
18
|
+
def setup
|
19
|
+
@queue = UM::Queue.new
|
20
|
+
count = (Etc.nprocessors - 1).clamp(2..6)
|
21
|
+
@workers = count.times.map {
|
22
|
+
Thread.new { side_run_worker(@queue) }
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def side_run_worker(queue)
|
27
|
+
machine = UM.new
|
28
|
+
loop { handle_request(machine, queue) }
|
29
|
+
rescue UM::Terminate
|
30
|
+
# # We can also add a timeout here
|
31
|
+
# t0 = Time.now
|
32
|
+
# while !queue.empty? && (Time.now - t0) < 10
|
33
|
+
# handle_request(machine, queue)
|
34
|
+
# end
|
35
|
+
end
|
36
|
+
|
37
|
+
def handle_request(machine, queue)
|
38
|
+
response_mailbox, closure = machine.shift(queue)
|
39
|
+
result = closure.call
|
40
|
+
machine.push(response_mailbox, result)
|
41
|
+
rescue Exception => e
|
42
|
+
machine.push(response_mailbox, e)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/syntropy/version.rb
CHANGED
data/lib/syntropy.rb
CHANGED
@@ -8,64 +8,29 @@ require 'syntropy/errors'
|
|
8
8
|
require 'syntropy/connection_pool'
|
9
9
|
require 'syntropy/module'
|
10
10
|
require 'syntropy/rpc_api'
|
11
|
+
require 'syntropy/side_run'
|
11
12
|
require 'syntropy/app'
|
13
|
+
require 'syntropy/request_extensions'
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
@ctx ||= {}
|
16
|
-
end
|
17
|
-
|
18
|
-
def validate_param(name, *clauses)
|
19
|
-
value = query[name]
|
20
|
-
clauses.each do |c|
|
21
|
-
valid = param_is_valid?(value, c)
|
22
|
-
raise(Syntropy::ValidationError, 'Validation error') if !valid
|
23
|
-
value = param_convert(value, c)
|
24
|
-
end
|
25
|
-
value
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
BOOL_REGEXP = /^(t|f|true|false|on|off|1|0|yes|no)$/
|
31
|
-
BOOL_TRUE_REGEXP = /^(t|true|on|1|yes)$/
|
32
|
-
INTEGER_REGEXP = /^[\+\-]?[0-9]+$/
|
33
|
-
FLOAT_REGEXP = /^[\+\-]?[0-9]+(\.[0-9]+)?$/
|
15
|
+
module Syntropy
|
16
|
+
Status = Qeweney::Status
|
34
17
|
|
35
|
-
|
36
|
-
|
37
|
-
return (value && value =~ BOOL_REGEXP)
|
38
|
-
elsif cond == Integer
|
39
|
-
return (value && value =~ INTEGER_REGEXP)
|
40
|
-
elsif cond == Float
|
41
|
-
return (value && value =~ FLOAT_REGEXP)
|
42
|
-
elsif cond.is_a?(Array)
|
43
|
-
return cond.any? { |c| param_is_valid?(value, c) }
|
44
|
-
end
|
18
|
+
class << self
|
19
|
+
attr_accessor :machine
|
45
20
|
|
46
|
-
|
47
|
-
|
21
|
+
def side_run(&block)
|
22
|
+
raise 'Syntropy.machine not set' if !@machine
|
48
23
|
|
49
|
-
|
50
|
-
if klass == :bool
|
51
|
-
value = value =~ BOOL_TRUE_REGEXP ? true : false
|
52
|
-
elsif klass == Integer
|
53
|
-
value = value.to_i
|
54
|
-
elsif klass == Float
|
55
|
-
value = value.to_f
|
56
|
-
else
|
57
|
-
value
|
24
|
+
SideRun.call(@machine, &block)
|
58
25
|
end
|
59
26
|
end
|
60
|
-
end
|
61
27
|
|
62
|
-
module Syntropy
|
63
28
|
def colorize(color_code)
|
64
29
|
"\e[#{color_code}m#{self}\e[0m"
|
65
30
|
end
|
66
31
|
|
67
32
|
GREEN = "\e[32m"
|
68
|
-
|
33
|
+
CLEAR = "\e[0m"
|
69
34
|
YELLOW = "\e[33m"
|
70
35
|
|
71
36
|
BANNER = (
|
@@ -73,10 +38,10 @@ module Syntropy
|
|
73
38
|
" #{GREEN}\n"\
|
74
39
|
" #{GREEN} ooo\n"\
|
75
40
|
" #{GREEN}ooooo\n"\
|
76
|
-
" #{GREEN} ooo vvv #{
|
77
|
-
" #{GREEN} o vvvvv #{
|
78
|
-
" #{GREEN} #{YELLOW}|#{GREEN} vvv o #{
|
41
|
+
" #{GREEN} ooo vvv #{CLEAR}Syntropy - a web framework for Ruby\n"\
|
42
|
+
" #{GREEN} o vvvvv #{CLEAR}--------------------------------------\n"\
|
43
|
+
" #{GREEN} #{YELLOW}|#{GREEN} vvv o #{CLEAR}https://github.com/noteflakes/syntropy\n"\
|
79
44
|
" #{GREEN} :#{YELLOW}|#{GREEN}:::#{YELLOW}|#{GREEN}::#{YELLOW}|#{GREEN}:\n"\
|
80
|
-
"
|
45
|
+
"#{YELLOW}+++++++++++++++++++++++++++++++++++++++++++++++++++++++++\e[0m\n\n"
|
81
46
|
)
|
82
47
|
end
|
data/syntropy.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |s|
|
|
25
25
|
s.add_dependency 'json', '2.12.2'
|
26
26
|
s.add_dependency 'papercraft', '1.4'
|
27
27
|
s.add_dependency 'qeweney', '0.21'
|
28
|
-
s.add_dependency 'tp2', '0.
|
28
|
+
s.add_dependency 'tp2', '0.13.2'
|
29
29
|
s.add_dependency 'uringmachine', '0.15'
|
30
30
|
|
31
31
|
s.add_dependency 'listen', '3.9.0'
|
data/test/app/baz.rb
ADDED