syntropy 0.32.0 → 0.34.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/TODO.md +0 -39
- data/cmd/console.rb +18 -7
- data/cmd/serve.rb +26 -20
- data/cmd/test.rb +90 -21
- data/examples/blog/.gitignore +1 -0
- data/examples/blog/app/_lib/database.rb +13 -0
- data/examples/blog/app/_lib/{post_store.rb → posts.rb} +3 -1
- data/examples/blog/app/posts/[id]/edit.rb +2 -2
- data/examples/blog/app/posts/[id]/index.rb +8 -5
- data/examples/blog/app/posts/index.rb +7 -5
- data/examples/blog/app/posts/new.rb +1 -1
- data/examples/blog/config/development.rb +5 -0
- data/examples/blog/config/production.rb +4 -0
- data/examples/blog/config/test.rb +5 -0
- data/examples/blog/test/test_posts.rb +65 -0
- data/examples/mcp-oauth/app/oauth/token.rb +1 -1
- data/examples/mcp-oauth/test/test_app.rb +2 -20
- data/examples/mcp-oauth/test/test_oauth.rb +93 -217
- data/lib/syntropy/app.rb +48 -40
- data/lib/syntropy/applets/builtin/auto_refresh/watch.sse.rb +1 -1
- data/lib/syntropy/db/schema.rb +1 -1
- data/lib/syntropy/db/store.rb +2 -0
- data/lib/syntropy/errors.rb +6 -2
- data/lib/syntropy/http/client.rb +1 -0
- data/lib/syntropy/http/server_connection.rb +15 -13
- data/lib/syntropy/json_api.rb +27 -1
- data/lib/syntropy/logger.rb +81 -27
- data/lib/syntropy/markdown.rb +61 -32
- data/lib/syntropy/mime_types.rb +9 -5
- data/lib/syntropy/module_loader.rb +25 -13
- data/lib/syntropy/papercraft_extensions.rb +2 -2
- data/lib/syntropy/request/mock_adapter.rb +10 -8
- data/lib/syntropy/request/request_info.rb +91 -0
- data/lib/syntropy/request/response.rb +3 -14
- data/lib/syntropy/request/validation.rb +1 -0
- data/lib/syntropy/request.rb +55 -14
- data/lib/syntropy/routing_tree.rb +27 -28
- data/lib/syntropy/session.rb +198 -0
- data/lib/syntropy/side_run.rb +25 -2
- data/lib/syntropy/test.rb +168 -2
- data/lib/syntropy/utils.rb +53 -18
- data/lib/syntropy/version.rb +1 -1
- data/lib/syntropy.rb +44 -10
- data/syntropy.gemspec +1 -0
- data/test/bm_router_proc.rb +4 -4
- data/test/fixtures/app/class_instance.rb +5 -0
- data/test/fixtures/app/http.rb +5 -0
- data/test/fixtures/app/post_ct.rb +5 -0
- data/test/fixtures/app/singleton.rb +3 -0
- data/test/test_app.rb +13 -52
- data/test/test_caching.rb +2 -2
- data/test/test_db_schema.rb +1 -1
- data/test/test_http_server_connection.rb +11 -8
- data/test/test_module_loader.rb +5 -2
- data/test/test_request_session.rb +254 -0
- data/test/test_response.rb +0 -19
- data/test/test_routing_tree.rb +69 -69
- data/test/test_server.rb +5 -9
- data/test/test_test.rb +70 -0
- metadata +67 -42
- data/examples/blog/app/_setup.rb +0 -4
- data/examples/mcp-oauth/test/helper.rb +0 -9
- /data/test/{app → fixtures/app}/.well-known/foo.rb +0 -0
- /data/test/{app → fixtures/app}/_hook.rb +0 -0
- /data/test/{app → fixtures/app}/_layout/default.rb +0 -0
- /data/test/{app → fixtures/app}/_lib/callable.rb +0 -0
- /data/test/{app → fixtures/app}/_lib/dep.rb +0 -0
- /data/test/{app → fixtures/app}/_lib/env.rb +0 -0
- /data/test/{app → fixtures/app}/_lib/klass.rb +0 -0
- /data/test/{app → fixtures/app}/_lib/missing-export.rb +0 -0
- /data/test/{app → fixtures/app}/_lib/self.rb +0 -0
- /data/test/{app → fixtures/app}/about/_error.rb +0 -0
- /data/test/{app → fixtures/app}/about/foo.md +0 -0
- /data/test/{app → fixtures/app}/about/index.rb +0 -0
- /data/test/{app → fixtures/app}/about/raise.rb +0 -0
- /data/test/{app → fixtures/app}/api+.rb +0 -0
- /data/test/{app → fixtures/app}/assets/style.css +0 -0
- /data/test/{app → fixtures/app}/bad_mod.rb +0 -0
- /data/test/{app → fixtures/app}/bar.rb +0 -0
- /data/test/{app → fixtures/app}/baz.rb +0 -0
- /data/test/{app → fixtures/app}/by_method.rb +0 -0
- /data/test/{app → fixtures/app}/deps.rb +0 -0
- /data/test/{app → fixtures/app}/index.html +0 -0
- /data/test/{app → fixtures/app}/mod/bar/index+.rb +0 -0
- /data/test/{app → fixtures/app}/mod/foo/index.rb +0 -0
- /data/test/{app → fixtures/app}/mod/path/a.rb +0 -0
- /data/test/{app → fixtures/app}/mod/path/b.rb +0 -0
- /data/test/{app → fixtures/app}/params/[foo].rb +0 -0
- /data/test/{app → fixtures/app}/rss.rb +0 -0
- /data/test/{app → fixtures/app}/tmp.rb +0 -0
- /data/test/{app_custom → fixtures/app_custom}/_site.rb +0 -0
- /data/test/{app_multi_site → fixtures/app_multi_site}/_site.rb +0 -0
- /data/test/{app_multi_site → fixtures/app_multi_site}/bar.baz/index.html +0 -0
- /data/test/{app_multi_site → fixtures/app_multi_site}/foo.bar/index.html +0 -0
- /data/test/{app_setup → fixtures/app_setup}/_setup.rb +0 -0
- /data/test/{app_setup → fixtures/app_setup}/index.rb +0 -0
- /data/test/{app_with_schema → fixtures/app_with_schema}/_schema/2026-01-02-foo.rb +0 -0
- /data/test/{app_with_schema → fixtures/app_with_schema}/_schema/2026-05-30-bar.rb +0 -0
- /data/test/{schema → fixtures/schema}/2026-01-02-foo.rb +0 -0
- /data/test/{schema → fixtures/schema}/2026-05-30-bar.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 29b7fe87a7350db0c154f0ee25b1c6992c81aead3938cec537e383dc5e3f6a62
|
|
4
|
+
data.tar.gz: 7dcf8de792102d368aefed72ced1ac3c2e5afd1fe0aea3ac106150fa59862a6b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 904b3740de777c83a631b4b0b37f72ee22b698b2f1618110a906c6ca5adf979f0c46c84af08d1ae228f8b9aa90bcb54093e88de9c18dcf3961d72ea7e0d94d2c
|
|
7
|
+
data.tar.gz: '0081303c531b38423d94f43423efcdcd80852201b0826f0078b276618b03dc7cde09e24dd599e8cee58ddf4ae8ce1f226d4684b4aa8592407728b9f911e30cc5'
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# 0.34.0 2026-06-03
|
|
2
|
+
|
|
3
|
+
- Streamline options for CLI commands
|
|
4
|
+
- Remove `App#setup_db`
|
|
5
|
+
- Add support for config modules (e.g. `config/development.rb`)
|
|
6
|
+
- Add support for running modes: production, development, test
|
|
7
|
+
|
|
8
|
+
# 0.33.0 2026-06-02
|
|
9
|
+
|
|
10
|
+
- Fix `ModuleLoader` to load a module only once
|
|
11
|
+
- Improve testing tools, add `Syntropy::Test` class
|
|
12
|
+
- Implement `Request#session`, `Request#flash`
|
|
13
|
+
- Improve `Request#set_cookie`
|
|
14
|
+
|
|
1
15
|
# 0.32.0 2026-06-01
|
|
2
16
|
|
|
3
17
|
- Ensure HTTP request body is consumed (skipped) before treating next request
|
data/TODO.md
CHANGED
|
@@ -1,44 +1,5 @@
|
|
|
1
1
|
## Immediate
|
|
2
2
|
|
|
3
|
-
- [ ] Session
|
|
4
|
-
|
|
5
|
-
https://guides.rubyonrails.org/action_controller_overview.html#session
|
|
6
|
-
|
|
7
|
-
We want something that offers the same features as in Ruby on Rails. A storage
|
|
8
|
-
space for session metadata which can include a user_id, flash messages etc.
|
|
9
|
-
|
|
10
|
-
- The session is attached to the request, and is valid for the browser
|
|
11
|
-
session.
|
|
12
|
-
- The session is a KV store. It can be used to store any data relevant to the
|
|
13
|
-
user's browser session.
|
|
14
|
-
- Each session has a unique ID and that ID is passed to the browser as a
|
|
15
|
-
non-persistent cookie.
|
|
16
|
-
- A session expires if not used (e.g. after 7 days)
|
|
17
|
-
- Session storage either in memory or in DB
|
|
18
|
-
- The session is generated (and session cookie set) upon first write to
|
|
19
|
-
session.
|
|
20
|
-
- Session `Set-Cookie` header should be injected into the HTTP response.
|
|
21
|
-
- The entire session info can be stored in a cookie, provided it does not
|
|
22
|
-
exceed 4KB.
|
|
23
|
-
|
|
24
|
-
```ruby
|
|
25
|
-
req.session[:flash] = 'Title cannot be empty!'
|
|
26
|
-
req.redirect '/blah'
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
- [ ] Flash messages
|
|
30
|
-
|
|
31
|
-
- Flash messages are a sub-feature of session storage and are used to relay
|
|
32
|
-
messages from one request to the next in the same session.
|
|
33
|
-
- Flash messages have more complex semantics:
|
|
34
|
-
- There can be more than one.
|
|
35
|
-
- They have types - notice, alert, etc.
|
|
36
|
-
- They normally disappear on the next request (i.e. the session cookie
|
|
37
|
-
should be updated in the response with Set-Cookie).
|
|
38
|
-
- But, you can keep them for the next request with `req.session.flash.keep`
|
|
39
|
-
- The flash messages could be used for the current request with
|
|
40
|
-
`req.session.flash.now`
|
|
41
|
-
|
|
42
3
|
- [ ] Collection - treat directories and files as collections of data.
|
|
43
4
|
|
|
44
5
|
Kind of similar to the routing tree, but instead of routes it just takes a
|
data/cmd/console.rb
CHANGED
|
@@ -2,16 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative '../lib/syntropy'
|
|
4
4
|
require 'optparse'
|
|
5
|
+
require 'fileutils'
|
|
5
6
|
|
|
6
7
|
env = {
|
|
8
|
+
app_root: File.join(FileUtils.pwd, 'app'),
|
|
9
|
+
config_root: File.join(FileUtils.pwd, 'config'),
|
|
10
|
+
mode: ENV['SYNTROPY_MODE'] || 'development',
|
|
7
11
|
mount_path: '/',
|
|
8
|
-
logger: true,
|
|
9
12
|
builtin_applet_path: '/.syntropy',
|
|
13
|
+
logger: true,
|
|
10
14
|
watch_files: true
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
parser = OptionParser.new do |o|
|
|
14
|
-
o.banner = 'Usage: syntropy serve [options]
|
|
18
|
+
o.banner = 'Usage: syntropy serve [options]'
|
|
19
|
+
|
|
20
|
+
o.on('-a', '--app PATH', 'Set app directory (default: ./app') do |path|
|
|
21
|
+
env[:app_root] = path
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
o.on('-c', '--config PATH', 'Set config directory (default: ./config') do |path|
|
|
25
|
+
env[:config_root] = path
|
|
26
|
+
end
|
|
15
27
|
|
|
16
28
|
o.on('-h', '--help', 'Show this help message') do
|
|
17
29
|
puts o
|
|
@@ -19,7 +31,6 @@ parser = OptionParser.new do |o|
|
|
|
19
31
|
end
|
|
20
32
|
|
|
21
33
|
o.on('-m', '--mount PATH', 'Set mount path (default: /)') do |path|
|
|
22
|
-
p mount: path
|
|
23
34
|
env[:mount_path] = path
|
|
24
35
|
env[:builtin_applet_path] = File.join(path, '.syntropy')
|
|
25
36
|
end
|
|
@@ -43,11 +54,11 @@ rescue StandardError => e
|
|
|
43
54
|
exit
|
|
44
55
|
end
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
Syntropy.dev_mode = env[:mode] == 'development'
|
|
58
|
+
Syntropy.load_config(env)
|
|
48
59
|
|
|
49
|
-
if !File.directory?(env[:
|
|
50
|
-
puts "#{File.expand_path(env[:
|
|
60
|
+
if !File.directory?(env[:app_root])
|
|
61
|
+
puts "#{File.expand_path(env[:app_root])} Not a directory"
|
|
51
62
|
exit
|
|
52
63
|
end
|
|
53
64
|
|
data/cmd/serve.rb
CHANGED
|
@@ -2,19 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative '../lib/syntropy'
|
|
4
4
|
require 'optparse'
|
|
5
|
+
require 'fileutils'
|
|
5
6
|
|
|
6
7
|
env = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
app_root: File.join(FileUtils.pwd, 'app'),
|
|
9
|
+
config_root: File.join(FileUtils.pwd, 'config'),
|
|
10
|
+
mode: ENV['SYNTROPY_MODE'] || 'development',
|
|
11
|
+
mount_path: '/',
|
|
12
|
+
builtin_applet_path: '/.syntropy',
|
|
13
|
+
logger: true,
|
|
14
|
+
server_extensions: {
|
|
11
15
|
date: true,
|
|
12
16
|
name: 'Syntropy'
|
|
13
17
|
}
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
parser = OptionParser.new do |o|
|
|
17
|
-
o.banner = 'Usage: syntropy serve [options]
|
|
21
|
+
o.banner = 'Usage: syntropy serve [options]'
|
|
22
|
+
|
|
23
|
+
o.on('-a', '--app PATH', 'Set app directory (default: ./app') do |path|
|
|
24
|
+
env[:app_root] = path
|
|
25
|
+
end
|
|
18
26
|
|
|
19
27
|
o.on('-b', '--bind BIND', String,
|
|
20
28
|
'Bind address (default: http://0.0.0.0:1234). You can specify this flag multiple times to bind to multiple addresses.') do
|
|
@@ -22,14 +30,8 @@ parser = OptionParser.new do |o|
|
|
|
22
30
|
env[:bind] << it
|
|
23
31
|
end
|
|
24
32
|
|
|
25
|
-
o.on('-
|
|
26
|
-
env[:
|
|
27
|
-
env[:logger] = nil
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
o.on('-d', '--dev', 'Development mode') do
|
|
31
|
-
env[:dev_mode] = true
|
|
32
|
-
env[:watch_files] = true
|
|
33
|
+
o.on('-c', '--config PATH', 'Set config directory (default: ./config') do |path|
|
|
34
|
+
env[:config_root] = path
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
o.on('-h', '--help', 'Show this help message') do
|
|
@@ -38,7 +40,6 @@ parser = OptionParser.new do |o|
|
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
o.on('-m', '--mount PATH', 'Set mount path (default: /)') do |path|
|
|
41
|
-
p mount: path
|
|
42
43
|
env[:mount_path] = path
|
|
43
44
|
env[:builtin_applet_path] = File.join(path, '.syntropy')
|
|
44
45
|
end
|
|
@@ -51,6 +52,11 @@ parser = OptionParser.new do |o|
|
|
|
51
52
|
env[:server_extensions] = nil
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
o.on('-s', '--silent', 'Silent mode') do
|
|
56
|
+
env[:banner] = nil
|
|
57
|
+
env[:logger] = nil
|
|
58
|
+
end
|
|
59
|
+
|
|
54
60
|
o.on('-v', '--version', 'Show version') do
|
|
55
61
|
require 'syntropy/version'
|
|
56
62
|
puts "Syntropy version #{Syntropy::VERSION}"
|
|
@@ -72,24 +78,24 @@ rescue StandardError => e
|
|
|
72
78
|
exit
|
|
73
79
|
end
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
Syntropy.dev_mode = env[:mode] == 'development'
|
|
82
|
+
Syntropy.load_config(env)
|
|
83
|
+
env[:watch_files] = Syntropy.dev_mode
|
|
77
84
|
|
|
78
|
-
if !File.directory?(env[:
|
|
79
|
-
puts "#{File.expand_path(env[:
|
|
85
|
+
if !File.directory?(env[:app_root])
|
|
86
|
+
puts "#{File.expand_path(env[:app_root])} Not a directory"
|
|
80
87
|
exit
|
|
81
88
|
end
|
|
82
89
|
|
|
83
90
|
puts env[:banner] if env[:banner]
|
|
84
91
|
env[:banner] = false
|
|
85
92
|
|
|
86
|
-
|
|
87
93
|
# We set Syntropy.machine so we can reference it from anywhere
|
|
88
94
|
env[:machine] = Syntropy.machine = UM.new
|
|
89
95
|
env[:logger] = env[:logger] && Syntropy::Logger.new(env[:machine], **env)
|
|
90
96
|
|
|
91
97
|
require 'syntropy/version'
|
|
92
|
-
require 'syntropy/dev_mode' if
|
|
98
|
+
require 'syntropy/dev_mode' if Syntropy.dev_mode
|
|
93
99
|
|
|
94
100
|
app = Syntropy::App.load(env)
|
|
95
101
|
Syntropy.run(env) { app.call(it) }
|
data/cmd/test.rb
CHANGED
|
@@ -1,40 +1,109 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'optparse'
|
|
5
|
+
|
|
6
|
+
pwd = FileUtils.pwd
|
|
7
|
+
env = {
|
|
8
|
+
app_root: File.join(FileUtils.pwd, 'app'),
|
|
9
|
+
config_root: File.join(FileUtils.pwd, 'config'),
|
|
10
|
+
test_root: File.join(pwd, 'test'),
|
|
11
|
+
mode: 'test',
|
|
12
|
+
mount_path: '/'
|
|
13
|
+
}
|
|
14
|
+
MINITEST_ARGV = []
|
|
15
|
+
|
|
16
|
+
parser = OptionParser.new do |o|
|
|
17
|
+
o.banner = 'Usage: syntropy test [options]'
|
|
18
|
+
|
|
19
|
+
o.on('-a', '--app PATH', 'Set app directory (default: ./app') do |path|
|
|
20
|
+
env[:app_root] = path
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
o.on('-c', '--config PATH', 'Set config directory (default: ./config') do |path|
|
|
24
|
+
env[:config_root] = path
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
o.on('-h', '--help', 'Show this help message') do
|
|
28
|
+
puts o
|
|
29
|
+
exit
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
o.on('-m', '--mount PATH', 'Set mount path (default: /)') do |path|
|
|
33
|
+
env[:mount_path] = path
|
|
34
|
+
env[:builtin_applet_path] = File.join(path, '.syntropy')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
o.on('-n', '--name NAME', 'Specify test to run') do |name|
|
|
38
|
+
MINITEST_ARGV << '--name' << name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
o.on('-s', '--seed SEED', 'Specify random seed') do |seed|
|
|
42
|
+
MINITEST_ARGV << '--seed' << seed
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
o.on('-t', '--test PATH', 'Set test directory (default: ./test)') do |path|
|
|
46
|
+
env[:test_root] = path
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
o.on('-V', '--verbose', 'Verbose test output') do
|
|
50
|
+
MINITEST_ARGV << '--verbose'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
o.on('-v', '--version', 'Show version') do
|
|
54
|
+
require 'syntropy/version'
|
|
55
|
+
puts "Syntropy version #{Syntropy::VERSION}"
|
|
56
|
+
exit
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
o.on('-w', '--watch', 'Watch for file changes') do
|
|
60
|
+
env[:watch_mode] = true
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
4
64
|
argv_copy = ARGV.dup
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
MSG
|
|
65
|
+
begin
|
|
66
|
+
parser.parse!
|
|
67
|
+
rescue OptionParser::InvalidOption
|
|
68
|
+
puts parser
|
|
69
|
+
exit
|
|
70
|
+
rescue StandardError => e
|
|
71
|
+
p e
|
|
72
|
+
puts e.message
|
|
73
|
+
puts e.backtrace.join("\n")
|
|
15
74
|
exit
|
|
16
75
|
end
|
|
17
76
|
|
|
18
77
|
require_relative '../lib/syntropy'
|
|
19
78
|
require_relative '../lib/syntropy/test'
|
|
20
79
|
|
|
80
|
+
Syntropy.load_config(env)
|
|
81
|
+
|
|
21
82
|
$stdout.sync = true
|
|
22
83
|
$stderr.sync = true
|
|
23
84
|
|
|
24
|
-
Dir.glob("
|
|
85
|
+
Dir.glob("#{File.expand_path(env[:test_root])}/test_*.rb").each { require(it) }
|
|
86
|
+
|
|
87
|
+
def restart_on_file_change(machine, dir, restart_argv)
|
|
25
88
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
puts "Waiting for file changes in #{FileUtils.pwd}"
|
|
29
|
-
m.file_watch(FileUtils.pwd, UM::IN_CREATE | UM::IN_DELETE | UM::IN_CLOSE_WRITE) {
|
|
30
|
-
puts "Detected changes to #{it[:fn]}, restarting"
|
|
89
|
+
machine.file_watch(dir, UM::IN_CREATE | UM::IN_DELETE | UM::IN_CLOSE_WRITE) {
|
|
90
|
+
machine.write(UM::STDOUT_FILENO, "File changed: #{it[:fn]}\n")
|
|
31
91
|
break
|
|
32
92
|
}
|
|
93
|
+
exec('ruby', __FILE__, *restart_argv)
|
|
33
94
|
end
|
|
34
95
|
|
|
35
|
-
|
|
96
|
+
Syntropy::Test.env = (env)
|
|
97
|
+
Minitest.run MINITEST_ARGV
|
|
98
|
+
|
|
36
99
|
if env[:watch_mode]
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
100
|
+
m = UM.new(size: 4)
|
|
101
|
+
m.write(UM::STDOUT_FILENO, "\n")
|
|
102
|
+
trap('SIGINT') { m.write(UM::STDOUT_FILENO, "\n"); exit! }
|
|
103
|
+
|
|
104
|
+
machine.write(UM::STDOUT_FILENO, "Waiting for file changes...\n")
|
|
105
|
+
m.join(
|
|
106
|
+
m.spin { restart_on_file_change(m, env[:app_root], argv_copy) },
|
|
107
|
+
m.spin { restart_on_file_change(m, env[:test_root], argv_copy) }
|
|
108
|
+
)
|
|
40
109
|
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
storage/*
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export self
|
|
2
|
+
|
|
3
|
+
def connection_pool
|
|
4
|
+
@connection_pool ||= DB::ConnectionPool.new(@machine, @env[:config][:storage][:path], 4)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def schema
|
|
8
|
+
DB::Schema.new(module_loader: @module_loader, schema_root: '_schema')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def migrate!
|
|
12
|
+
schema.apply(connection_pool)
|
|
13
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
DB = import '/_lib/database'
|
|
2
|
+
|
|
1
3
|
class PostStore < Syntropy::DB::Store
|
|
2
4
|
# @return [Integer] post id
|
|
3
5
|
def create(title, body)
|
|
@@ -44,4 +46,4 @@ class PostStore < Syntropy::DB::Store
|
|
|
44
46
|
end
|
|
45
47
|
end
|
|
46
48
|
|
|
47
|
-
export PostStore.new(
|
|
49
|
+
export PostStore.new(DB.connection_pool)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
@
|
|
1
|
+
@posts = import '/_lib/posts'
|
|
2
2
|
@layout = import '/_layout/default'
|
|
3
3
|
|
|
4
4
|
export http_methods
|
|
5
5
|
|
|
6
6
|
def get(req)
|
|
7
7
|
id = req.route_params['id'].to_i
|
|
8
|
-
post = @
|
|
8
|
+
post = @posts.get(id)
|
|
9
9
|
raise Syntropy::Error.not_found if !post
|
|
10
10
|
|
|
11
11
|
req.respond_html(
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
@
|
|
1
|
+
@posts = import '/_lib/posts'
|
|
2
2
|
@layout = import '/_layout/default'
|
|
3
3
|
|
|
4
4
|
export http_methods
|
|
5
5
|
|
|
6
6
|
def get(req)
|
|
7
7
|
id = req.route_params['id'].to_i
|
|
8
|
-
post = @
|
|
8
|
+
post = @posts.get(id)
|
|
9
9
|
raise Syntropy::Error.not_found if !post
|
|
10
10
|
|
|
11
11
|
req.respond_html(
|
|
12
|
-
@template.render(post:)
|
|
12
|
+
@template.render(post:, req:)
|
|
13
13
|
)
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -21,23 +21,26 @@ def post(req)
|
|
|
21
21
|
title = req.validate(data['title'], String, /.+/)
|
|
22
22
|
body = req.validate(data['body'], String, /.+/)
|
|
23
23
|
|
|
24
|
-
updated = @
|
|
24
|
+
updated = @posts.update(id, title, body)
|
|
25
25
|
raise BadRequestError, "Failed to update post" if updated != 1
|
|
26
26
|
|
|
27
|
+
req.flash[:notice] = 'Post was successfully updated.'
|
|
27
28
|
req.redirect "/posts/#{id}", Syntropy::HTTP::SEE_OTHER
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def delete(req)
|
|
31
32
|
id = req.route_params['id'].to_i
|
|
32
33
|
|
|
33
|
-
deleted = @
|
|
34
|
+
deleted = @posts.delete(id)
|
|
34
35
|
raise BadRequestError, "Failed to delete post" if deleted != 1
|
|
35
36
|
|
|
37
|
+
req.flash[:notice] = 'Post was successfully destroyed.'
|
|
36
38
|
req.redirect "/posts", Syntropy::HTTP::SEE_OTHER
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
@template = @layout.apply { |post:, **props|
|
|
40
42
|
h1 "My blog"
|
|
43
|
+
p props[:req]&.flash[:notice], style: 'color: green'
|
|
41
44
|
div {
|
|
42
45
|
h2 {
|
|
43
46
|
a post[:title]
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
@
|
|
1
|
+
@posts = import '_lib/posts'
|
|
2
2
|
@layout = import '_layout/default'
|
|
3
3
|
|
|
4
4
|
export http_methods
|
|
5
5
|
|
|
6
6
|
def get(req)
|
|
7
|
-
posts = @
|
|
7
|
+
posts = @posts.get_all
|
|
8
8
|
req.respond_html(
|
|
9
|
-
@template.render(posts:)
|
|
9
|
+
@template.render(posts:, req:)
|
|
10
10
|
)
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -14,13 +14,15 @@ def post(req)
|
|
|
14
14
|
data = req.get_form_data
|
|
15
15
|
title = req.validate(data['title'], String, /.+/)
|
|
16
16
|
body = req.validate(data['body'], String, /.+/)
|
|
17
|
-
id = @
|
|
17
|
+
id = @posts.create(title, body)
|
|
18
18
|
|
|
19
|
+
req.flash[:notice] = 'Post was successfully created.'
|
|
19
20
|
req.redirect("posts/#{id}")
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
@template = @layout.apply { |**props|
|
|
23
|
-
h1 "My blog"
|
|
24
|
+
h1 "My awesome blog"
|
|
25
|
+
p props[:req]&.flash[:notice], style: 'color: green'
|
|
24
26
|
props[:posts].each { |post|
|
|
25
27
|
div {
|
|
26
28
|
h2 {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class PostsTest < Syntropy::Test
|
|
4
|
+
def setup
|
|
5
|
+
super
|
|
6
|
+
@posts = load_module('/_lib/posts')
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_get_all
|
|
10
|
+
assert_equal [], @posts.get_all
|
|
11
|
+
|
|
12
|
+
@posts.create('foo', 'bar')
|
|
13
|
+
|
|
14
|
+
assert_equal [
|
|
15
|
+
{ id: 1, title: 'foo', body: 'bar' }
|
|
16
|
+
], @posts.get_all
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_get
|
|
20
|
+
assert_nil @posts.get(1)
|
|
21
|
+
assert_nil @posts.get(2)
|
|
22
|
+
|
|
23
|
+
@posts.create('foo', 'bar')
|
|
24
|
+
|
|
25
|
+
assert_equal(
|
|
26
|
+
{ id: 1, title: 'foo', body: 'bar' },
|
|
27
|
+
@posts.get(1)
|
|
28
|
+
)
|
|
29
|
+
assert_nil @posts.get(2)
|
|
30
|
+
|
|
31
|
+
@posts.create('bar', 'baz')
|
|
32
|
+
|
|
33
|
+
assert_equal(
|
|
34
|
+
{ id: 1, title: 'foo', body: 'bar' },
|
|
35
|
+
@posts.get(1)
|
|
36
|
+
)
|
|
37
|
+
assert_equal(
|
|
38
|
+
{ id: 2, title: 'bar', body: 'baz' },
|
|
39
|
+
@posts.get(2)
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def test_update
|
|
44
|
+
assert_equal 0, @posts.update(1, 'qqq', 'ttt')
|
|
45
|
+
|
|
46
|
+
@posts.create('foo', 'bar')
|
|
47
|
+
assert_equal 1, @posts.update(1, 'qqq', 'ttt')
|
|
48
|
+
|
|
49
|
+
assert_equal [
|
|
50
|
+
{ id: 1, title: 'qqq', body: 'ttt' }
|
|
51
|
+
], @posts.get_all
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_delete
|
|
55
|
+
assert_equal 0, @posts.delete(1)
|
|
56
|
+
|
|
57
|
+
@posts.create('foo', 'bar')
|
|
58
|
+
@posts.create('bar', 'baz')
|
|
59
|
+
|
|
60
|
+
assert_equal 1, @posts.delete(1)
|
|
61
|
+
assert_equal [
|
|
62
|
+
{ id: 2, title: 'bar', body: 'baz' }
|
|
63
|
+
], @posts.get_all
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -1,26 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class AppTest < Minitest::Test
|
|
6
|
-
APP_ROOT = File.expand_path(File.join(__dir__, '../app'))
|
|
7
|
-
HTTP = Syntropy::HTTP
|
|
8
|
-
|
|
9
|
-
def setup
|
|
10
|
-
@machine = UM.new
|
|
11
|
-
@app = Syntropy::App.new(
|
|
12
|
-
root_dir: APP_ROOT,
|
|
13
|
-
mount_path: '/',
|
|
14
|
-
machine: @machine
|
|
15
|
-
)
|
|
16
|
-
@test_harness = Syntropy::TestHarness.new(@app)
|
|
17
|
-
end
|
|
18
|
-
|
|
3
|
+
class AppTest < Syntropy::Test
|
|
19
4
|
def test_root
|
|
20
|
-
req =
|
|
21
|
-
':method' => 'GET',
|
|
22
|
-
':path' => '/'
|
|
23
|
-
)
|
|
5
|
+
req = get('/')
|
|
24
6
|
assert_equal HTTP::OK, req.response_status
|
|
25
7
|
assert_match /Syntropy/, req.response_body
|
|
26
8
|
end
|