syntropy 0.15.1 → 0.17

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.
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ gem 'syntropy', path: '.'
7
+ gem 'roda'
8
+ gem 'benchmark-ips'
9
+ end
10
+
11
+ require 'syntropy'
12
+ require 'roda'
13
+ require 'benchmark/ips'
14
+ require 'securerandom'
15
+ require 'rack/mock_request'
16
+ require 'qeweney/mock_adapter'
17
+
18
+ class BM
19
+ def self.name(name)
20
+ define_method(:name) { name }
21
+ end
22
+
23
+ def self.run(&block)
24
+ new(&block).run
25
+ end
26
+
27
+ def initialize(&block)
28
+ @entry_classes = []
29
+ instance_exec(&block)
30
+ end
31
+
32
+ def entry(name, &block)
33
+ k = Class.new(Entry)
34
+ k.define_method(:name) { name }
35
+ k.class_exec(&block)
36
+ @entry_classes << k
37
+ end
38
+
39
+ def run
40
+ entries = @entry_classes.map { it.new.tap(&:setup) }
41
+
42
+ Benchmark.ips do |x|
43
+ entries.each do |e|
44
+ x.report(e.name) { e.call }
45
+ end
46
+ x.compare!(order: :baseline)
47
+ end
48
+ end
49
+
50
+ class Entry
51
+ def setup
52
+ end
53
+
54
+ def call
55
+ raise NotImplementedError
56
+ end
57
+ end
58
+ end
59
+
60
+ ################################################################################
61
+
62
+ class RodaRouter < Roda
63
+ route do |r|
64
+ # GET / request
65
+ r.root do
66
+
67
+ r.redirect "/hello"
68
+ end
69
+
70
+ # /hello branch
71
+ r.on "hello" do
72
+ # Set variable for all routes in /hello branch
73
+ @greeting = 'Hello'
74
+
75
+ # GET /hello/world request
76
+ r.get "world" do
77
+ "#{@greeting} world!"
78
+ end
79
+
80
+ # /hello request
81
+ r.is do
82
+ # GET /hello request
83
+ r.get do
84
+ "#{@greeting}!"
85
+ end
86
+
87
+ # POST /hello request
88
+ r.post do
89
+ puts "Someone said #{@greeting}!"
90
+ r.redirect
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ req = Rack::MockRequest.env_for("http://example.com:8080/hello/world")
98
+ roda_app = RodaRouter.app
99
+ p roda_app.(req)
100
+
101
+ ################################################################################
102
+
103
+ class Qeweney::Request
104
+ def response_headers
105
+ adapter.headers
106
+ end
107
+
108
+ def response_status
109
+ adapter.status
110
+ end
111
+
112
+ def response_body
113
+ adapter.body
114
+ end
115
+
116
+ def response_json
117
+ raise if response_content_type != 'application/json'
118
+ JSON.parse(response_body, symbolize_names: true)
119
+ end
120
+
121
+ def response_content_type
122
+ response_headers['Content-Type']
123
+ end
124
+ end
125
+
126
+ def make_tmp_file_tree(dir, spec)
127
+ FileUtils.mkdir(dir) rescue nil
128
+ spec.each do |k, v|
129
+ fn = File.join(dir, k.to_s)
130
+ case v
131
+ when String
132
+ IO.write(fn, v)
133
+ when Hash
134
+ FileUtils.mkdir(fn) rescue nil
135
+ make_tmp_file_tree(fn, v)
136
+ end
137
+ end
138
+ dir
139
+ end
140
+
141
+ ROOT_DIR = "/tmp/#{__FILE__.gsub('/', '-')}-#{SecureRandom.hex}"
142
+ make_tmp_file_tree(ROOT_DIR, {
143
+ 'index.rb': "export ->(req) { req.redirect('/hello') }",
144
+ 'hello': {
145
+ 'index.rb': "export ->(req) { req.respond('Hello!', 'Content-Type' => 'text/html') }",
146
+ 'world.rb': "export ->(req) { req.respond('Hello world!', 'Content-Type' => 'text/html') }",
147
+ }
148
+ })
149
+
150
+ machine = UM.new
151
+ syntropy_app = Syntropy::App.new(
152
+ root_dir: ROOT_DIR,
153
+ mount_path: '/',
154
+ machine: machine
155
+ )
156
+ proc = ->(req) { syntropy_app.(req) }
157
+
158
+ module ::Kernel
159
+ def mock_req(headers, body = nil)
160
+ Qeweney::MockAdapter.mock(headers, body).tap { it.setup_mock_request }
161
+ end
162
+ end
163
+
164
+ puts '*' * 40
165
+
166
+ req = mock_req(':method' => 'GET', ':path' => '/hello/world')
167
+ proc.(req)
168
+ p [req.response_status, req.response_headers, req.response_body]
169
+
170
+ ################################################################################
171
+
172
+ BM.run do
173
+ entry(:roda) {
174
+ def setup
175
+ @app = RodaRouter.app
176
+ end
177
+
178
+ def call
179
+ req = Rack::MockRequest.env_for("http://example.com:8080/hello/world")
180
+ @app.(req)
181
+ end
182
+ }
183
+
184
+ entry(:syntropy) {
185
+ def setup
186
+ machine = UM.new
187
+ syntropy_app = Syntropy::App.new(
188
+ root_dir: ROOT_DIR,
189
+ mount_path: '/',
190
+ # watch_files: 0.05,
191
+ machine: machine
192
+ )
193
+ @app = ->(req) { syntropy_app.(req) }
194
+ end
195
+
196
+ def call
197
+ req = mock_req(':method' => 'GET', ':path' => '/hello/world')
198
+ @app.(req)
199
+ end
200
+ }
201
+ end
data/test/test_app.rb CHANGED
@@ -28,10 +28,6 @@ class AppTest < Minitest::Test
28
28
  end
29
29
 
30
30
  def test_app_rendering
31
- # puts "*" * 40
32
- # pp @app.routing_tree.root
33
- # puts
34
-
35
31
  req = make_request(':method' => 'GET', ':path' => '/')
36
32
  assert_equal Status::NOT_FOUND, req.response_status
37
33
  assert_equal 'Not found', req.response_body
@@ -108,7 +104,7 @@ class AppTest < Minitest::Test
108
104
  assert_equal 'About', req.response_body.chomp
109
105
 
110
106
  req = make_request(':method' => 'GET', ':path' => '/test/about/foo')
111
- assert_equal '<p>Hello from Markdown</p>', req.response_body.chomp
107
+ assert_equal '<!DOCTYPE html><html><head><title></title></head><body><p>Hello from Markdown</p></body></html>', req.response_body.gsub(/\n/, '')
112
108
 
113
109
  req = make_request(':method' => 'HEAD', ':path' => '/test/about/foo')
114
110
  assert_nil req.response_body
@@ -118,6 +114,10 @@ class AppTest < Minitest::Test
118
114
 
119
115
  req = make_request(':method' => 'GET', ':path' => '/test/params/abc')
120
116
  assert_equal '/test/params/[foo]-abc', req.response_body.chomp
117
+
118
+ req = make_request(':method' => 'GET', ':path' => '/test/rss')
119
+ assert_equal '<link>foo</link>', req.response_body
120
+
121
121
  end
122
122
 
123
123
  def test_app_file_watching
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ class JSONAPITest < Minitest::Test
6
+ class TestAPI < Syntropy::JSONAPI
7
+ def foo(req)
8
+ @value
9
+ end
10
+
11
+ def bar!(req)
12
+ @value = req.query[:v]
13
+ true
14
+ end
15
+ end
16
+
17
+ def setup
18
+ @app = TestAPI.new({})
19
+ end
20
+
21
+ def test_json_api
22
+ req = mock_req(':method' => 'GET', ':path' => '/')
23
+ @app.call(req)
24
+ assert_equal Qeweney::Status::BAD_REQUEST, req.response_status
25
+
26
+ req = mock_req(':method' => 'GET', ':path' => '/?q=foo')
27
+ @app.call(req)
28
+ assert_equal Qeweney::Status::OK, req.response_status
29
+ assert_equal({ status: 'OK', response: nil }, req.response_json)
30
+
31
+ req = mock_req(':method' => 'POST', ':path' => '/?q=foo')
32
+ @app.call(req)
33
+ assert_equal Qeweney::Status::METHOD_NOT_ALLOWED, req.response_status
34
+
35
+
36
+ req = mock_req(':method' => 'POST', ':path' => '/?q=bar&v=foo')
37
+ @app.call(req)
38
+ assert_equal Qeweney::Status::OK, req.response_status
39
+ assert_equal({ status: 'OK', response: true }, req.response_json)
40
+
41
+ req = mock_req(':method' => 'GET', ':path' => '/?q=bar&v=foo')
42
+ @app.call(req)
43
+ assert_equal Qeweney::Status::METHOD_NOT_ALLOWED, req.response_status
44
+
45
+ req = mock_req(':method' => 'GET', ':path' => '/?q=foo')
46
+ @app.call(req)
47
+ assert_equal Qeweney::Status::OK, req.response_status
48
+ assert_equal({ status: 'OK', response: 'foo' }, req.response_json)
49
+
50
+ req = mock_req(':method' => 'GET', ':path' => '/?q=foo')
51
+ @app.call(req)
52
+ assert_equal Qeweney::Status::OK, req.response_status
53
+ assert_equal({ status: 'OK', response: 'foo' }, req.response_json)
54
+
55
+ req = mock_req(':method' => 'GET', ':path' => '/?q=xxx')
56
+ @app.call(req)
57
+ assert_equal Qeweney::Status::NOT_FOUND, req.response_status
58
+ end
59
+ end
@@ -292,9 +292,6 @@ class RoutingTreeTest < Minitest::Test
292
292
  rt = Syntropy::RoutingTree.new(root_dir: File.join(@root_dir, 'site'), mount_path: '/')
293
293
  router = rt.router_proc
294
294
 
295
- # pp rt.root
296
- # puts
297
-
298
295
  route = router.('/docs/df/p2/issues/14', {})
299
296
  assert_nil route
300
297
 
@@ -372,4 +369,42 @@ class RoutingTreeTest < Minitest::Test
372
369
  assert_equal File.join(@rt.root_dir, 'posts/[id].rb'), route[:target][:fn]
373
370
  assert_equal 'foo', params['id']
374
371
  end
372
+
373
+ def test_mount_applet
374
+ rt = Syntropy::RoutingTree.new(root_dir: File.join(@root_dir, 'site'), mount_path: '/')
375
+ applet = ->(req) { :foo }
376
+ rt.mount_applet('/foo/bar', applet)
377
+ router = rt.router_proc
378
+
379
+ refute_nil rt.dynamic_map['/foo/bar']
380
+ assert_equal applet, rt.dynamic_map['/foo/bar'][:proc]
381
+
382
+ route = router.('/foo/bar', {})
383
+ assert_equal applet, route[:proc]
384
+ end
385
+
386
+ def test_mount_applet_nested_mount_path
387
+ rt = Syntropy::RoutingTree.new(root_dir: File.join(@root_dir, 'site'), mount_path: '/my/site')
388
+ applet = ->(req) { :foo }
389
+ rt.mount_applet('/my/site/foo/bar', applet)
390
+ router = rt.router_proc
391
+
392
+ refute_nil rt.dynamic_map['/my/site/foo/bar']
393
+ assert_equal applet, rt.dynamic_map['/my/site/foo/bar'][:proc]
394
+
395
+ route = router.('/my/site/foo/bar', {})
396
+ assert_equal applet, route[:proc]
397
+ end
398
+
399
+ def test_mount_applet_clash
400
+ rt = Syntropy::RoutingTree.new(root_dir: File.join(@root_dir, 'site'), mount_path: '/')
401
+ applet = ->(req) { :foo }
402
+ assert_raises(Syntropy::Error) {
403
+ rt.mount_applet('/about', applet)
404
+ }
405
+
406
+ assert_raises(Syntropy::Error) {
407
+ rt.mount_applet('/old/baz', applet)
408
+ }
409
+ end
375
410
  end
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.15.1
4
+ version: '0.17'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
@@ -43,28 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - '='
45
45
  - !ruby/object:Gem::Version
46
- version: '2.8'
47
- type: :runtime
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - '='
52
- - !ruby/object:Gem::Version
53
- version: '2.8'
54
- - !ruby/object:Gem::Dependency
55
- name: papercraft
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - '='
59
- - !ruby/object:Gem::Version
60
- version: '1.4'
46
+ version: '2.13'
61
47
  type: :runtime
62
48
  prerelease: false
63
49
  version_requirements: !ruby/object:Gem::Requirement
64
50
  requirements:
65
51
  - - '='
66
52
  - !ruby/object:Gem::Version
67
- version: '1.4'
53
+ version: '2.13'
68
54
  - !ruby/object:Gem::Dependency
69
55
  name: qeweney
70
56
  requirement: !ruby/object:Gem::Requirement
@@ -85,14 +71,14 @@ dependencies:
85
71
  requirements:
86
72
  - - '='
87
73
  - !ruby/object:Gem::Version
88
- version: '0.15'
74
+ version: '0.16'
89
75
  type: :runtime
90
76
  prerelease: false
91
77
  version_requirements: !ruby/object:Gem::Requirement
92
78
  requirements:
93
79
  - - '='
94
80
  - !ruby/object:Gem::Version
95
- version: '0.15'
81
+ version: '0.16'
96
82
  - !ruby/object:Gem::Dependency
97
83
  name: uringmachine
98
84
  requirement: !ruby/object:Gem::Requirement
@@ -204,16 +190,31 @@ files:
204
190
  - cmd/setup/template/site/site/assets/img/syntropy.png
205
191
  - cmd/setup/template/site/site/index.rb
206
192
  - docker-compose.yml
193
+ - examples/card.rb
194
+ - examples/counter.js
195
+ - examples/counter.rb
196
+ - examples/counter_api.rb
197
+ - examples/favicon.ico
198
+ - examples/index.md
199
+ - examples/templates.rb
207
200
  - lib/syntropy.rb
208
201
  - lib/syntropy/app.rb
202
+ - lib/syntropy/applets/builtin/auto_refresh/watch.js
203
+ - lib/syntropy/applets/builtin/auto_refresh/watch.sse.rb
204
+ - lib/syntropy/applets/builtin/debug/debug.css
205
+ - lib/syntropy/applets/builtin/debug/debug.js
206
+ - lib/syntropy/applets/builtin/json_api.js
207
+ - lib/syntropy/applets/builtin/ping.rb
209
208
  - lib/syntropy/connection_pool.rb
209
+ - lib/syntropy/dev_mode.rb
210
210
  - lib/syntropy/errors.rb
211
211
  - lib/syntropy/file_watch.rb
212
+ - lib/syntropy/json_api.rb
212
213
  - lib/syntropy/markdown.rb
213
214
  - lib/syntropy/module.rb
215
+ - lib/syntropy/p2_extensions.rb
214
216
  - lib/syntropy/request_extensions.rb
215
217
  - lib/syntropy/routing_tree.rb
216
- - lib/syntropy/rpc_api.rb
217
218
  - lib/syntropy/side_run.rb
218
219
  - lib/syntropy/utils.rb
219
220
  - lib/syntropy/version.rb
@@ -236,29 +237,31 @@ files:
236
237
  - test/app/baz.rb
237
238
  - test/app/index.html
238
239
  - test/app/params/[foo].rb
240
+ - test/app/rss.rb
239
241
  - test/app/tmp.rb
240
242
  - test/app_custom/_site.rb
241
243
  - test/app_multi_site/_site.rb
242
244
  - test/app_multi_site/bar.baz/index.html
243
245
  - test/app_multi_site/foo.bar/index.html
246
+ - test/bm_router_proc.rb
244
247
  - test/helper.rb
245
248
  - test/run.rb
246
249
  - test/test_app.rb
247
250
  - test/test_connection_pool.rb
248
251
  - test/test_errors.rb
249
252
  - test/test_file_watch.rb
253
+ - test/test_json_api.rb
250
254
  - test/test_module.rb
251
255
  - test/test_request_extensions.rb
252
256
  - test/test_routing_tree.rb
253
- - test/test_rpc_api.rb
254
257
  - test/test_side_run.rb
255
- homepage: https://github.com/noteflakes/syntropy
258
+ homepage: https://github.com/digital-fabric/syntropy
256
259
  licenses:
257
260
  - MIT
258
261
  metadata:
259
- homepage_uri: https://github.com/noteflakes/syntropy
262
+ homepage_uri: https://github.com/digital-fabric/syntropy
260
263
  documentation_uri: https://www.rubydoc.info/gems/syntropy
261
- changelog_uri: https://github.com/noteflakes/syntropy/blob/master/CHANGELOG.md
264
+ changelog_uri: https://github.com/digital-fabric/syntropy/blob/master/CHANGELOG.md
262
265
  rdoc_options:
263
266
  - "--title"
264
267
  - Extralite
@@ -277,7 +280,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
277
280
  - !ruby/object:Gem::Version
278
281
  version: '0'
279
282
  requirements: []
280
- rubygems_version: 3.6.9
283
+ rubygems_version: 3.7.0.dev
281
284
  specification_version: 4
282
285
  summary: Syntropic Web Framework
283
286
  test_files: []
data/test/test_rpc_api.rb DELETED
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'helper'
4
-
5
- class RPCAPITest < Minitest::Test
6
- class TestAPI < Syntropy::RPCAPI
7
- def get(req)
8
- @value
9
- end
10
-
11
- def set!(req)
12
- @value = req.query[:v]
13
- true
14
- end
15
- end
16
-
17
- def setup
18
- @app = TestAPI.new({})
19
- end
20
-
21
- def test_rpc_api
22
- req = mock_req(':method' => 'GET', ':path' => '/')
23
- @app.call(req)
24
- assert_equal Qeweney::Status::BAD_REQUEST, req.response_status
25
-
26
- req = mock_req(':method' => 'GET', ':path' => '/?q=get')
27
- @app.call(req)
28
- assert_equal Qeweney::Status::OK, req.response_status
29
- assert_equal({ status: 'OK', response: nil }, req.response_json)
30
-
31
- req = mock_req(':method' => 'POST', ':path' => '/?q=set&v=foo')
32
- @app.call(req)
33
- assert_equal Qeweney::Status::OK, req.response_status
34
- assert_equal({ status: 'OK', response: true }, req.response_json)
35
-
36
- req = mock_req(':method' => 'GET', ':path' => '/?q=get')
37
- @app.call(req)
38
- assert_equal Qeweney::Status::OK, req.response_status
39
- assert_equal({ status: 'OK', response: 'foo' }, req.response_json)
40
- end
41
- end