syntropy 0.32.0 → 0.33.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbf52e43aad7bcf2dc6259fd40dac6fb6f70d2215513ae89a5e72b120c4b8585
4
- data.tar.gz: 7a7fd31a1e47bf999d64ad3216bb2c77345a4eb3753df6e2a51384870d9f764f
3
+ metadata.gz: a39afb5d39d1a1c9b95e2e242b9ce55144ba6923ad4b0addee3c6c005d314ead
4
+ data.tar.gz: 0b067b50a4f27381ba495a32d8c8e471e4a64374d6eee3c62d25fb30aaa10a24
5
5
  SHA512:
6
- metadata.gz: ff3ac2fbb77785db855080dcd0302beb61332c067c30db6330540cda9a60a7ab72ad684b87772951e8498d5c6aa10b2cf57cce7f1a85932f95db889432c704a0
7
- data.tar.gz: 84ee4aa581f46c84999109bb102d9c890f9dc45b2803a52f114357981edf9150b84c187ba3a62670691e3eb8629b6396bd8f1789f882857b1558089c198f2877
6
+ metadata.gz: 9768274a2d09c8f4068625006660403e98655cfa7d7d742ed75f3ce0e31abfc0410f301d16d8d8910fcc94d7947008238a730b19b514342cbb67dac8669a4e76
7
+ data.tar.gz: 76a2d90674f7ebc34f8162e3435d81edb1ca56022ef7d490bcc6fcfa0041529f1ace321233451dd7df40867b05eb83327984634461f0986dd650db3e0913af6c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # 0.33.0 2026-06-02
2
+
3
+ - Fix `ModuleLoader` to load a module only once
4
+ - Improve testing tools, add `Syntropy::Test` class
5
+ - Implement `Request#session`, `Request#flash`
6
+ - Improve `Request#set_cookie`
7
+
1
8
  # 0.32.0 2026-06-01
2
9
 
3
10
  - 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/serve.rb CHANGED
@@ -38,7 +38,6 @@ parser = OptionParser.new do |o|
38
38
  end
39
39
 
40
40
  o.on('-m', '--mount PATH', 'Set mount path (default: /)') do |path|
41
- p mount: path
42
41
  env[:mount_path] = path
43
42
  env[:builtin_applet_path] = File.join(path, '.syntropy')
44
43
  end
@@ -83,7 +82,6 @@ end
83
82
  puts env[:banner] if env[:banner]
84
83
  env[:banner] = false
85
84
 
86
-
87
85
  # We set Syntropy.machine so we can reference it from anywhere
88
86
  env[:machine] = Syntropy.machine = UM.new
89
87
  env[:logger] = env[:logger] && Syntropy::Logger.new(env[:machine], **env)
data/cmd/test.rb CHANGED
@@ -1,17 +1,70 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- env = {}
3
+ require 'fileutils'
4
+ require 'optparse'
5
+
6
+ pwd = FileUtils.pwd
7
+ env = {
8
+ root_dir: File.join(pwd, 'app'),
9
+ test_dir: File.join(pwd, 'test'),
10
+ mount_path: '/'
11
+ }
12
+ MINITEST_ARGV = []
13
+
14
+ parser = OptionParser.new do |o|
15
+ o.banner = 'Usage: syntropy test [options]'
16
+
17
+ o.on('-h', '--help', 'Show this help message') do
18
+ puts o
19
+ exit
20
+ end
21
+
22
+ o.on('-m', '--mount PATH', 'Set mount path (default: /)') do |path|
23
+ env[:mount_path] = path
24
+ env[:builtin_applet_path] = File.join(path, '.syntropy')
25
+ end
26
+
27
+ o.on('-w', '--watch', 'Watch for file changes') do
28
+ env[:watch_mode] = true
29
+ end
30
+
31
+ o.on('-a', '--app PATH', 'Set app root (default: ./app)') do |path|
32
+ env[:root_dir] = path
33
+ end
34
+
35
+ o.on('-t', '--test PATH', 'Set test root (default: ./test)') do |path|
36
+ env[:test_dir] = path
37
+ end
38
+
39
+ o.on('-n', '--name NAME', 'Specify test to run') do |name|
40
+ MINITEST_ARGV << '--name' << name
41
+ end
42
+
43
+ o.on('-V', '--verbose', 'Verbose test output') do
44
+ MINITEST_ARGV << '--verbose'
45
+ end
46
+
47
+ o.on('-s', '--seed SEED', 'Specify random seed') do |seed|
48
+ MINITEST_ARGV << '--seed' << seed
49
+ end
50
+
51
+ o.on('-v', '--version', 'Show version') do
52
+ require 'syntropy/version'
53
+ puts "Syntropy version #{Syntropy::VERSION}"
54
+ exit
55
+ end
56
+ end
57
+
4
58
  argv_copy = ARGV.dup
5
- case ARGV[0]
6
- when '-w', '--watch'
7
- env[:watch_mode] = true
8
- ARGV.shift
9
- when '-h', '--help'
10
- puts <<~MSG
11
- Usage: syntropy test [options] [minitest options]
12
- -w, --watch Rerun tests on file system changes
13
- -h, --help Show this help message
14
- MSG
59
+ begin
60
+ parser.parse!
61
+ rescue OptionParser::InvalidOption
62
+ puts parser
63
+ exit
64
+ rescue StandardError => e
65
+ p e
66
+ puts e.message
67
+ puts e.backtrace.join("\n")
15
68
  exit
16
69
  end
17
70
 
@@ -21,20 +74,23 @@ require_relative '../lib/syntropy/test'
21
74
  $stdout.sync = true
22
75
  $stderr.sync = true
23
76
 
24
- Dir.glob("./test/test_*.rb").each { require(it) }
77
+ Dir.glob("#{env[:test_dir]}/test_*.rb").each { require(it) }
25
78
 
26
- def watch_for_file_changes
27
- m = UM.new
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"
79
+ def watch_for_file_changes(machine)
80
+ machine.write(UM::STDOUT_FILENO, "Waiting for file changes in #{FileUtils.pwd}\n")
81
+ machine.file_watch(FileUtils.pwd, UM::IN_CREATE | UM::IN_DELETE | UM::IN_CLOSE_WRITE) {
82
+ machine.write(UM::STDOUT_FILENO, "File changed: #{it[:fn]}\n")
31
83
  break
32
84
  }
33
85
  end
34
86
 
35
- Minitest.run ARGV
87
+ Syntropy::Test.env=(env)
88
+ Minitest.run MINITEST_ARGV
89
+
36
90
  if env[:watch_mode]
37
- puts
38
- watch_for_file_changes
91
+ m = UM.new(size: 4)
92
+ m.write(UM::STDOUT_FILENO, "\n")
93
+ trap('SIGINT') { m.write(UM::STDOUT_FILENO, "\n"); exit! }
94
+ watch_for_file_changes(m)
39
95
  exec("ruby", __FILE__, *argv_copy)
40
96
  end
@@ -9,7 +9,7 @@ def get(req)
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
 
@@ -24,6 +24,7 @@ def post(req)
24
24
  updated = @post_store.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
 
@@ -33,11 +34,13 @@ def delete(req)
33
34
  deleted = @post_store.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]
@@ -6,7 +6,7 @@ export http_methods
6
6
  def get(req)
7
7
  posts = @post_store.get_all
8
8
  req.respond_html(
9
- @template.render(posts:)
9
+ @template.render(posts:, req:)
10
10
  )
11
11
  end
12
12
 
@@ -16,11 +16,13 @@ def post(req)
16
16
  body = req.validate(data['body'], String, /.+/)
17
17
  id = @post_store.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
24
  h1 "My blog"
25
+ p props[:req]&.flash[:notice], style: 'color: green'
24
26
  props[:posts].each { |post|
25
27
  div {
26
28
  h2 {
@@ -1,26 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'helper'
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 = @test_harness.request(
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