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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +37 -5
- data/TODO.md +145 -0
- data/bin/syntropy +14 -5
- data/examples/card.rb +12 -0
- data/examples/counter.js +20 -0
- data/examples/counter.rb +23 -0
- data/examples/counter_api.rb +22 -0
- data/examples/favicon.ico +0 -0
- data/examples/index.md +8 -0
- data/examples/templates.rb +40 -0
- data/lib/syntropy/app.rb +58 -22
- data/lib/syntropy/applets/builtin/auto_refresh/watch.js +12 -0
- data/lib/syntropy/applets/builtin/auto_refresh/watch.sse.rb +49 -0
- data/lib/syntropy/applets/builtin/debug/debug.css +61 -0
- data/lib/syntropy/applets/builtin/debug/debug.js +57 -0
- data/lib/syntropy/applets/builtin/json_api.js +28 -0
- data/lib/syntropy/applets/builtin/ping.rb +3 -0
- data/lib/syntropy/dev_mode.rb +11 -0
- data/lib/syntropy/{rpc_api.rb → json_api.rb} +1 -1
- data/lib/syntropy/module.rb +48 -18
- data/lib/syntropy/p2_extensions.rb +12 -0
- data/lib/syntropy/routing_tree.rb +77 -5
- data/lib/syntropy/utils.rb +11 -0
- data/lib/syntropy/version.rb +1 -1
- data/lib/syntropy.rb +3 -3
- data/syntropy.gemspec +5 -6
- data/test/app/api+.rb +1 -1
- data/test/app/rss.rb +3 -0
- data/test/bm_router_proc.rb +201 -0
- data/test/test_app.rb +5 -5
- data/test/test_json_api.rb +59 -0
- data/test/test_routing_tree.rb +38 -3
- metadata +28 -25
- data/test/test_rpc_api.rb +0 -41
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This module implements an SSE (server-sent events) route, that emits a message
|
4
|
+
# to the client when a file has been changed. The module is signalled by the
|
5
|
+
# running app whenever a file change, when in watch mode (`-w`). This route
|
6
|
+
# resides by default at `/.syntropy/auto_refresh/watch.sse`.
|
7
|
+
#
|
8
|
+
# The complementary client-side party is implemented in a small JS script
|
9
|
+
# residing by default at `/.syntropy/auto_refresh/watch.js`.
|
10
|
+
|
11
|
+
# Returns a hash holding references to queues for ongoing `watch.sse` requests.
|
12
|
+
def watchers
|
13
|
+
@watchers ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
# Signals a file change by pushing to all watcher queues.
|
17
|
+
def signal!
|
18
|
+
watchers.each_key { @machine.push(it, true) }
|
19
|
+
end
|
20
|
+
|
21
|
+
# Handles incoming requests to the `watch.sse` route. Adds a queue to the list
|
22
|
+
# of watchers, and waits for the queue to be signalled. In the absence of file
|
23
|
+
# change, a timeout occurs after one minute, and the request is terminated.
|
24
|
+
def call(req)
|
25
|
+
queue = UM::Queue.new
|
26
|
+
watchers[queue] = true
|
27
|
+
|
28
|
+
req.send_headers('Content-Type' => 'text/event-stream')
|
29
|
+
req.send_chunk("data: \n\n")
|
30
|
+
@machine.timeout(60, Timeout::Error) do
|
31
|
+
@machine.shift(queue)
|
32
|
+
req.send_chunk("data: refresh\n\n")
|
33
|
+
end
|
34
|
+
req.send_chunk("retry: 0\n\n", done: true) rescue nil
|
35
|
+
rescue Timeout::Error
|
36
|
+
req.send_chunk("retry: 0\n\n", done: true) rescue nil
|
37
|
+
rescue SystemCallError
|
38
|
+
# ignore
|
39
|
+
rescue => e
|
40
|
+
@logger&.error(
|
41
|
+
message: 'Unexpected error encountered while serving auto refresh watcher',
|
42
|
+
error: e
|
43
|
+
)
|
44
|
+
req.finish rescue nil
|
45
|
+
ensure
|
46
|
+
watchers.delete(queue)
|
47
|
+
end
|
48
|
+
|
49
|
+
export self
|
@@ -0,0 +1,61 @@
|
|
1
|
+
debug-attachment {
|
2
|
+
position: relative;
|
3
|
+
/* display: block; */
|
4
|
+
top: 0;
|
5
|
+
right: 0;
|
6
|
+
bottom: 0;
|
7
|
+
left: 0;
|
8
|
+
/* opacity: 0; */
|
9
|
+
width: 100%;
|
10
|
+
height: 100%;
|
11
|
+
}
|
12
|
+
|
13
|
+
debug-label {
|
14
|
+
display: block;
|
15
|
+
position: absolute;
|
16
|
+
top: -12px;
|
17
|
+
left: 4px;
|
18
|
+
background: rgba(0, 0, 0, 0.8);
|
19
|
+
color: white;
|
20
|
+
padding: 2px 6px;
|
21
|
+
font-size: 11px;
|
22
|
+
font-family: 'Segoe UI', Roboto, monospace;
|
23
|
+
font-weight: 500;
|
24
|
+
border-radius: 3px;
|
25
|
+
display: block;
|
26
|
+
z-index: 1001;
|
27
|
+
cursor: pointer;
|
28
|
+
/* transition: all 0.2s ease; */
|
29
|
+
white-space: nowrap;
|
30
|
+
line-height: 1.2;
|
31
|
+
|
32
|
+
a, a:hover, a:visited {
|
33
|
+
color: white;
|
34
|
+
}
|
35
|
+
|
36
|
+
&:hover {
|
37
|
+
display: block;
|
38
|
+
}
|
39
|
+
|
40
|
+
&.fn {
|
41
|
+
display: block;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
body *:not(script)[data-syntropy-level]:hover {
|
46
|
+
outline: #13630c dotted 2px;
|
47
|
+
outline-offset: 2px;
|
48
|
+
|
49
|
+
/* debug-label {
|
50
|
+
display: block;
|
51
|
+
} */
|
52
|
+
}
|
53
|
+
|
54
|
+
body[data-syntropy-level="1"], body *:not(script)[data-syntropy-level="1"] {
|
55
|
+
outline: #2357aa dotted 2px;
|
56
|
+
outline-offset: 2px;
|
57
|
+
}
|
58
|
+
|
59
|
+
card {
|
60
|
+
display: block;
|
61
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
(() => {
|
2
|
+
const jsURL = import.meta.url;
|
3
|
+
const cssURL = jsURL.replace(/\.js$/, '.css');
|
4
|
+
|
5
|
+
const head = document.querySelector('head');
|
6
|
+
const link = document.createElement('link');
|
7
|
+
link.rel = 'stylesheet';
|
8
|
+
link.type = 'text/css';
|
9
|
+
link.href = cssURL;
|
10
|
+
head.appendChild(link);
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
let lastTarget = undefined;
|
15
|
+
document.querySelectorAll('[data-syntropy-level]').forEach((ele) => {
|
16
|
+
// ele.addEventListener('mouseover', (evt) => {
|
17
|
+
// if (evt.target != lastTarget) {
|
18
|
+
|
19
|
+
// }
|
20
|
+
// console.log(evt)
|
21
|
+
// });
|
22
|
+
|
23
|
+
// ele.addEventListener('mouseout', (evt) => {
|
24
|
+
// if (evt.target == last)
|
25
|
+
// });
|
26
|
+
|
27
|
+
const parent = ele.parentElement;
|
28
|
+
const attachment = document.createElement('debug-attachment');
|
29
|
+
const tag = ele.tagName;
|
30
|
+
if (tag == 'SCRIPT' || tag == 'HEAD') return;
|
31
|
+
|
32
|
+
const level = ele.dataset.syntropyLevel;
|
33
|
+
const href = ele.dataset.syntropyLoc;
|
34
|
+
const fn = ele.dataset.syntropyFn;
|
35
|
+
|
36
|
+
let attachToParent = false; //(tag != 'BODY');
|
37
|
+
|
38
|
+
if (level == '1') {
|
39
|
+
const cleanFn = fn.match(/([^\/]+)$/)[0];
|
40
|
+
attachment.innerHTML = `<debug-label class="fn"><a href="${href}">${cleanFn}</a></debug-label>`;
|
41
|
+
}
|
42
|
+
else {
|
43
|
+
attachToParent = true;
|
44
|
+
attachment.innerHTML = `<debug-label><a href="${href}">${tag}</a></debug-label>`;
|
45
|
+
}
|
46
|
+
|
47
|
+
// console.log(tag, attachToParent);
|
48
|
+
if (attachToParent) {
|
49
|
+
parent.style.position = 'relative';
|
50
|
+
parent.prepend(attachment);
|
51
|
+
}
|
52
|
+
else {
|
53
|
+
ele.style.position = 'relative';
|
54
|
+
ele.prepend(attachment);
|
55
|
+
}
|
56
|
+
});
|
57
|
+
})()
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class JSONAPI {
|
2
|
+
constructor(url) {
|
3
|
+
this.url = url;
|
4
|
+
}
|
5
|
+
|
6
|
+
async get(q, params = {}) {
|
7
|
+
return await this.#query('get', q, params);
|
8
|
+
}
|
9
|
+
|
10
|
+
async post(q, params = {}) {
|
11
|
+
return await this.#query('post', q, params);
|
12
|
+
}
|
13
|
+
|
14
|
+
async #query(method, q, params = {}) {
|
15
|
+
const url = `${this.url}?q=${q}`;
|
16
|
+
const req = fetch(url, {
|
17
|
+
method: method
|
18
|
+
});
|
19
|
+
const response = await req;
|
20
|
+
if (!response.ok)
|
21
|
+
throw new Error(`Response status: ${response.status}`)
|
22
|
+
|
23
|
+
const result = await response.json();
|
24
|
+
return result.response;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
export default JSONAPI;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
TAG_DEBUG_PROC = ->(level, fn, line, col) {
|
4
|
+
{
|
5
|
+
'data-syntropy-level' => level,
|
6
|
+
'data-syntropy-fn' => fn,
|
7
|
+
'data-syntropy-loc' => "vscode://file/#{fn}:#{line}:#{col}"
|
8
|
+
}
|
9
|
+
}
|
10
|
+
|
11
|
+
P2::Compiler.html_debug_attribute_injector = TAG_DEBUG_PROC
|
data/lib/syntropy/module.rb
CHANGED
@@ -6,15 +6,15 @@ module Syntropy
|
|
6
6
|
# The ModuleLoader class implemenets a module loader. It handles loading of
|
7
7
|
# modules, tracking of dependencies between modules, and invalidation of
|
8
8
|
# loaded modules (following a change to the module file).
|
9
|
-
#
|
9
|
+
#
|
10
10
|
# A module may implement a route endpoint, a layout template, utility methods,
|
11
11
|
# classes, or any other functionality needed by the web app.
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# Modules are Ruby files that can import other modules as dependencies. A
|
14
14
|
# module must export a single value, which can be a class, a template, a proc,
|
15
15
|
# or any other Ruby object. A module can also export itself by calling `export
|
16
16
|
# self`.
|
17
|
-
#
|
17
|
+
#
|
18
18
|
# Modules are referenced relative to the web app's root directory, without the
|
19
19
|
# `.rb` extension. For example, for a site residing in `/my_site`, the
|
20
20
|
# reference `_lib/foo` will point to a module residing in
|
@@ -23,7 +23,7 @@ module Syntropy
|
|
23
23
|
attr_reader :modules
|
24
24
|
|
25
25
|
# Instantiates a module loader
|
26
|
-
#
|
26
|
+
#
|
27
27
|
# @param env [Hash] environment hash
|
28
28
|
# @return [void]
|
29
29
|
def initialize(env)
|
@@ -34,7 +34,7 @@ module Syntropy
|
|
34
34
|
end
|
35
35
|
|
36
36
|
# Loads a module (if not already loaded) and returns its export value.
|
37
|
-
#
|
37
|
+
#
|
38
38
|
# @param ref [String] module reference
|
39
39
|
# @return [any] export value
|
40
40
|
def load(ref)
|
@@ -46,7 +46,7 @@ module Syntropy
|
|
46
46
|
# underlying file (in order to cause reloading of the module). The module
|
47
47
|
# will be removed from the modules map, as well as modules dependending on
|
48
48
|
# it.
|
49
|
-
#
|
49
|
+
#
|
50
50
|
# @param fn [String] module filename
|
51
51
|
# @return [void]
|
52
52
|
def invalidate_fn(fn)
|
@@ -62,7 +62,7 @@ module Syntropy
|
|
62
62
|
# underlying file (in order to cause reloading of the module). The module
|
63
63
|
# will be removed from the modules map, as well as modules dependending on
|
64
64
|
# it.
|
65
|
-
#
|
65
|
+
#
|
66
66
|
# @param ref [String] module reference
|
67
67
|
# @return [void]
|
68
68
|
def invalidate_ref(ref)
|
@@ -74,7 +74,7 @@ module Syntropy
|
|
74
74
|
end
|
75
75
|
|
76
76
|
# Registers reverse dependencies for the given module reference.
|
77
|
-
#
|
77
|
+
#
|
78
78
|
# @param ref [String] module reference
|
79
79
|
# @param deps [Array<String>] array of dependencies for the given module
|
80
80
|
# @return [void]
|
@@ -82,14 +82,14 @@ module Syntropy
|
|
82
82
|
deps.each do
|
83
83
|
entry = @modules[it]
|
84
84
|
next if !entry
|
85
|
-
|
85
|
+
|
86
86
|
entry[:reverse_deps] << ref
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
90
90
|
# Loads a module and returns a module entry. Any dependencies (using
|
91
91
|
# `import`) are loaded as well.
|
92
|
-
#
|
92
|
+
#
|
93
93
|
# @param ref [String] module reference
|
94
94
|
# @return [Hash] module entry
|
95
95
|
def load_module(ref)
|
@@ -112,7 +112,7 @@ module Syntropy
|
|
112
112
|
|
113
113
|
# Transforms the given export value. If the value is nil, an exception is
|
114
114
|
# raised.
|
115
|
-
#
|
115
|
+
#
|
116
116
|
# @param export_value [any] module's export value
|
117
117
|
# @return [any] transformed value
|
118
118
|
def transform_module_export_value(export_value)
|
@@ -132,6 +132,19 @@ module Syntropy
|
|
132
132
|
# The Syntropy::Module class implements a reloadable module. A module is a
|
133
133
|
# `.rb` source file that implements a route endpoint, a template, utility
|
134
134
|
# methods or any other functionality needed by the web app.
|
135
|
+
#
|
136
|
+
# The following instance variables are available to modules:
|
137
|
+
#
|
138
|
+
# - `@env`: the app environment hash
|
139
|
+
# - `@machine`: a reference to the UringMachine instance
|
140
|
+
# - `@module_loader`: a reference to the module loader
|
141
|
+
# - `@app`: a reference to the app
|
142
|
+
# - `@ref`: the module's logical path (path relative to the app root)
|
143
|
+
# - `@logger`: a reference to the app's logger
|
144
|
+
#
|
145
|
+
# In addition, the module code also has access to the `MODULE` constant which
|
146
|
+
# is set to `self`, and may be used to refer to various methods defined in the
|
147
|
+
# module.
|
135
148
|
class Module
|
136
149
|
# Loads a module, returning the module instance
|
137
150
|
def self.load(env, code, fn)
|
@@ -148,7 +161,7 @@ module Syntropy
|
|
148
161
|
end
|
149
162
|
|
150
163
|
# Initializes a module with the given environment hash.
|
151
|
-
#
|
164
|
+
#
|
152
165
|
# @param env [Hash] environment hash
|
153
166
|
# @return [void]
|
154
167
|
def initialize(**env)
|
@@ -157,6 +170,7 @@ module Syntropy
|
|
157
170
|
@module_loader = env[:module_loader]
|
158
171
|
@app = env[:app]
|
159
172
|
@ref = env[:ref]
|
173
|
+
@logger = env[:logger]
|
160
174
|
singleton_class.const_set(:MODULE, self)
|
161
175
|
end
|
162
176
|
|
@@ -165,7 +179,7 @@ module Syntropy
|
|
165
179
|
# Exports the given value. This value will be used as the module's
|
166
180
|
# entrypoint. It can be any Ruby value, but for a route module would
|
167
181
|
# normally be a proc.
|
168
|
-
#
|
182
|
+
#
|
169
183
|
# @param v [any] export value
|
170
184
|
# @return [void]
|
171
185
|
def export(v)
|
@@ -173,7 +187,7 @@ module Syntropy
|
|
173
187
|
end
|
174
188
|
|
175
189
|
# Returns the list of module references imported by the module.
|
176
|
-
#
|
190
|
+
#
|
177
191
|
# @return [Array] array of module references
|
178
192
|
def __dependencies__
|
179
193
|
@__dependencies__ ||= []
|
@@ -181,7 +195,7 @@ module Syntropy
|
|
181
195
|
|
182
196
|
# Imports the module corresponding to the given reference. The return value
|
183
197
|
# is the module's export value.
|
184
|
-
#
|
198
|
+
#
|
185
199
|
# @param ref [String] module reference
|
186
200
|
# @return [any] loaded dependency's export value
|
187
201
|
def import(ref)
|
@@ -191,7 +205,7 @@ module Syntropy
|
|
191
205
|
end
|
192
206
|
|
193
207
|
# Creates and returns a P2 template created with the given block.
|
194
|
-
#
|
208
|
+
#
|
195
209
|
# @param proc [Proc, nil] template proc or nil
|
196
210
|
# @param block [Proc] template block
|
197
211
|
# @return [P2::Template] template
|
@@ -202,8 +216,24 @@ module Syntropy
|
|
202
216
|
P2::Template.new(proc)
|
203
217
|
end
|
204
218
|
|
219
|
+
# Creates and returns a P2 XML template created with the given block.
|
220
|
+
#
|
221
|
+
# @param proc [Proc, nil] template proc or nil
|
222
|
+
# @param block [Proc] template block
|
223
|
+
# @return [P2::Template] template
|
224
|
+
def template_xml(proc = nil, &block)
|
225
|
+
proc ||= block
|
226
|
+
raise "No template block/proc given" if !proc
|
227
|
+
|
228
|
+
P2::Template.new(proc, mode: :xml)
|
229
|
+
rescue => e
|
230
|
+
p e
|
231
|
+
p e.backtrace
|
232
|
+
raise
|
233
|
+
end
|
234
|
+
|
205
235
|
# Returns a list of pages found at the given ref.
|
206
|
-
#
|
236
|
+
#
|
207
237
|
# @param ref [String] directory reference
|
208
238
|
# @return [Array] array of pages found in directory
|
209
239
|
def page_list(ref)
|
@@ -211,7 +241,7 @@ module Syntropy
|
|
211
241
|
end
|
212
242
|
|
213
243
|
# Creates and returns a Syntropy app for the given environment.
|
214
|
-
#
|
244
|
+
#
|
215
245
|
# @param env [Hash] environment
|
216
246
|
def app(**env)
|
217
247
|
Syntropy::App.new(**(@env.merge(env)))
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'p2'
|
4
|
+
|
5
|
+
P2.extension(
|
6
|
+
'auto_refresh_watch!': ->(loc = '/.syntropy') {
|
7
|
+
script(src: File.join(loc, 'auto_refresh/watch.js'), type: 'module')
|
8
|
+
},
|
9
|
+
'debug_template!': ->(loc = '/.syntropy') {
|
10
|
+
script(src: File.join(loc, 'debug/debug.js'), type: 'module')
|
11
|
+
}
|
12
|
+
)
|
@@ -55,15 +55,13 @@ module Syntropy
|
|
55
55
|
@dynamic_map = {}
|
56
56
|
@env = env
|
57
57
|
@root = compute_tree
|
58
|
-
@static_map.freeze
|
59
|
-
@dynamic_map.freeze
|
60
58
|
end
|
61
59
|
|
62
60
|
# Returns the generated router proc for the routing tree
|
63
61
|
#
|
64
62
|
# @return [Proc] router proc
|
65
63
|
def router_proc
|
66
|
-
@router_proc ||=
|
64
|
+
@router_proc ||= generate_router_proc
|
67
65
|
end
|
68
66
|
|
69
67
|
# Computes a "clean" URL path for the given path. Modules and markdown are
|
@@ -93,6 +91,17 @@ module Syntropy
|
|
93
91
|
fn.sub(/^#{Regexp.escape(@root_dir)}\//, '').sub(/\.[^\.]+$/, '')
|
94
92
|
end
|
95
93
|
|
94
|
+
# Mounts the given applet on the routng tree at the given (absolute) mount
|
95
|
+
# path. This method must be called before the router proc is generated.
|
96
|
+
#
|
97
|
+
# @param path [String] absolute mount path for the applet
|
98
|
+
# @param applet [Syntropy::App, Proc] applet
|
99
|
+
# @return [void]
|
100
|
+
def mount_applet(path, applet)
|
101
|
+
path = rel_mount_path(path)
|
102
|
+
mount_applet_on_tree(@root, path, applet)
|
103
|
+
end
|
104
|
+
|
96
105
|
private
|
97
106
|
|
98
107
|
# Maps extensions to route kind.
|
@@ -136,6 +145,66 @@ module Syntropy
|
|
136
145
|
compute_route_directory(dir: @root_dir, rel_path: '/', parent: nil)
|
137
146
|
end
|
138
147
|
|
148
|
+
# Converts the given absolute path to a relative one (relative to the
|
149
|
+
# routing tree's mount path).
|
150
|
+
#
|
151
|
+
# @param path [String] absolute mount path
|
152
|
+
# @return [String] relative mount path
|
153
|
+
def rel_mount_path(path)
|
154
|
+
if @mount_path == '/'
|
155
|
+
path.sub(/^\//, '')
|
156
|
+
else
|
157
|
+
path.sub(/^#{Regexp.escape(@mount_path)}\//, '')
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Mounts the given applet as a child of the given entry. If the given
|
162
|
+
# (relative) path is nested, drills down the given entry's subtree and
|
163
|
+
# automatically creates intermediate children entries. If a child entry
|
164
|
+
# already exists for the given path, an error is raised. The given applet
|
165
|
+
# may be an instance of `Syntropy::App` or a proc.
|
166
|
+
#
|
167
|
+
# @param entry [Hash] route entry on which to mount the applet
|
168
|
+
# @param path [String] relative path
|
169
|
+
# @param applet [Syntropy::App, Proc] applet
|
170
|
+
# @return [void]
|
171
|
+
def mount_applet_on_tree(entry, path, applet)
|
172
|
+
if (m = path.match(/^([^\/]+)\/(.+)$/))
|
173
|
+
child_entry = find_or_create_child_entry(entry, m[1])
|
174
|
+
mount_applet_on_tree(child_entry, m[2], applet)
|
175
|
+
else
|
176
|
+
child_entry = entry[:children] && entry[:children][path]
|
177
|
+
raise Syntropy::Error, "Could not mount applet, entry already exists" if child_entry
|
178
|
+
|
179
|
+
applet_path = File.join(entry[:path], path)
|
180
|
+
applet_entry = {
|
181
|
+
parent: entry,
|
182
|
+
path: applet_path,
|
183
|
+
handle_subtree: true,
|
184
|
+
target: { kind: :module },
|
185
|
+
proc: applet
|
186
|
+
}
|
187
|
+
|
188
|
+
(entry[:children] ||= {})[path] = applet_entry
|
189
|
+
@dynamic_map[applet_path] = applet_entry
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Finds or creates a child entry with the given name on the given parent
|
194
|
+
# entry.
|
195
|
+
#
|
196
|
+
# @param parent [Hash] parent entry
|
197
|
+
# @param name [String] child's name
|
198
|
+
# @return [Hash] child entry
|
199
|
+
def find_or_create_child_entry(parent, name)
|
200
|
+
parent[:children] ||= {}
|
201
|
+
parent[:children][name] ||= {
|
202
|
+
parent: parent,
|
203
|
+
path: File.join(parent[:path], name),
|
204
|
+
children: {}
|
205
|
+
}
|
206
|
+
end
|
207
|
+
|
139
208
|
# Computes a route entry for a directory.
|
140
209
|
#
|
141
210
|
# @param dir [String] directory path
|
@@ -341,10 +410,13 @@ module Syntropy
|
|
341
410
|
entry[:param] ? '[]' : File.basename(entry[:path]).gsub(/\+$/, '')
|
342
411
|
end
|
343
412
|
|
344
|
-
#
|
413
|
+
# Freezes the static and dynamic maps, generates and returns a router proc
|
414
|
+
# based on the routing tree.
|
345
415
|
#
|
346
416
|
# @return [Proc] router proc
|
347
|
-
def
|
417
|
+
def generate_router_proc
|
418
|
+
@static_map.freeze
|
419
|
+
@dynamic_map.freeze
|
348
420
|
code = generate_routing_tree_code
|
349
421
|
eval(code, binding, '(router)', 1)
|
350
422
|
end
|
data/lib/syntropy/utils.rb
CHANGED
@@ -37,5 +37,16 @@ module Syntropy
|
|
37
37
|
def app(**env)
|
38
38
|
Syntropy::App.new(**env)
|
39
39
|
end
|
40
|
+
|
41
|
+
BUILTIN_APPLET_ROOT_DIR = File.expand_path(File.join(__dir__, 'applets/builtin'))
|
42
|
+
def builtin_applet(env, mount_path: '/.syntropy')
|
43
|
+
app(
|
44
|
+
machine: env[:machine],
|
45
|
+
root_dir: BUILTIN_APPLET_ROOT_DIR,
|
46
|
+
mount_path: mount_path,
|
47
|
+
builtin_applet_path: nil,
|
48
|
+
watch_files: nil
|
49
|
+
)
|
50
|
+
end
|
40
51
|
end
|
41
52
|
end
|
data/lib/syntropy/version.rb
CHANGED
data/lib/syntropy.rb
CHANGED
@@ -4,7 +4,6 @@ require 'qeweney'
|
|
4
4
|
require 'uringmachine'
|
5
5
|
require 'tp2'
|
6
6
|
require 'p2'
|
7
|
-
require 'papercraft'
|
8
7
|
|
9
8
|
require 'syntropy/app'
|
10
9
|
require 'syntropy/connection_pool'
|
@@ -12,8 +11,9 @@ require 'syntropy/errors'
|
|
12
11
|
require 'syntropy/markdown'
|
13
12
|
require 'syntropy/module'
|
14
13
|
require 'syntropy/request_extensions'
|
14
|
+
require 'syntropy/p2_extensions'
|
15
15
|
require 'syntropy/routing_tree'
|
16
|
-
require 'syntropy/
|
16
|
+
require 'syntropy/json_api'
|
17
17
|
require 'syntropy/side_run'
|
18
18
|
require 'syntropy/utils'
|
19
19
|
|
@@ -47,7 +47,7 @@ module Syntropy
|
|
47
47
|
" #{GREEN}ooooo\n"\
|
48
48
|
" #{GREEN} ooo vvv #{CLEAR}Syntropy - a web framework for Ruby\n"\
|
49
49
|
" #{GREEN} o vvvvv #{CLEAR}--------------------------------------\n"\
|
50
|
-
" #{GREEN} #{YELLOW}|#{GREEN} vvv o #{CLEAR}https://github.com/
|
50
|
+
" #{GREEN} #{YELLOW}|#{GREEN} vvv o #{CLEAR}https://github.com/digital-fabric/syntropy\n"\
|
51
51
|
" #{GREEN} :#{YELLOW}|#{GREEN}:::#{YELLOW}|#{GREEN}::#{YELLOW}|#{GREEN}:\n"\
|
52
52
|
"#{YELLOW}+++++++++++++++++++++++++++++++++++++++++++++++++++++++++\e[0m\n\n"
|
53
53
|
end
|
data/syntropy.gemspec
CHANGED
@@ -9,11 +9,11 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.email = 'sharon@noteflakes.com'
|
10
10
|
s.files = `git ls-files`.split
|
11
11
|
|
12
|
-
s.homepage = 'https://github.com/
|
12
|
+
s.homepage = 'https://github.com/digital-fabric/syntropy'
|
13
13
|
s.metadata = {
|
14
|
-
'homepage_uri' => 'https://github.com/
|
14
|
+
'homepage_uri' => 'https://github.com/digital-fabric/syntropy',
|
15
15
|
'documentation_uri' => 'https://www.rubydoc.info/gems/syntropy',
|
16
|
-
'changelog_uri' => 'https://github.com/
|
16
|
+
'changelog_uri' => 'https://github.com/digital-fabric/syntropy/blob/master/CHANGELOG.md'
|
17
17
|
}
|
18
18
|
s.rdoc_options = ['--title', 'Extralite', '--main', 'README.md']
|
19
19
|
s.extra_rdoc_files = ['README.md']
|
@@ -23,10 +23,9 @@ Gem::Specification.new do |s|
|
|
23
23
|
|
24
24
|
s.add_dependency 'extralite', '2.13'
|
25
25
|
s.add_dependency 'json', '2.13.2'
|
26
|
-
s.add_dependency 'p2', '2.
|
27
|
-
s.add_dependency 'papercraft', '1.4'
|
26
|
+
s.add_dependency 'p2', '2.13'
|
28
27
|
s.add_dependency 'qeweney', '0.22'
|
29
|
-
s.add_dependency 'tp2', '0.
|
28
|
+
s.add_dependency 'tp2', '0.16'
|
30
29
|
s.add_dependency 'uringmachine', '0.18'
|
31
30
|
|
32
31
|
s.add_dependency 'listen', '3.9.0'
|
data/test/app/api+.rb
CHANGED
data/test/app/rss.rb
ADDED