syntropy 0.5 → 0.7
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 +9 -0
- data/README.md +135 -4
- data/TODO.md +25 -36
- data/lib/syntropy/app.rb +22 -11
- data/lib/syntropy/connection_pool.rb +6 -13
- data/lib/syntropy/module.rb +23 -1
- data/lib/syntropy/router.rb +34 -2
- data/lib/syntropy/version.rb +1 -1
- data/test/app/_hook.rb +4 -0
- data/test/app/_layout/default.rb +1 -1
- data/test/app/_lib/self.rb +5 -0
- data/test/app/about/_error.rb +6 -0
- data/test/app/about/raise.rb +3 -0
- data/test/app/api+.rb +4 -0
- data/test/app_multi_site/_site.rb +1 -0
- data/test/app_multi_site/bar.baz/index.html +1 -0
- data/test/app_multi_site/foo.bar/index.html +1 -0
- data/test/test_app.rb +45 -0
- data/test/test_module.rb +6 -0
- metadata +8 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b86a65bfeb990aa38b76b5bb6487210887e7103bab652e40c3790d68c1ab48df
|
4
|
+
data.tar.gz: 89397a9faf63d2a07c69ab69f93048cb92c82014e30d7ca6ed9399671c17bb48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f536c81be4baa5c8e8733705a9a41042b322eb3960ee8a092523bfdfff886893a4183a6b00c69c2960daefe6443a6f06ee0e07596eeb5ffc3b45ceceb86c342
|
7
|
+
data.tar.gz: 05c3e6c5d3641847a0c962811e290313f7d08a4233504466035e94cfc03284cf652f990f5fd4681efd059673050fda5d021bba5a982c0e539b8c3351bd316913
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -59,6 +59,7 @@ site/
|
|
59
59
|
├ _articles/
|
60
60
|
| └ 2025-01-01-hello_world.md
|
61
61
|
├ api/
|
62
|
+
| ├ _hook.rb
|
62
63
|
| └ v1.rb
|
63
64
|
├ assets/
|
64
65
|
| ├ css/
|
@@ -73,10 +74,30 @@ site/
|
|
73
74
|
Syntropy knows how to serve static asset files (CSS, JS, images...) as well as
|
74
75
|
render markdown files and run modules written in Ruby.
|
75
76
|
|
77
|
+
Some conventions employed in Syntropy-based web apps:
|
78
|
+
|
79
|
+
- Files and directories starting with an underscore, e.g. `/_layout` are
|
80
|
+
considered private, and are not exposed to HTTP clients.
|
81
|
+
- Normally, a module route only responds to its exact path. To respond to any
|
82
|
+
subtree path, add a plus sign to the end of the module name, e.g. `/api+.rb`.
|
83
|
+
- A `_hook.rb` module is invoked on each request routed to anywhere in the
|
84
|
+
corresponding subtree. For example, a hook defined in `/api/_hook.rb` will be
|
85
|
+
used on requests to `/api`, `/api/foo`, `/api/bar` etc.
|
86
|
+
- As a corollary, each route "inherits" all hooks defined up the tree. For
|
87
|
+
example, a request to `/api/foo` will invoke hooks defined in `/api/_hook.rb`
|
88
|
+
and `/_hook.rb`.
|
89
|
+
- In a similar fashion to hooks, error handlers can be defined for different
|
90
|
+
subtrees in a `_error.rb` module. For each route, in case of an exception,
|
91
|
+
Syntropy will invoke the closest-found error handler module up the tree. For
|
92
|
+
example, an error raised while responding to a request to `/api/foo` will
|
93
|
+
prefer the error handler in `/api/_error.rb`, rather than `/_error.rb`.
|
94
|
+
- The Syntrpy router accepts clean URLs for Ruby modules and Markdown files. It
|
95
|
+
also accepts clean URLs for `index.html` files.
|
96
|
+
|
76
97
|
## What does a Syntropic Ruby module look like?
|
77
98
|
|
78
|
-
Consider `archive.rb` in the
|
79
|
-
and render it
|
99
|
+
Consider `site/archive.rb` in the file tree above. We want to get a list of
|
100
|
+
articles and render it using the given layout:
|
80
101
|
|
81
102
|
```ruby
|
82
103
|
# archive.rb
|
@@ -97,7 +118,7 @@ export @@layout.apply(title: 'archive') {
|
|
97
118
|
}
|
98
119
|
```
|
99
120
|
|
100
|
-
But a module can be something completely different:
|
121
|
+
But a module can also be something completely different:
|
101
122
|
|
102
123
|
```ruby
|
103
124
|
# api/v1.rb
|
@@ -121,4 +142,114 @@ export APIV1
|
|
121
142
|
```
|
122
143
|
|
123
144
|
Basically, the exported value can be a template, a callable or a class that
|
124
|
-
responds to the request.
|
145
|
+
responds to the request. Here's a minimal module that responds with a hello
|
146
|
+
world:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
export ->(req) { req.respond('Hello, world!') }
|
150
|
+
```
|
151
|
+
|
152
|
+
## Module Export / Import
|
153
|
+
|
154
|
+
Modules communicate with the Syntropy framework and with other modules using
|
155
|
+
`export` and `import`. Each module must export a single object, which can be a
|
156
|
+
controller class, a callable (a proc/closure) or a template. The exported object
|
157
|
+
is used by Syntropy as the entrypoint for the route.
|
158
|
+
|
159
|
+
But modules can also import other modules. This permits the use of layouts:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
# site/_layout/default.rb
|
163
|
+
export template { |**props|
|
164
|
+
header {
|
165
|
+
h1 'Foo'
|
166
|
+
}
|
167
|
+
content {
|
168
|
+
emit_yield(**props)
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
# site/index.rb
|
173
|
+
layout = import '_layout/default'
|
174
|
+
|
175
|
+
export layout.apply { |**props|
|
176
|
+
p 'o hi!'
|
177
|
+
}
|
178
|
+
```
|
179
|
+
|
180
|
+
A module can also be written as a set of methods without any explicit class
|
181
|
+
definition. This allows writing modules in a more functional style:
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
# site/_lib/utils.rb
|
185
|
+
|
186
|
+
def foo
|
187
|
+
42
|
188
|
+
end
|
189
|
+
|
190
|
+
export self
|
191
|
+
|
192
|
+
# site/index.rb
|
193
|
+
Utils = import '_lib/utils'
|
194
|
+
|
195
|
+
export template {
|
196
|
+
h1 "foo = #{Utils.foo}"
|
197
|
+
}
|
198
|
+
```
|
199
|
+
|
200
|
+
## Hooks (a.k.a. Middleware)
|
201
|
+
|
202
|
+
A hook is a piece of code that can intercept HTTP requests before they are
|
203
|
+
passed off to the correspending route. Hooks are applied to the subtree of the
|
204
|
+
directory in which they reside.
|
205
|
+
|
206
|
+
Hooks can be used for a variety of purposes:
|
207
|
+
|
208
|
+
- Parameter validation
|
209
|
+
- Authentication, authorization & session management
|
210
|
+
- Logging
|
211
|
+
- Request rewriting / redirecting
|
212
|
+
|
213
|
+
When multiple hooks are defined up the tree for a particular route, they are
|
214
|
+
chained together such that each hook is invoked starting from the file tree root
|
215
|
+
and down to the route path.
|
216
|
+
|
217
|
+
Hooks are implemented as modules named `_hook.rb`, that export procs (or
|
218
|
+
callables) with the following signature:
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
# **/_hook.rb
|
222
|
+
export ->(req, app) { ... }
|
223
|
+
```
|
224
|
+
|
225
|
+
... where req is the request object, and app is the callable that code. Here's
|
226
|
+
an example of an authorization hook:
|
227
|
+
|
228
|
+
```ruby
|
229
|
+
export ->(req, app) {
|
230
|
+
if (!req.cookies[:session_id])
|
231
|
+
req.redirect('/signin')
|
232
|
+
else
|
233
|
+
app.(req)
|
234
|
+
end
|
235
|
+
}
|
236
|
+
```
|
237
|
+
|
238
|
+
## Error handlers
|
239
|
+
|
240
|
+
An error handler can be defined separately for each subtree. When an exception
|
241
|
+
is raised that is not rescued by the application code, Syntropy will look for an
|
242
|
+
error handler up the file tree, and will invoke the first error handler found.
|
243
|
+
|
244
|
+
Error handlers are implemented as modules named `_error.rb`, that export procs (or
|
245
|
+
callables) with the following signature:
|
246
|
+
|
247
|
+
```ruby
|
248
|
+
# **/_error.rb
|
249
|
+
->(req, err) { ... }
|
250
|
+
```
|
251
|
+
|
252
|
+
Using different error handlers for parts of the route tree allows different
|
253
|
+
error responses for each route. For example, the error response for an API route
|
254
|
+
can be a JSON object, while the error response for a browser page route can be a
|
255
|
+
custom HTML page.
|
data/TODO.md
CHANGED
@@ -1,44 +1,33 @@
|
|
1
|
-
-
|
2
|
-
|
3
|
-
-
|
4
|
-
-
|
5
|
-
-
|
6
|
-
-
|
7
|
-
- middleware is defined in `_hook.rb` modules
|
8
|
-
- interface: ->(req, next)
|
9
|
-
- a special case for handling errors is `_error.rb`
|
10
|
-
- interface: ->(req, err)
|
11
|
-
- dispatching routes
|
12
|
-
- error handling:
|
13
|
-
- on uncaught error, if an `_error.rb` file exists in the same directory
|
14
|
-
or up the file tree
|
15
|
-
- middleware:
|
16
|
-
- a closure is created from the composition of the different hooks
|
17
|
-
defined, from the route's directory and up the file
|
18
|
-
- error handlers and middleware closures are cached as part of the route's
|
19
|
-
entry
|
20
|
-
- on file change for any _hook.rb or _error.rb files, all route entries in
|
21
|
-
the corresponding subtree are invalidated
|
22
|
-
|
23
|
-
|
24
|
-
- Middleware
|
1
|
+
- Some standard middleware:
|
2
|
+
|
3
|
+
- request rewriter
|
4
|
+
- logger
|
5
|
+
- auth
|
6
|
+
- selector + terminator
|
25
7
|
|
26
8
|
```Ruby
|
27
|
-
#
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
9
|
+
# For the chainable DSL shown below, we need to create a custom class:
|
10
|
+
class Syntropy::Middleware::Selector
|
11
|
+
def initialize(select_proc, terminator_proc = nil)
|
12
|
+
@select_proc = select_proc
|
13
|
+
@terminator_proc = terminator_proc
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_proc
|
17
|
+
->(req, proc) {
|
18
|
+
@select_proc.(req) ? @terminator_proc.(req) : proc(req)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def terminate(&proc)
|
23
|
+
@terminator_proc = proc
|
24
|
+
end
|
32
25
|
end
|
33
26
|
|
34
|
-
|
35
|
-
|
36
|
-
# Just a normal callable:
|
37
|
-
#
|
38
|
-
export ->(req, err) do
|
39
|
-
render_error_page(req, err.http_status)
|
40
|
-
end
|
27
|
+
def Syntropy
|
28
|
+
```
|
41
29
|
|
30
|
+
```Ruby
|
42
31
|
# a _site.rb file can be used to wrap a whole app
|
43
32
|
# site/_site.rb
|
44
33
|
|
data/lib/syntropy/app.rb
CHANGED
@@ -32,20 +32,20 @@ module Syntropy
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
def initialize(machine,
|
35
|
+
def initialize(machine, location, mount_path, opts = {})
|
36
36
|
@machine = machine
|
37
|
-
@
|
37
|
+
@location = File.expand_path(location)
|
38
38
|
@mount_path = mount_path
|
39
39
|
@opts = opts
|
40
40
|
|
41
|
-
@module_loader = Syntropy::ModuleLoader.new(@
|
41
|
+
@module_loader = Syntropy::ModuleLoader.new(@location, @opts)
|
42
42
|
@router = Syntropy::Router.new(@opts, @module_loader)
|
43
43
|
|
44
44
|
@machine.spin do
|
45
45
|
# we do startup stuff asynchronously, in order to first let TP2 do its
|
46
46
|
# setup tasks
|
47
47
|
@machine.sleep 0.15
|
48
|
-
@opts[:logger]&.call("Serving from #{File.expand_path(@
|
48
|
+
@opts[:logger]&.call("Serving from #{File.expand_path(@location)}")
|
49
49
|
@router.start_file_watcher if opts[:watch_files]
|
50
50
|
end
|
51
51
|
end
|
@@ -65,21 +65,32 @@ module Syntropy
|
|
65
65
|
private
|
66
66
|
|
67
67
|
def render_entry(req, entry)
|
68
|
+
kind = entry[:kind]
|
69
|
+
return respond_not_found(req) if kind == :not_found
|
70
|
+
|
71
|
+
entry[:proc] ||= calculate_route_proc(entry)
|
72
|
+
entry[:proc].(req)
|
73
|
+
end
|
74
|
+
|
75
|
+
def calculate_route_proc(entry)
|
76
|
+
render_proc = route_render_proc(entry)
|
77
|
+
@router.calc_route_proc_with_hooks(entry, render_proc)
|
78
|
+
end
|
79
|
+
|
80
|
+
def route_render_proc(entry)
|
68
81
|
case entry[:kind]
|
69
|
-
when :not_found
|
70
|
-
respond_not_found(req, entry)
|
71
82
|
when :static
|
72
|
-
respond_static(req, entry)
|
83
|
+
->(req) { respond_static(req, entry) }
|
73
84
|
when :markdown
|
74
|
-
respond_markdown(req, entry)
|
85
|
+
->(req) { respond_markdown(req, entry) }
|
75
86
|
when :module
|
76
|
-
|
87
|
+
load_module(entry)
|
77
88
|
else
|
78
89
|
raise 'Invalid entry kind'
|
79
90
|
end
|
80
91
|
end
|
81
92
|
|
82
|
-
def respond_not_found(req
|
93
|
+
def respond_not_found(req)
|
83
94
|
headers = { ':status' => Qeweney::Status::NOT_FOUND }
|
84
95
|
case req.method
|
85
96
|
when 'head'
|
@@ -124,7 +135,7 @@ module Syntropy
|
|
124
135
|
end
|
125
136
|
|
126
137
|
def load_module(entry)
|
127
|
-
ref = entry[:fn].gsub(%r{^#{@
|
138
|
+
ref = entry[:fn].gsub(%r{^#{@location}/}, '').gsub(/\.rb$/, '')
|
128
139
|
o = @module_loader.load(ref)
|
129
140
|
o.is_a?(Papercraft::Template) ? wrap_template(o) : o
|
130
141
|
rescue Exception => e
|
@@ -34,9 +34,7 @@ module Syntropy
|
|
34
34
|
private
|
35
35
|
|
36
36
|
def checkout
|
37
|
-
if @queue.count == 0 && @count < @max_conn
|
38
|
-
return create_db
|
39
|
-
end
|
37
|
+
return make_db_instance if @queue.count == 0 && @count < @max_conn
|
40
38
|
|
41
39
|
@machine.shift(@queue)
|
42
40
|
end
|
@@ -45,16 +43,11 @@ module Syntropy
|
|
45
43
|
@machine.push(@queue, db)
|
46
44
|
end
|
47
45
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
def setup_db(db)
|
56
|
-
# setup WAL, sync
|
57
|
-
# setup concurrency stuff
|
46
|
+
def make_db_instance
|
47
|
+
Extralite::Database.new(@fn, wal: true).tap do
|
48
|
+
@count += 1
|
49
|
+
it.on_progress(mode: :at_least_once, period: 320, tick: 10) { @machine.snooze }
|
50
|
+
end
|
58
51
|
end
|
59
52
|
end
|
60
53
|
end
|
data/lib/syntropy/module.rb
CHANGED
@@ -33,6 +33,7 @@ module Syntropy
|
|
33
33
|
mod_body = IO.read(fn)
|
34
34
|
mod_ctx = Class.new(Syntropy::Module)
|
35
35
|
mod_ctx.loader = self
|
36
|
+
mod_ctx.env = @env
|
36
37
|
mod_ctx.module_eval(mod_body, fn, 1)
|
37
38
|
|
38
39
|
export_value = mod_ctx.__export_value__
|
@@ -66,6 +67,10 @@ module Syntropy
|
|
66
67
|
@loader = loader
|
67
68
|
end
|
68
69
|
|
70
|
+
def self.env=(env)
|
71
|
+
@env = env
|
72
|
+
end
|
73
|
+
|
69
74
|
def self.import(ref)
|
70
75
|
@loader.load(ref)
|
71
76
|
end
|
@@ -74,12 +79,29 @@ module Syntropy
|
|
74
79
|
@__export_value__ = ref
|
75
80
|
end
|
76
81
|
|
77
|
-
def self.
|
82
|
+
def self.template(&block)
|
78
83
|
Papercraft.html(&block)
|
79
84
|
end
|
80
85
|
|
86
|
+
def self.route_by_host
|
87
|
+
root = @env[:location]
|
88
|
+
sites = Dir[File.join(root, '*')]
|
89
|
+
.select { File.directory?(it) }
|
90
|
+
.inject({}) { |h, fn|
|
91
|
+
name = File.basename(fn)
|
92
|
+
opts = @env.merge(location: fn)
|
93
|
+
h[name] = Syntropy::App.new(opts[:machine], opts[:location], opts[:mount_path], opts)
|
94
|
+
h
|
95
|
+
}
|
96
|
+
->(req) {
|
97
|
+
site = sites[req.host]
|
98
|
+
site ? site.call(req) : req.respond(nil, ':status' => Status::BAD_REQUEST)
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
81
102
|
def self.__export_value__
|
82
103
|
@__export_value__
|
83
104
|
end
|
105
|
+
|
84
106
|
end
|
85
107
|
end
|
data/lib/syntropy/router.rb
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
module Syntropy
|
4
4
|
class Router
|
5
|
-
attr_reader :cache
|
6
|
-
|
7
5
|
def initialize(opts, module_loader = nil)
|
8
6
|
raise 'Invalid location given' if !File.directory?(opts[:location])
|
9
7
|
|
@@ -32,6 +30,10 @@ module Syntropy
|
|
32
30
|
@machine.spin { file_watcher_loop }
|
33
31
|
end
|
34
32
|
|
33
|
+
def calc_route_proc_with_hooks(entry, proc)
|
34
|
+
compose_up_tree_hooks(entry[:fn], proc)
|
35
|
+
end
|
36
|
+
|
35
37
|
private
|
36
38
|
|
37
39
|
HIDDEN_RE = /^_/
|
@@ -204,5 +206,35 @@ module Syntropy
|
|
204
206
|
def remove_entry_cache_keys(entry)
|
205
207
|
entry[:cache_keys]&.each_key { @cache.delete(it) }.clear
|
206
208
|
end
|
209
|
+
|
210
|
+
def compose_up_tree_hooks(path, proc)
|
211
|
+
parent = File.dirname(path)
|
212
|
+
proc = hook_wrap_if_exists(File.join(parent, '_hook.rb'), proc)
|
213
|
+
proc = error_handler_wrap_if_exists(File.join(parent, '_error.rb'), proc)
|
214
|
+
return proc if parent == @root
|
215
|
+
|
216
|
+
compose_up_tree_hooks(parent, proc)
|
217
|
+
end
|
218
|
+
|
219
|
+
def hook_wrap_if_exists(hook_fn, proc)
|
220
|
+
return proc if !File.file?(hook_fn)
|
221
|
+
|
222
|
+
ref = path_rel(hook_fn).gsub(/\.rb$/, '')
|
223
|
+
hook_proc = @module_loader.load(ref)
|
224
|
+
->(req) { hook_proc.(req, proc) }
|
225
|
+
end
|
226
|
+
|
227
|
+
def error_handler_wrap_if_exists(error_handler_fn, proc)
|
228
|
+
return proc if !File.file?(error_handler_fn)
|
229
|
+
|
230
|
+
ref = path_rel(error_handler_fn).gsub(/\.rb$/, '')
|
231
|
+
error_proc = @module_loader.load(ref)
|
232
|
+
|
233
|
+
proc do |req|
|
234
|
+
proc.(req)
|
235
|
+
rescue StandardError => e
|
236
|
+
error_proc.(req, e)
|
237
|
+
end
|
238
|
+
end
|
207
239
|
end
|
208
240
|
end
|
data/lib/syntropy/version.rb
CHANGED
data/test/app/_hook.rb
ADDED
data/test/app/_layout/default.rb
CHANGED
data/test/app/api+.rb
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
export route_by_host
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>bar.baz</h1>
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>foo.bar</h1>
|
data/test/test_app.rb
CHANGED
@@ -140,6 +140,18 @@ class AppTest < Minitest::Test
|
|
140
140
|
ensure
|
141
141
|
IO.write(@tmp_fn, orig_body) if orig_body
|
142
142
|
end
|
143
|
+
|
144
|
+
def test_middleware
|
145
|
+
req = make_request(':method' => 'HEAD', ':path' => '/test?foo=42')
|
146
|
+
assert_equal Status::OK, req.response_status
|
147
|
+
assert_nil req.response_body
|
148
|
+
assert_equal '42', req.ctx[:foo]
|
149
|
+
|
150
|
+
req = make_request(':method' => 'HEAD', ':path' => '/test/about/raise?foo=43')
|
151
|
+
assert_equal Status::INTERNAL_SERVER_ERROR, req.response_status
|
152
|
+
assert_equal '<h1>Raised error</h1>', req.response_body
|
153
|
+
assert_equal '43', req.ctx[:foo]
|
154
|
+
end
|
143
155
|
end
|
144
156
|
|
145
157
|
class CustomAppTest < Minitest::Test
|
@@ -168,3 +180,36 @@ class CustomAppTest < Minitest::Test
|
|
168
180
|
assert_equal Status::TEAPOT, req.response_status
|
169
181
|
end
|
170
182
|
end
|
183
|
+
|
184
|
+
class MultiSiteAppTest < Minitest::Test
|
185
|
+
Status = Qeweney::Status
|
186
|
+
|
187
|
+
APP_ROOT = File.join(__dir__, 'app_multi_site')
|
188
|
+
|
189
|
+
def setup
|
190
|
+
@machine = UM.new
|
191
|
+
@app = Syntropy::App.load(
|
192
|
+
machine: @machine,
|
193
|
+
location: APP_ROOT,
|
194
|
+
mount_path: '/'
|
195
|
+
)
|
196
|
+
end
|
197
|
+
|
198
|
+
def make_request(*, **)
|
199
|
+
req = mock_req(*, **)
|
200
|
+
@app.call(req)
|
201
|
+
req
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_route_by_host
|
205
|
+
req = make_request(':method' => 'GET', ':path' => '/', 'host' => 'blah')
|
206
|
+
assert_nil req.response_body
|
207
|
+
assert_equal Status::BAD_REQUEST, req.response_status
|
208
|
+
|
209
|
+
req = make_request(':method' => 'GET', ':path' => '/', 'host' => 'foo.bar')
|
210
|
+
assert_equal '<h1>foo.bar</h1>', req.response_body.chomp
|
211
|
+
|
212
|
+
req = make_request(':method' => 'GET', ':path' => '/', 'host' => 'bar.baz')
|
213
|
+
assert_equal '<h1>bar.baz</h1>', req.response_body.chomp
|
214
|
+
end
|
215
|
+
end
|
data/test/test_module.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: syntropy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.7'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
@@ -202,12 +202,16 @@ files:
|
|
202
202
|
- lib/syntropy/side_run.rb
|
203
203
|
- lib/syntropy/version.rb
|
204
204
|
- syntropy.gemspec
|
205
|
+
- test/app/_hook.rb
|
205
206
|
- test/app/_layout/default.rb
|
206
207
|
- test/app/_lib/callable.rb
|
207
208
|
- test/app/_lib/klass.rb
|
208
209
|
- test/app/_lib/missing-export.rb
|
210
|
+
- test/app/_lib/self.rb
|
211
|
+
- test/app/about/_error.rb
|
209
212
|
- test/app/about/foo.md
|
210
213
|
- test/app/about/index.rb
|
214
|
+
- test/app/about/raise.rb
|
211
215
|
- test/app/api+.rb
|
212
216
|
- test/app/assets/style.css
|
213
217
|
- test/app/bar.rb
|
@@ -215,6 +219,9 @@ files:
|
|
215
219
|
- test/app/index.html
|
216
220
|
- test/app/tmp.rb
|
217
221
|
- test/app_custom/_site.rb
|
222
|
+
- test/app_multi_site/_site.rb
|
223
|
+
- test/app_multi_site/bar.baz/index.html
|
224
|
+
- test/app_multi_site/foo.bar/index.html
|
218
225
|
- test/helper.rb
|
219
226
|
- test/run.rb
|
220
227
|
- test/test_app.rb
|