syntropy 0.37.0 → 0.38.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 +8 -0
- data/Gemfile +4 -0
- data/TODO.md +4 -0
- data/bin/syntropy +12 -2
- data/cmd/help.rb +4 -0
- data/cmd/version.rb +14 -0
- data/examples/blog/app/posts/[id]/edit.rb +1 -1
- data/examples/blog/app/posts/[id]/index.rb +1 -1
- data/examples/blog/app/posts/index.rb +1 -1
- data/examples/blog/app/posts/new.rb +1 -1
- data/examples/github/app/[org]/[repo]/index.rb +0 -0
- data/examples/github/app/[org]/[repo]/issues/[id].rb +0 -0
- data/examples/github/app/[org]/index.rb +0 -0
- data/examples/github/app/collections.rb +0 -0
- data/examples/github/app/explore.rb +0 -0
- data/examples/github/app/index.rb +0 -0
- data/lib/syntropy/app.rb +6 -2
- data/lib/syntropy/controller_extensions.rb +136 -0
- data/lib/syntropy/module_loader.rb +46 -40
- data/lib/syntropy/routing_tree.rb +14 -14
- data/lib/syntropy/test.rb +28 -10
- data/lib/syntropy/version.rb +1 -1
- data/lib/syntropy.rb +0 -3
- data/test/bm_router_proc.rb +14 -15
- data/test/fixtures/app/bad_mod_arity.rb +3 -0
- data/test/fixtures/app/by_method.rb +1 -1
- data/test/fixtures/app/post_ct.rb +1 -1
- data/test/fixtures/app_errors/_error.rb +3 -0
- data/test/fixtures/app_errors/foo/_error.rb +3 -0
- data/test/fixtures/app_errors/foo/bar/_error.rb +3 -0
- data/test/fixtures/app_errors/foo/bar/baz/index.rb +3 -0
- data/test/fixtures/app_errors/foo/bar/index.rb +3 -0
- data/test/fixtures/app_errors/foo/index.rb +3 -0
- data/test/fixtures/app_errors/index.rb +3 -0
- data/test/fixtures/app_hooks/_hook.rb +4 -0
- data/test/fixtures/app_hooks/foo/_hook.rb +4 -0
- data/test/fixtures/app_hooks/foo/bar/_hook.rb +4 -0
- data/test/fixtures/app_hooks/foo/bar/baz/_hook.rb +4 -0
- data/test/fixtures/app_hooks/foo/bar/baz/index.rb +3 -0
- data/test/fixtures/app_hooks/foo/bar/index.rb +3 -0
- data/test/fixtures/app_hooks/foo/index.rb +3 -0
- data/test/fixtures/app_hooks/index.rb +3 -0
- data/test/fixtures/app_multi_site/_site.rb +1 -1
- data/test/fixtures/controllers/by_host/bar.com/index.rb +3 -0
- data/test/fixtures/controllers/by_host/foo.com/index.rb +3 -0
- data/test/fixtures/controllers/by_host_dir.rb +1 -0
- data/test/fixtures/controllers/by_host_dir_map.rb +4 -0
- data/test/fixtures/controllers/by_host_map.rb +4 -0
- data/test/fixtures/controllers/by_http_method.rb +9 -0
- data/test/fixtures/controllers/jsonrpc_endpoint.rb +0 -0
- data/test/test_app.rb +86 -1
- data/test/test_controller.rb +71 -0
- data/test/test_module_loader.rb +42 -3
- data/test/test_routing_tree.rb +1 -0
- data/test/test_test.rb +1 -1
- metadata +33 -2
- data/lib/syntropy/utils.rb +0 -87
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85989e43beae58a319d108e1c8cf42f8265fdfaa0675c0fa2b0b8c8df5dbfbd1
|
|
4
|
+
data.tar.gz: cd2dd5db7add2b3e3aa74f2a3fa02da3c1896b808a46be16b088e264875aec10
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3cd0fb5d085456bc8d51db049fecaf11f000071cde6b19dd224c7ec422e1fb749be3ba92dfd5b8a43e3e95313f99b1ffed5f926ca37abd5377e2badd249576f3
|
|
7
|
+
data.tar.gz: c3a2b789fe2c9cfbff79b2245381f3ee76bad2e905e3f8408df2666af1495b51f93988d7085c1957330b194237f366e0999b43eb25b8e0e42954f07146aaa5cb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
# 0.38.0 2026-06-13
|
|
2
|
+
|
|
3
|
+
- Reimplement controller extensions: `dispatch_by_host`,
|
|
4
|
+
`dispatch_by_http_method`
|
|
5
|
+
- Fix middleware composition
|
|
6
|
+
- Add CLI command shortcuts
|
|
7
|
+
- Rename `Syntropy::Module` to `Syntropy::ModuleContext`
|
|
8
|
+
|
|
1
9
|
# 0.37.0 2026-06-07
|
|
2
10
|
|
|
3
11
|
- Call `IO#clear` before closing server connection
|
data/Gemfile
CHANGED
data/TODO.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
## Immediate
|
|
2
2
|
|
|
3
|
+
- [ ] Controllers
|
|
4
|
+
- [ ] Streamline names of ready-made control methods:
|
|
5
|
+
- [ ] dispatch_json_rpc
|
|
6
|
+
|
|
3
7
|
- [ ] Collection - treat directories and files as collections of data.
|
|
4
8
|
|
|
5
9
|
Kind of similar to the routing tree, but instead of routes it just takes a
|
data/bin/syntropy
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require_relative '../cmd/_banner'
|
|
5
|
-
|
|
6
4
|
def cmd_fn(cmd)= File.join(__dir__, "../cmd/#{cmd}.rb")
|
|
7
5
|
|
|
8
6
|
cmd = ARGV.shift || 'help'
|
|
9
7
|
cmd = 'help' if cmd !~ /^[a-z]+$/
|
|
10
8
|
|
|
9
|
+
SHORTCUTS = {
|
|
10
|
+
'c' => 'console',
|
|
11
|
+
'n' => 'new',
|
|
12
|
+
's' => 'serve',
|
|
13
|
+
't' => 'test',
|
|
14
|
+
'v' => 'version'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (target_cmd = SHORTCUTS[cmd])
|
|
18
|
+
cmd = target_cmd
|
|
19
|
+
end
|
|
20
|
+
|
|
11
21
|
fn = cmd_fn(cmd)
|
|
12
22
|
fn = cmd_fn('help') if !File.file?(fn)
|
|
13
23
|
|
data/cmd/help.rb
CHANGED
data/cmd/version.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'syntropy/version'
|
|
4
|
+
require 'uringmachine/version'
|
|
5
|
+
require_relative './_banner'
|
|
6
|
+
|
|
7
|
+
VERSION = <<~MSG
|
|
8
|
+
Syntropy version #{Syntropy::VERSION}
|
|
9
|
+
UringMachine version #{UringMachine::VERSION}
|
|
10
|
+
Ruby version #{RUBY_VERSION}
|
|
11
|
+
MSG
|
|
12
|
+
|
|
13
|
+
$stdout << SYNTROPY_BANNER
|
|
14
|
+
$stdout << VERSION
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
data/lib/syntropy/app.rb
CHANGED
|
@@ -9,6 +9,7 @@ require 'syntropy/errors'
|
|
|
9
9
|
require 'syntropy/module_loader'
|
|
10
10
|
require 'syntropy/routing_tree'
|
|
11
11
|
require 'syntropy/mime_types'
|
|
12
|
+
require 'syntropy/controller_extensions'
|
|
12
13
|
|
|
13
14
|
module Syntropy
|
|
14
15
|
# The App implements a Syntropy application. It is responsible for handling
|
|
@@ -34,7 +35,7 @@ module Syntropy
|
|
|
34
35
|
fn = File.join(env[:app_root], '_site.rb')
|
|
35
36
|
return nil if !File.file?(fn)
|
|
36
37
|
|
|
37
|
-
loader = Syntropy::ModuleLoader.new(env)
|
|
38
|
+
loader = Syntropy::ModuleLoader.new(env, extensions: ControllerExtensions)
|
|
38
39
|
loader.load('_site')
|
|
39
40
|
end
|
|
40
41
|
|
|
@@ -61,7 +62,10 @@ module Syntropy
|
|
|
61
62
|
@env = env
|
|
62
63
|
@logger = env[:logger]
|
|
63
64
|
|
|
64
|
-
@module_loader = Syntropy::ModuleLoader.new(
|
|
65
|
+
@module_loader = Syntropy::ModuleLoader.new(
|
|
66
|
+
env.merge(app: self),
|
|
67
|
+
extensions: ControllerExtensions
|
|
68
|
+
)
|
|
65
69
|
setup_routing_tree
|
|
66
70
|
start
|
|
67
71
|
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Syntropy
|
|
6
|
+
# Utilities for use in modules
|
|
7
|
+
module ControllerExtensions
|
|
8
|
+
# Returns a unique temporary path
|
|
9
|
+
#
|
|
10
|
+
# @param prefix [String] temp file prefix
|
|
11
|
+
# @return [String]
|
|
12
|
+
def tmp_path(prefix = 'syntropy')
|
|
13
|
+
"/tmp/#{prefix}-#{SecureRandom.hex(16)}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns a request handler that routes request according to the host
|
|
17
|
+
# header. Looks for site directories (named by host name) in the app's root
|
|
18
|
+
# directory. A map may be given in order to provide additional hostnames to
|
|
19
|
+
# site directories.
|
|
20
|
+
#
|
|
21
|
+
# @param dir [String, nil] relative directory path for host sites
|
|
22
|
+
# @param map [Hash, nil] hash mapping host names to relative site directory
|
|
23
|
+
# @return [Proc] router proc
|
|
24
|
+
def dispatch_by_host(dir = nil, map = nil)
|
|
25
|
+
raise Syntropy::Error, 'Must provide dir and/or map' if !dir && !map
|
|
26
|
+
|
|
27
|
+
site_map = {}
|
|
28
|
+
setup_directory_sites(dir, site_map) if dir
|
|
29
|
+
setup_mapped_sites(map, site_map) if map
|
|
30
|
+
|
|
31
|
+
->(req) do
|
|
32
|
+
site = site_map[req.host]
|
|
33
|
+
site ? site.call(req) : req.respond(nil, ':status' => HTTP::BAD_REQUEST)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns a request handler that handles requests by calling the appropriate
|
|
38
|
+
# module method (e.g. get, post, etc.)
|
|
39
|
+
#
|
|
40
|
+
# @return [Proc]
|
|
41
|
+
def dispatch_by_http_method
|
|
42
|
+
->(req) do
|
|
43
|
+
route_by_http_method(req)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns a list of parsed markdown pages at the given path.
|
|
48
|
+
#
|
|
49
|
+
# @param env [Hash] app environment hash
|
|
50
|
+
# @param ref [String] directory path
|
|
51
|
+
# @return [Array<Hash>] array of page entries
|
|
52
|
+
def page_list(env, ref)
|
|
53
|
+
full_path = File.join(env[:app_root], ref)
|
|
54
|
+
raise 'Not a directory' if !File.directory?(full_path)
|
|
55
|
+
|
|
56
|
+
Dir[File.join(full_path, '*.md')].sort.map {
|
|
57
|
+
atts, markdown = Syntropy::Markdown.parse(it, env)
|
|
58
|
+
{ atts:, markdown: }
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Instantiates a Syntropy app for the given environment hash.
|
|
63
|
+
#
|
|
64
|
+
# @return [Syntropy::App]
|
|
65
|
+
def app(**)
|
|
66
|
+
Syntropy::App.new(**)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
BUILTIN_APPLET_app_root = File.expand_path(File.join(__dir__, 'applets/builtin'))
|
|
70
|
+
|
|
71
|
+
# Creates a builtin applet with the given environment hash. By default the
|
|
72
|
+
# builtin applet is mounted at /.syntropy.
|
|
73
|
+
#
|
|
74
|
+
# @param env [Hash] app environment
|
|
75
|
+
# @param mount_path [String] mount path for the builtin applet
|
|
76
|
+
# @return [Syntropy::App] applet
|
|
77
|
+
def builtin_applet(env, mount_path: '/.syntropy')
|
|
78
|
+
app(
|
|
79
|
+
machine: env[:machine],
|
|
80
|
+
app_root: BUILTIN_APPLET_app_root,
|
|
81
|
+
mount_path: mount_path,
|
|
82
|
+
builtin_applet_path: nil,
|
|
83
|
+
watch_files: nil
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
# Finds sites in the root directory for the given environment hash, adds
|
|
90
|
+
# entries to the given site map.
|
|
91
|
+
#
|
|
92
|
+
# @param dir [String] relative or absolute path
|
|
93
|
+
# @param site_map [Hash] site map
|
|
94
|
+
# @return [void]
|
|
95
|
+
def setup_directory_sites(ref, site_map)
|
|
96
|
+
app_root = @app ? @app.app_root : @env[:app_root]
|
|
97
|
+
ref = normalize_import_ref(ref)
|
|
98
|
+
|
|
99
|
+
Dir[File.join(app_root, ref, '*')]
|
|
100
|
+
.select { File.directory?(it) && File.basename(it) !~ /^_/ }
|
|
101
|
+
.each { |entry| site_map[File.basename(entry)] = make_app(entry) }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# converts the given map entries by adding entries to the given site map.
|
|
105
|
+
#
|
|
106
|
+
# @param map [Hash] ref map
|
|
107
|
+
# @param site_map [Hash] site map
|
|
108
|
+
# @return [void]
|
|
109
|
+
def setup_mapped_sites(map, site_map)
|
|
110
|
+
app_root = @app ? @app.app_root : @env[:app_root]
|
|
111
|
+
map.each do |name, ref|
|
|
112
|
+
ref = File.join(File.dirname(@ref), ref) if ref !~ /^\//
|
|
113
|
+
site_root = File.join(app_root, ref)
|
|
114
|
+
site_map[name] = make_app(site_root)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Creates an app loaded from the given root directory, with the present
|
|
119
|
+
# mount path.
|
|
120
|
+
def make_app(site_root)
|
|
121
|
+
mount_path = @ref == '/_site' ? '/' : @ref
|
|
122
|
+
env = @env.merge(app_root: site_root, mount_path:)
|
|
123
|
+
Syntropy::App.new(**env)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Handles the given request by calling the module method corresponding to
|
|
127
|
+
# the request's HTTP method. If no method is found, raises a
|
|
128
|
+
# method_not_allowed error.
|
|
129
|
+
def route_by_http_method(req)
|
|
130
|
+
sym = req.method.to_sym
|
|
131
|
+
raise Syntropy::Error.method_not_allowed if !respond_to?(sym)
|
|
132
|
+
|
|
133
|
+
send(sym, req)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'papercraft'
|
|
4
|
+
require 'syntropy/errors'
|
|
4
5
|
|
|
5
6
|
module Syntropy
|
|
6
7
|
# The ModuleLoader class implemenets a module loader. It handles loading of
|
|
@@ -25,12 +26,14 @@ module Syntropy
|
|
|
25
26
|
# Instantiates a module loader
|
|
26
27
|
#
|
|
27
28
|
# @param env [Hash] environment hash
|
|
29
|
+
# @param extensions [Module, Array<Module>] extension module(s)
|
|
28
30
|
# @return [void]
|
|
29
|
-
def initialize(env)
|
|
31
|
+
def initialize(env, extensions: nil)
|
|
30
32
|
@env = env
|
|
31
33
|
@app_root = env[:app_root]
|
|
32
34
|
@modules = {} # maps ref to module entry
|
|
33
35
|
@fn_map = {} # maps filename to ref
|
|
36
|
+
@extensions = extensions
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
# Loads a module (if not already loaded) and returns its export value.
|
|
@@ -131,7 +134,7 @@ module Syntropy
|
|
|
131
134
|
@fn_map[fn] = ref
|
|
132
135
|
code = IO.read(fn)
|
|
133
136
|
env = @env.merge(module_loader: self, ref: clean_ref(ref))
|
|
134
|
-
mod = Syntropy::
|
|
137
|
+
mod = Syntropy::ModuleContext.load(env, code, fn, @extensions)
|
|
135
138
|
add_dependencies(ref, mod.__dependencies__)
|
|
136
139
|
export_value = transform_module_export_value(
|
|
137
140
|
mod.__export_value__, fn, raise_on_missing:
|
|
@@ -150,10 +153,10 @@ module Syntropy
|
|
|
150
153
|
# @param ref [String] input ref
|
|
151
154
|
# @return [String] clean ref
|
|
152
155
|
def clean_ref(ref)
|
|
153
|
-
return '/' if ref =~ /^index
|
|
156
|
+
return '/' if ref =~ /^index[+]?$/
|
|
154
157
|
|
|
155
|
-
clean = ref.gsub(/\/index
|
|
156
|
-
clean == '' ? '/' : clean
|
|
158
|
+
clean = ref.gsub(/\/index[+]?$/, '')
|
|
159
|
+
(clean == '') ? '/' : clean
|
|
157
160
|
end
|
|
158
161
|
|
|
159
162
|
# Transforms the given export value. If the value is nil, an exception is
|
|
@@ -173,9 +176,9 @@ module Syntropy
|
|
|
173
176
|
end
|
|
174
177
|
end
|
|
175
178
|
|
|
176
|
-
# The Syntropy::
|
|
177
|
-
# `.rb` source file that implements a route endpoint, a template,
|
|
178
|
-
# methods or any other functionality needed by the web app.
|
|
179
|
+
# The Syntropy::ModuleContext class provides a context for loading a module. A
|
|
180
|
+
# module is a `.rb` source file that implements a route endpoint, a template,
|
|
181
|
+
# utility methods or any other functionality needed by the web app.
|
|
179
182
|
#
|
|
180
183
|
# The following instance variables are available to modules:
|
|
181
184
|
#
|
|
@@ -189,35 +192,45 @@ module Syntropy
|
|
|
189
192
|
# In addition, the module code also has access to the `MODULE` constant which
|
|
190
193
|
# is set to `self`, and may be used to refer to various methods defined in the
|
|
191
194
|
# module.
|
|
192
|
-
class
|
|
195
|
+
class ModuleContext
|
|
193
196
|
# Loads a module, returning the module instance
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
# @param env [Hash] app environment
|
|
198
|
+
# @param code [String] module source code
|
|
199
|
+
# @param fn [String] module file name
|
|
200
|
+
# @param extensions [Module, Array<Module>] extension module(s)
|
|
201
|
+
# @return [Syntropy::ModuleContext] created module context
|
|
202
|
+
def self.load(env, code, fn, extensions)
|
|
203
|
+
mod = new(env)
|
|
204
|
+
apply_extensions(mod, extensions)
|
|
205
|
+
mod.instance_eval(code, fn)
|
|
197
206
|
env[:logger]&.info(message: "Loaded module at #{fn}")
|
|
198
|
-
|
|
199
|
-
rescue SyntaxError => e
|
|
207
|
+
mod
|
|
208
|
+
rescue StandardError, SyntaxError => e
|
|
200
209
|
env[:logger]&.error(message: "Error while loading module at #{fn}", error: e)
|
|
201
|
-
|
|
210
|
+
e.is_a?(SyntaxError) ? handle_syntax_error(env, e) : (raise e)
|
|
211
|
+
end
|
|
202
212
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
213
|
+
# Applies the given extension(s) to the given module context.
|
|
214
|
+
#
|
|
215
|
+
# @param mod [Syntropy::ModuleContext] module context
|
|
216
|
+
# @param extensions [Module, Array<Module>] extension module(s)
|
|
217
|
+
def self.apply_extensions(mod, extensions)
|
|
218
|
+
case extensions
|
|
219
|
+
when Array
|
|
220
|
+
extensions.each { mod.extend(it) }
|
|
221
|
+
when Module
|
|
222
|
+
mod.extend(extensions)
|
|
223
|
+
when nil # return
|
|
208
224
|
else
|
|
209
|
-
raise
|
|
225
|
+
raise Syntropy::Error, "Invalid module extensions: #{extensions.inspect}"
|
|
210
226
|
end
|
|
211
|
-
rescue => e
|
|
212
|
-
env[:logger]&.error(message: "Error while loading module at #{fn}", error: e)
|
|
213
|
-
raise e
|
|
214
227
|
end
|
|
215
228
|
|
|
216
229
|
# Initializes a module with the given environment hash.
|
|
217
230
|
#
|
|
218
231
|
# @param env [Hash] environment hash
|
|
219
232
|
# @return [void]
|
|
220
|
-
def initialize(
|
|
233
|
+
def initialize(env)
|
|
221
234
|
@env = env
|
|
222
235
|
@machine = env[:machine]
|
|
223
236
|
@module_loader = env[:module_loader]
|
|
@@ -329,22 +342,15 @@ module Syntropy
|
|
|
329
342
|
Syntropy::App.new(**env)
|
|
330
343
|
end
|
|
331
344
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
def http_methods
|
|
337
|
-
->(req) { route_by_http_method(req) }
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
# Handles the given request by calling the module method corresponding to
|
|
341
|
-
# the request's HTTP method. If no method is found, raises a
|
|
342
|
-
# method_not_allowed error.
|
|
343
|
-
def route_by_http_method(req)
|
|
344
|
-
sym = req.method.to_sym
|
|
345
|
-
raise Syntropy::Error.method_not_allowed if !respond_to?(sym)
|
|
345
|
+
def handle_syntax_error(env, e)
|
|
346
|
+
$stderr.puts("\n#{e.message}") if !Syntropy.test_mode
|
|
347
|
+
m = e.message.match(/^(.+): syntax/)
|
|
348
|
+
raise e if !m
|
|
346
349
|
|
|
347
|
-
|
|
350
|
+
location = m[1]
|
|
351
|
+
e2 = SyntaxError.new("Syntax errors found in module #{env[:ref]}")
|
|
352
|
+
e2.set_backtrace([location] + e.backtrace)
|
|
353
|
+
raise e2
|
|
348
354
|
end
|
|
349
355
|
end
|
|
350
356
|
end
|
|
@@ -339,8 +339,8 @@ module Syntropy
|
|
|
339
339
|
target: { kind:, fn: },
|
|
340
340
|
# In case we're at the tree root, we need to copy over the hook and
|
|
341
341
|
# error refs.
|
|
342
|
-
hook:
|
|
343
|
-
error:
|
|
342
|
+
hook: parent[:hook],
|
|
343
|
+
error: parent[:error]
|
|
344
344
|
}
|
|
345
345
|
end
|
|
346
346
|
nil
|
|
@@ -458,9 +458,9 @@ module Syntropy
|
|
|
458
458
|
emit_router_proc_prelude(buffer)
|
|
459
459
|
segment_idx = 1
|
|
460
460
|
if @root[:path] != '/'
|
|
461
|
-
|
|
462
|
-
segment_idx =
|
|
463
|
-
emit_root_validate_guard(buffer:,
|
|
461
|
+
root_segments = @root[:path].split('/')
|
|
462
|
+
segment_idx = root_segments.size
|
|
463
|
+
emit_root_validate_guard(buffer:, root_segments:)
|
|
464
464
|
end
|
|
465
465
|
|
|
466
466
|
visit_routing_tree_entry(buffer:, entry: @root, segment_idx:)
|
|
@@ -492,18 +492,18 @@ module Syntropy
|
|
|
492
492
|
def emit_router_proc_prelude(buffer)
|
|
493
493
|
emit_code_line(buffer, '->(path, params) {')
|
|
494
494
|
emit_code_line(buffer, ' entry = @static_map[path]; return entry if entry')
|
|
495
|
-
emit_code_line(buffer, '
|
|
495
|
+
emit_code_line(buffer, ' segments = path.split("/")')
|
|
496
496
|
end
|
|
497
497
|
|
|
498
498
|
# Emits root path validation guard code.
|
|
499
499
|
#
|
|
500
500
|
# @param buffer [String] output buffer
|
|
501
|
-
# @param
|
|
501
|
+
# @param root_segments [Array<String>] root path segments
|
|
502
502
|
# @return [void]
|
|
503
|
-
def emit_root_validate_guard(buffer:,
|
|
503
|
+
def emit_root_validate_guard(buffer:, root_segments:)
|
|
504
504
|
validate_parts = []
|
|
505
|
-
(1...
|
|
506
|
-
validate_parts << "(
|
|
505
|
+
(1...root_segments.size).each do |i|
|
|
506
|
+
validate_parts << "(segments[#{i}] != #{root_segments[i].inspect})"
|
|
507
507
|
end
|
|
508
508
|
emit_code_line(buffer, " return nil if #{validate_parts.join(' || ')}")
|
|
509
509
|
end
|
|
@@ -568,7 +568,7 @@ module Syntropy
|
|
|
568
568
|
|
|
569
569
|
# Get next segment
|
|
570
570
|
if !case_buffer.empty?
|
|
571
|
-
emit_code_line(buffer, "#{ws}case (
|
|
571
|
+
emit_code_line(buffer, "#{ws}case (s = segments[#{segment_idx}])")
|
|
572
572
|
buffer << case_buffer
|
|
573
573
|
emit_code_line(buffer, "#{ws}end")
|
|
574
574
|
end
|
|
@@ -630,7 +630,7 @@ module Syntropy
|
|
|
630
630
|
next if child_entry[:static]
|
|
631
631
|
|
|
632
632
|
emit_code_line(buffer, "#{ws}when #{k.inspect}")
|
|
633
|
-
if_clause = child_entry[:handle_subtree] ? '' : " if !
|
|
633
|
+
if_clause = child_entry[:handle_subtree] ? '' : " if !segments[#{segment_idx + 1}]"
|
|
634
634
|
|
|
635
635
|
child_path = child_entry[:path]
|
|
636
636
|
route_value = "@dynamic_map[#{child_path.inspect}]"
|
|
@@ -657,12 +657,12 @@ module Syntropy
|
|
|
657
657
|
# parametric route
|
|
658
658
|
if param_entry
|
|
659
659
|
if when_count == 0
|
|
660
|
-
emit_code_line(buffer, "#{ws}when
|
|
660
|
+
emit_code_line(buffer, "#{ws}when s")
|
|
661
661
|
else
|
|
662
662
|
emit_code_line(buffer, "#{ws}else")
|
|
663
663
|
end
|
|
664
664
|
|
|
665
|
-
emit_code_line(buffer, "#{ws} params[#{param_entry[:param].inspect}] =
|
|
665
|
+
emit_code_line(buffer, "#{ws} params[#{param_entry[:param].inspect}] = s")
|
|
666
666
|
visit_routing_tree_entry(buffer:, entry: param_entry, indent: indent + 1, segment_idx: segment_idx + 1)
|
|
667
667
|
# wildcard route
|
|
668
668
|
elsif entry[:handle_subtree]
|
data/lib/syntropy/test.rb
CHANGED
|
@@ -11,12 +11,9 @@ module Syntropy
|
|
|
11
11
|
class Test < Minitest::Test
|
|
12
12
|
HTTP = Syntropy::HTTP
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# @return [void]
|
|
18
|
-
def self.env=(env)
|
|
19
|
-
@@env = env
|
|
14
|
+
class << self
|
|
15
|
+
# Gets/sets app environment for tests
|
|
16
|
+
attr_accessor :env
|
|
20
17
|
end
|
|
21
18
|
|
|
22
19
|
attr_reader :machine, :app
|
|
@@ -25,7 +22,7 @@ module Syntropy
|
|
|
25
22
|
#
|
|
26
23
|
# @return [Hash] test app environment
|
|
27
24
|
def env
|
|
28
|
-
|
|
25
|
+
self.class.env
|
|
29
26
|
end
|
|
30
27
|
|
|
31
28
|
# Loads and returns a module with the given reference.
|
|
@@ -101,17 +98,38 @@ module Syntropy
|
|
|
101
98
|
post(path, 'application/x-www-form-urlencoded', URI.encode_www_form(data), **)
|
|
102
99
|
end
|
|
103
100
|
|
|
101
|
+
# Makes an HTTP PATCH request to the test app.
|
|
102
|
+
#
|
|
103
|
+
# @param path [String] request path
|
|
104
|
+
# @param content_type [String, nil] content MIME type
|
|
105
|
+
# @param body [String] request body
|
|
106
|
+
# @param headers [Hash] request headers
|
|
107
|
+
# @return [Syntropy::Request]
|
|
108
|
+
def patch(path, content_type, body, **headers)
|
|
109
|
+
headers = headers.merge('content-type' => content_type) if content_type
|
|
110
|
+
http_request(
|
|
111
|
+
headers.merge(
|
|
112
|
+
{
|
|
113
|
+
':method' => 'PATCH',
|
|
114
|
+
':path' => path
|
|
115
|
+
}
|
|
116
|
+
),
|
|
117
|
+
body
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
104
121
|
# Sets up a test instance.
|
|
105
122
|
#
|
|
106
123
|
# @return [void]
|
|
107
124
|
def setup
|
|
108
|
-
|
|
125
|
+
env = self.class.env
|
|
126
|
+
raise 'Environment not set' if !env
|
|
109
127
|
|
|
110
|
-
Syntropy.load_config(
|
|
128
|
+
Syntropy.load_config(env)
|
|
111
129
|
|
|
112
130
|
@machine = UM.new
|
|
113
131
|
@app = Syntropy::App.new(
|
|
114
|
-
|
|
132
|
+
**env.merge(
|
|
115
133
|
machine: @machine,
|
|
116
134
|
test_mode: true
|
|
117
135
|
)
|
data/lib/syntropy/version.rb
CHANGED
data/lib/syntropy.rb
CHANGED
|
@@ -17,15 +17,12 @@ require 'syntropy/papercraft_extensions'
|
|
|
17
17
|
require 'syntropy/routing_tree'
|
|
18
18
|
require 'syntropy/json_api'
|
|
19
19
|
require 'syntropy/side_run'
|
|
20
|
-
require 'syntropy/utils'
|
|
21
20
|
require 'syntropy/version'
|
|
22
21
|
|
|
23
22
|
# Syntropy is a web framework for building web apps in Ruby. Syntropy uses
|
|
24
23
|
# UringMachine for I/O and concurrency, and provides a comprehensive and
|
|
25
24
|
# flexible solution for writing web apps with minimal boilerplate.
|
|
26
25
|
module Syntropy
|
|
27
|
-
extend Utilities
|
|
28
|
-
|
|
29
26
|
class << self
|
|
30
27
|
attr_accessor :machine, :dev_mode, :test_mode
|
|
31
28
|
|