syntropy 0.13.1 → 0.15

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: dad58d19dc755d1a8ea6adb32cd7146b2228b5ace655a01b2e7932fd8abb7d23
4
- data.tar.gz: 228678e0a76e67b012e52c89f5ed90c3d08e884a6424c0c51fcf9f840b2e6876
3
+ metadata.gz: 8fda8af32a2963e75aa4f1a600ecda8d3d9c927377d9a972314ebb28d35575fb
4
+ data.tar.gz: 7fe707495be32c3c72aa0d6d7019056eaac16567caa5d3b5dfe8e8541e17f234
5
5
  SHA512:
6
- metadata.gz: 5ea7c453ae3e32d17f4bc35204fcf4c29f4eae7d95e9232f56b6ff65dcfaab9f3f6e41fb5562817ab3d3bd10a3ee2e5c92c440045bbdc8a51bc88d155fac7ab4
7
- data.tar.gz: fb28a9011ea58505b9923589b62f0010eb97dea9f1df81e4c0483682669715c455fff5a150888f06f117e127497a98e7c93535f683d924e86ffcfeb007cf9fc5
6
+ metadata.gz: 266dd7fdbd8b5cb6b9a68cf6cab367c1f089ac2a59141d19368614d8bb0eda7c52d1697d1d67bb404ea35894673fea02ee2ed655d16a7cf26209bd20bcaa3b63
7
+ data.tar.gz: 2e31bc7fabe3e59557dbb719d7148f9a8f77cd685806fb0e9cbbd187f8ef3aedab12a1a256b915fd461e82e450b8c6b25e8b548d50b09e1d782a95351546f335
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ # 0.15 2025-08-31
2
+
3
+ - Implement invalidation of reverse dependencies on module file change
4
+
5
+ # 0.14 2025-08-30
6
+
7
+ - Tweak "boot" sequence
8
+ - Update dependencies
9
+ - Log errors in App#call
10
+ - Improve module loading, add logging
11
+
1
12
  # 0.13 2025-08-28
2
13
 
3
14
  - Reimplement module loading
data/TODO.md CHANGED
@@ -1,3 +1,30 @@
1
+ ## Immediate
2
+
3
+ - [ ] Collection - treat directories and files as collections of data.
4
+
5
+ Kind of similar to the routing tree, but instead of routes it just takes a
6
+ bunch of files and turns it into a dataset. Each directory is a "table" and is
7
+ composed of zero or more files that form rows in the table. Supported file
8
+ formats:
9
+
10
+ - foo.md - markdown with optional front matter
11
+ - foo.json - JSON record
12
+ - foo.yml - YAML record
13
+
14
+ API:
15
+
16
+ ```ruby
17
+ Articles = @app.collection('_articles/*.md')
18
+ article = Articles.last_by(&:date)
19
+
20
+ article.title #=>
21
+ article.date #=>
22
+ article.layout #=>
23
+ article.render_proc #=> (load layout, apply article)
24
+ article.render #=> (render to HTML)
25
+ ...
26
+ ```
27
+
1
28
  - [ ] Improve serving of static files:
2
29
  - [ ] support for compression
3
30
  - [ ] support for caching headers
data/bin/syntropy CHANGED
@@ -4,7 +4,7 @@
4
4
  require 'syntropy'
5
5
  require 'optparse'
6
6
 
7
- opts = {
7
+ env = {
8
8
  mount_path: '/',
9
9
  banner: Syntropy::BANNER,
10
10
  logger: true
@@ -15,17 +15,17 @@ parser = OptionParser.new do |o|
15
15
 
16
16
  o.on('-b', '--bind BIND', String,
17
17
  'Bind address (default: http://0.0.0.0:1234). You can specify this flag multiple times to bind to multiple addresses.') do
18
- opts[:bind] ||= []
19
- opts[:bind] << it
18
+ env[:bind] ||= []
19
+ env[:bind] << it
20
20
  end
21
21
 
22
22
  o.on('-s', '--silent', 'Silent mode') do
23
- opts[:banner] = nil
24
- opts[:logger] = nil
23
+ env[:banner] = nil
24
+ env[:logger] = nil
25
25
  end
26
26
 
27
27
  o.on('-w', '--watch', 'Watch for changed files') do
28
- opts[:watch_files] = 0.1
28
+ env[:watch_files] = 0.1
29
29
  end
30
30
 
31
31
  o.on('-h', '--help', 'Show this help message') do
@@ -34,7 +34,7 @@ parser = OptionParser.new do |o|
34
34
  end
35
35
 
36
36
  o.on('-m', '--mount', 'Set mount path (default: /)') do
37
- opts[:mount_path] = it
37
+ env[:mount_path] = it
38
38
  end
39
39
 
40
40
  o.on('-v', '--version', 'Show version') do
@@ -54,17 +54,21 @@ rescue StandardError => e
54
54
  exit
55
55
  end
56
56
 
57
- opts[:root_dir] = ARGV.shift || '.'
57
+ env[:root_dir] = ARGV.shift || '.'
58
58
 
59
- if !File.directory?(opts[:root_dir])
60
- puts "#{File.expand_path(opts[:root_dir])} Not a directory"
59
+ if !File.directory?(env[:root_dir])
60
+ puts "#{File.expand_path(env[:root_dir])} Not a directory"
61
61
  exit
62
62
  end
63
63
 
64
+ puts env[:banner] if env[:banner]
65
+ env[:banner] = false
66
+
64
67
 
65
68
  # We set Syntropy.machine so we can reference it from anywhere
66
- opts[:machine] = Syntropy.machine = UM.new
67
- opts[:logger] = opts[:logger] && TP2::Logger.new(opts[:machine], **opts)
69
+ env[:machine] = Syntropy.machine = UM.new
70
+ env[:logger] = env[:logger] && TP2::Logger.new(env[:machine], **env)
68
71
 
69
- app = Syntropy::App.load(opts)
70
- TP2.run(opts) { app.call(it) }
72
+ env[:logger]&.info(message: "Running Syntropy version #{Syntropy::VERSION}")
73
+ app = Syntropy::App.load(env)
74
+ TP2.run(env) { app.call(it) }
data/lib/syntropy/app.rb CHANGED
@@ -15,38 +15,38 @@ require 'syntropy/routing_tree'
15
15
  module Syntropy
16
16
  class App
17
17
  class << self
18
- def load(opts)
19
- site_file_app(opts) || default_app(opts)
18
+ def load(env)
19
+ site_file_app(env) || default_app(env)
20
20
  end
21
21
 
22
22
  private
23
23
 
24
24
  # for apps with a _site.rb file
25
- def site_file_app(opts)
26
- fn = File.join(opts[:root_dir], '_site.rb')
25
+ def site_file_app(env)
26
+ fn = File.join(env[:root_dir], '_site.rb')
27
27
  return nil if !File.file?(fn)
28
28
 
29
- loader = Syntropy::ModuleLoader.new(opts)
29
+ loader = Syntropy::ModuleLoader.new(env)
30
30
  loader.load('_site')
31
31
  end
32
32
 
33
33
  # default app
34
- def default_app(opts)
35
- new(**opts)
34
+ def default_app(env)
35
+ new(**env)
36
36
  end
37
37
  end
38
38
 
39
- attr_reader :module_loader, :routing_tree, :root_dir, :mount_path, :opts
40
-
41
- def initialize(**opts)
42
- @machine = opts[:machine]
43
- @root_dir = opts[:root_dir]
44
- @mount_path = opts[:mount_path]
45
- @opts = opts
39
+ attr_reader :module_loader, :routing_tree, :root_dir, :mount_path, :env
46
40
 
47
- @module_loader = Syntropy::ModuleLoader.new(app: self, **opts)
41
+ def initialize(**env)
42
+ @machine = env[:machine]
43
+ @root_dir = File.expand_path(env[:root_dir])
44
+ @mount_path = env[:mount_path]
45
+ @env = env
46
+
47
+ @module_loader = Syntropy::ModuleLoader.new(app: self, **env)
48
48
  setup_routing_tree
49
- start_app
49
+ start
50
50
  end
51
51
 
52
52
  # Processes an incoming HTTP request. Requests are processed by first
@@ -70,8 +70,11 @@ module Syntropy
70
70
  proc = route[:proc] ||= compute_route_proc(route)
71
71
  proc.(req)
72
72
  rescue StandardError => e
73
- # p e
74
- # p e.backtrace
73
+ @env[:logger]&.error(
74
+ message: "Error while serving request",
75
+ method: req.method,
76
+ path: req.path
77
+ )
75
78
  error_handler = get_error_handler(route)
76
79
  error_handler.(req, e)
77
80
  end
@@ -84,7 +87,7 @@ module Syntropy
84
87
  # @return [void]
85
88
  def setup_routing_tree
86
89
  @routing_tree = Syntropy::RoutingTree.new(
87
- root_dir: @root_dir, mount_path: @mount_path, **@opts
90
+ root_dir: @root_dir, mount_path: @mount_path, **@env
88
91
  )
89
92
  @router_proc = @routing_tree.router_proc
90
93
  end
@@ -138,7 +141,7 @@ module Syntropy
138
141
  end
139
142
 
140
143
  def render_markdown(route)
141
- atts, md = Syntropy.parse_markdown_file(route[:target][:fn], @opts)
144
+ atts, md = Syntropy.parse_markdown_file(route[:target][:fn], @env)
142
145
 
143
146
  if (layout = atts[:layout])
144
147
  route[:applied_layouts] ||= {}
@@ -277,15 +280,17 @@ module Syntropy
277
280
  # watcher according to app options.
278
281
  #
279
282
  # @return [void]
280
- def start_app
283
+ def start
281
284
  @machine.spin do
282
285
  # we do startup stuff asynchronously, in order to first let TP2 do its
283
286
  # setup tasks
284
287
  @machine.sleep 0.2
285
- @opts[:logger]&.info(
286
- message: "Serving from #{File.expand_path(@location)}"
288
+ route_count = @routing_tree.static_map.size + @routing_tree.dynamic_map.size
289
+ @env[:logger]&.info(
290
+ message: "Serving from #{@root_dir} (#{route_count} routes found)"
287
291
  )
288
- file_watcher_loop if opts[:watch_files]
292
+
293
+ file_watcher_loop if @env[:watch_files]
289
294
  end
290
295
  end
291
296
 
@@ -294,10 +299,11 @@ module Syntropy
294
299
  #
295
300
  # @return [void]
296
301
  def file_watcher_loop
297
- wf = @opts[:watch_files]
302
+ wf = @env[:watch_files]
298
303
  period = wf.is_a?(Numeric) ? wf : 0.1
299
304
  Syntropy.file_watch(@machine, @root_dir, period: period) do |event, fn|
300
- @module_loader.invalidate(fn)
305
+ @env[:logger]&.info(message: "File change detected", fn: fn)
306
+ @module_loader.invalidate_fn(fn)
301
307
  debounce_file_change
302
308
  end
303
309
  rescue Exception => e
@@ -14,7 +14,7 @@ module Syntropy
14
14
  #
15
15
  # @param path [String] file path
16
16
  # @return [Array] an tuple containing properties<Hash>, contents<String>
17
- def self.parse_markdown_file(path, opts)
17
+ def self.parse_markdown_file(path, env)
18
18
  content = IO.read(path) || ''
19
19
  atts = {}
20
20
 
@@ -30,9 +30,9 @@ module Syntropy
30
30
  atts = atts.merge(yaml)
31
31
  end
32
32
 
33
- if opts[:root_dir]
33
+ if env[:root_dir]
34
34
  atts[:url] = path
35
- .gsub(/#{opts[:root_dir]}/, '')
35
+ .gsub(/#{env[:root_dir]}/, '')
36
36
  .gsub(/\.md$/, '')
37
37
  end
38
38
 
@@ -3,39 +3,118 @@
3
3
  require 'p2'
4
4
 
5
5
  module Syntropy
6
+ # The ModuleLoader class implemenets a module loader. It handles loading of
7
+ # modules, tracking of dependencies between modules, and invalidation of
8
+ # loaded modules (following a change to the module file).
9
+ #
10
+ # A module may implement a route endpoint, a layout template, utility methods,
11
+ # classes, or any other functionality needed by the web app.
12
+ #
13
+ # Modules are Ruby files that can import other modules as dependencies. A
14
+ # module must export a single value, which can be a class, a template, a proc,
15
+ # or any other Ruby object. A module can also export itself by calling `export
16
+ # self`.
17
+ #
18
+ # Modules are referenced relative to the web app's root directory, without the
19
+ # `.rb` extension. For example, for a site residing in `/my_site`, the
20
+ # reference `_lib/foo` will point to a module residing in
21
+ # `/my_site/_lib/foo.rb`.
6
22
  class ModuleLoader
23
+ attr_reader :modules
24
+
25
+ # Instantiates a module loader
26
+ #
27
+ # @param env [Hash] environment hash
28
+ # @return [void]
7
29
  def initialize(env)
8
30
  @root_dir = env[:root_dir]
9
31
  @env = env
10
- @loaded = {} # maps ref to code
32
+ @modules = {} # maps ref to module entry
11
33
  @fn_map = {} # maps filename to ref
12
34
  end
13
35
 
36
+ # Loads a module (if not already loaded) and returns its export value.
37
+ #
38
+ # @param ref [String] module reference
39
+ # @return [any] export value
14
40
  def load(ref)
15
- @loaded[ref] ||= load_module(ref)
41
+ entry = (@modules[ref] ||= load_module(ref))
42
+ entry[:export_value]
16
43
  end
17
44
 
18
- def invalidate(fn)
45
+ # Invalidates a module by its filename, normally following a change to the
46
+ # underlying file (in order to cause reloading of the module). The module
47
+ # will be removed from the modules map, as well as modules dependending on
48
+ # it.
49
+ #
50
+ # @param fn [String] module filename
51
+ # @return [void]
52
+ def invalidate_fn(fn)
19
53
  ref = @fn_map[fn]
20
54
  return if !ref
21
55
 
22
- @loaded.delete(ref)
23
- @fn_map.delete(fn)
56
+ invalidate_ref(ref)
24
57
  end
25
58
 
26
59
  private
27
60
 
61
+ # Invalidates a module by its reference, normally following a change to the
62
+ # underlying file (in order to cause reloading of the module). The module
63
+ # will be removed from the modules map, as well as modules dependending on
64
+ # it.
65
+ #
66
+ # @param ref [String] module reference
67
+ # @return [void]
68
+ def invalidate_ref(ref)
69
+ entry = @modules.delete(ref)
70
+ return if !entry
71
+
72
+ @fn_map.delete(entry[:fn])
73
+ entry[:reverse_deps].each { invalidate_ref(it) }
74
+ end
75
+
76
+ # Registers reverse dependencies for the given module reference.
77
+ #
78
+ # @param ref [String] module reference
79
+ # @param deps [Array<String>] array of dependencies for the given module
80
+ # @return [void]
81
+ def add_dependencies(ref, deps)
82
+ deps.each do
83
+ entry = @modules[it]
84
+ next if !entry
85
+
86
+ entry[:reverse_deps] << ref
87
+ end
88
+ end
89
+
90
+ # Loads a module and returns a module entry. Any dependencies (using
91
+ # `import`) are loaded as well.
92
+ #
93
+ # @param ref [String] module reference
94
+ # @return [Hash] module entry
28
95
  def load_module(ref)
29
96
  fn = File.expand_path(File.join(@root_dir, "#{ref}.rb"))
30
- @fn_map[fn] = ref
31
97
  raise Syntropy::Error, "File not found #{fn}" if !File.file?(fn)
32
98
 
99
+ @fn_map[fn] = ref
33
100
  code = IO.read(fn)
34
101
  env = @env.merge(module_loader: self, ref: ref)
35
- export_value = Syntropy::Module.load(env, code)
36
- transform_module_export_value(export_value)
102
+ m = Syntropy::Module.load(env, code, fn)
103
+ add_dependencies(ref, m.__dependencies__)
104
+ export_value = transform_module_export_value(m.__export_value__)
105
+
106
+ {
107
+ fn: fn,
108
+ export_value: export_value,
109
+ reverse_deps: []
110
+ }
37
111
  end
38
112
 
113
+ # Transforms the given export value. If the value is nil, an exception is
114
+ # raised.
115
+ #
116
+ # @param export_value [any] module's export value
117
+ # @return [any] transformed value
39
118
  def transform_module_export_value(export_value)
40
119
  case export_value
41
120
  when nil
@@ -50,14 +129,28 @@ module Syntropy
50
129
  end
51
130
  end
52
131
 
132
+ # The Syntropy::Module class implements a reloadable module. A module is a
133
+ # `.rb` source file that implements a route endpoint, a template, utility
134
+ # methods or any other functionality needed by the web app.
53
135
  class Module
54
- def self.load(env, code)
136
+ # Loads a module, returning the module instance
137
+ def self.load(env, code, fn)
55
138
  m = new(**env)
56
- m.instance_eval(code)
57
- m.__export_value__
139
+ m.instance_eval(code, fn)
140
+ env[:logger]&.info(message: "Loaded module at #{fn}")
141
+ m
142
+ rescue StandardError => e
143
+ env[:logger]&.error(
144
+ message: "Error while loading module #{fn}",
145
+ error: e
146
+ )
147
+ raise
58
148
  end
59
149
 
60
- attr_reader
150
+ # Initializes a module with the given environment hash.
151
+ #
152
+ # @param env [Hash] environment hash
153
+ # @return [void]
61
154
  def initialize(**env)
62
155
  @env = env
63
156
  @machine = env[:machine]
@@ -66,16 +159,42 @@ module Syntropy
66
159
  @ref = env[:ref]
67
160
  singleton_class.const_set(:MODULE, self)
68
161
  end
69
-
162
+
70
163
  attr_reader :__export_value__
164
+
165
+ # Exports the given value. This value will be used as the module's
166
+ # entrypoint. It can be any Ruby value, but for a route module would
167
+ # normally be a proc.
168
+ #
169
+ # @param v [any] export value
170
+ # @return [void]
71
171
  def export(v)
72
172
  @__export_value__ = v
73
173
  end
74
174
 
175
+ # Returns the list of module references imported by the module.
176
+ #
177
+ # @return [Array] array of module references
178
+ def __dependencies__
179
+ @__dependencies__ ||= []
180
+ end
181
+
182
+ # Imports the module corresponding to the given reference. The return value
183
+ # is the module's export value.
184
+ #
185
+ # @param ref [String] module reference
186
+ # @return [any] loaded dependency's export value
75
187
  def import(ref)
76
- @module_loader.load(ref)
188
+ @module_loader.load(ref).tap {
189
+ __dependencies__ << ref
190
+ }
77
191
  end
78
192
 
193
+ # Creates and returns a P2 template created with the given block.
194
+ #
195
+ # @param proc [Proc, nil] template proc or nil
196
+ # @param block [Proc] template block
197
+ # @return [P2::Template] template
79
198
  def template(proc = nil, &block)
80
199
  proc ||= block
81
200
  raise "No template block/proc given" if !proc
@@ -83,10 +202,17 @@ module Syntropy
83
202
  P2::Template.new(proc)
84
203
  end
85
204
 
205
+ # Returns a list of pages found at the given ref.
206
+ #
207
+ # @param ref [String] directory reference
208
+ # @return [Array] array of pages found in directory
86
209
  def page_list(ref)
87
210
  Syntropy.page_list(@env, ref)
88
211
  end
89
212
 
213
+ # Creates and returns a Syntropy app for the given environment.
214
+ #
215
+ # @param env [Hash] environment
90
216
  def app(**env)
91
217
  Syntropy::App.new(**(@env.merge(env)))
92
218
  end
@@ -66,32 +66,6 @@ module Syntropy
66
66
  @router_proc ||= compile_router_proc
67
67
  end
68
68
 
69
- # Returns the first error handler found for the entry. If no error handler
70
- # exists for the entry itself, the search continues up the tree through the
71
- # entry's ancestors.
72
- #
73
- # @param entry [Hash] route entry
74
- # @return [String, nil] filename of error handler, or nil if not found
75
- def route_error_handler(entry)
76
- return entry[:error] if entry[:error]
77
-
78
- entry[:parent] && route_error_handler(entry[:parent])
79
- end
80
-
81
- # Returns a list of all hooks found up the tree from the given entry. Hooks
82
- # are returned ordered from the tree root down to the given entry.
83
- #
84
- # @param entry [Hash] route entry
85
- # @return [Array<String>] list of hook entries
86
- def route_hooks(entry)
87
- hooks = []
88
- while entry
89
- hooks.unshift(entry[:hook]) if entry[:hook]
90
- entry = entry[:parent]
91
- end
92
- hooks
93
- end
94
-
95
69
  # Computes a "clean" URL path for the given path. Modules and markdown are
96
70
  # stripped of their extensions, and index file paths are also converted to the
97
71
  # containing directory path. For example, the clean URL path for `/foo/bar.rb`
@@ -233,7 +207,7 @@ module Syntropy
233
207
  case
234
208
  when (m = fn.match(/\/index(\+)?(\.(?:rb|md))$/))
235
209
  make_index_module_route(m:, parent:, path: abs_path, fn:)
236
-
210
+
237
211
  # index.html
238
212
  when fn.match(/\/index\.html$/)
239
213
  set_index_route_target(parent:, path: abs_path, kind: :static, fn:)
@@ -256,7 +230,7 @@ module Syntropy
256
230
  # (modules or markdown) files) are applied as targets to the immediate
257
231
  # containing directory. A + suffix indicates this route handles requests to
258
232
  # its subtree
259
- #
233
+ #
260
234
  # @param m [MatchData] path match data
261
235
  # @param parent [Hash] parent route entry
262
236
  # @param path [String] route path
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
-
2
+
3
3
  module Syntropy
4
4
  # Utilities for use in modules
5
5
  module Utilities
6
- # Returns a request handler that routes request according to
6
+ # Returns a request handler that routes request according to
7
7
  def route_by_host(env, map = nil)
8
8
  root = env[:root_dir]
9
9
  sites = Dir[File.join(root, '*')]
@@ -17,7 +17,7 @@ module Syntropy
17
17
  # copy over map refs
18
18
  map&.each { |k, v| sites[k] = sites[v] }
19
19
 
20
- #
20
+ #
21
21
  lambda { |req|
22
22
  site = sites[req.host]
23
23
  site ? site.call(req) : req.respond(nil, ':status' => Status::BAD_REQUEST)
@@ -36,6 +36,6 @@ module Syntropy
36
36
 
37
37
  def app(**env)
38
38
  Syntropy::App.new(**env)
39
- end
39
+ end
40
40
  end
41
41
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Syntropy
4
- VERSION = '0.13.1'
4
+ VERSION = '0.15'
5
5
  end
data/syntropy.gemspec CHANGED
@@ -26,8 +26,8 @@ Gem::Specification.new do |s|
26
26
  s.add_dependency 'p2', '2.8'
27
27
  s.add_dependency 'papercraft', '1.4'
28
28
  s.add_dependency 'qeweney', '0.22'
29
- s.add_dependency 'tp2', '0.14.1'
30
- s.add_dependency 'uringmachine', '0.16'
29
+ s.add_dependency 'tp2', '0.15'
30
+ s.add_dependency 'uringmachine', '0.18'
31
31
 
32
32
  s.add_dependency 'listen', '3.9.0'
33
33
  s.add_dependency 'logger', '1.7.0'
@@ -0,0 +1,7 @@
1
+ Foo = import '_lib/self'
2
+
3
+ def bar
4
+ Foo.foo
5
+ end
6
+
7
+ export self
data/test/test_module.rb CHANGED
@@ -44,4 +44,14 @@ class ModuleTest < Minitest::Test
44
44
  assert_equal @loader, mod.module_loader
45
45
  assert_equal 42, mod.app
46
46
  end
47
+
48
+ def test_dependency_invalidation
49
+ mod = @loader.load('_lib/dep')
50
+ assert_equal ['_lib/self', '_lib/dep'], @loader.modules.keys
51
+
52
+ self_fn = @loader.modules['_lib/self'][:fn]
53
+ @loader.invalidate_fn(self_fn)
54
+
55
+ assert_equal [], @loader.modules.keys
56
+ end
47
57
  end
@@ -288,58 +288,6 @@ class RoutingTreeTest < Minitest::Test
288
288
  assert_equal 'foo', params['id']
289
289
  end
290
290
 
291
- def test_route_error_handler
292
- e = @rt.dynamic_map['/docs/[org]']
293
- target = @rt.route_error_handler(e)
294
- assert_kind_of Hash, target
295
- assert_equal :module, target[:kind]
296
- assert_equal File.join(@rt.root_dir, '_error.rb'), target[:fn]
297
-
298
- e = @rt.dynamic_map['/docs/api+']
299
- target = @rt.route_error_handler(e)
300
- assert_equal :module, target[:kind]
301
- assert_equal File.join(@rt.root_dir, '_error.rb'), target[:fn]
302
-
303
- e = @rt.dynamic_map['/docs/[org]/[repo]']
304
- target = @rt.route_error_handler(e)
305
- assert_equal :module, target[:kind]
306
- assert_equal File.join(@rt.root_dir, '[org]/[repo]/_error.rb'), target[:fn]
307
- end
308
-
309
- def test_route_hooks
310
- e = @rt.dynamic_map['/docs/[org]']
311
- hooks = @rt.route_hooks(e)
312
- assert_equal [
313
- { kind: :module, fn: File.join(@rt.root_dir, '_hook.rb') }
314
- ], hooks
315
-
316
- e = @rt.dynamic_map['/docs/api+']
317
- hooks = @rt.route_hooks(e)
318
- assert_equal [
319
- { kind: :module, fn: File.join(@rt.root_dir, '_hook.rb') }
320
- ], hooks
321
-
322
- e = @rt.dynamic_map['/docs/[org]/[repo]']
323
- hooks = @rt.route_hooks(e)
324
- assert_equal [
325
- { kind: :module, fn: File.join(@rt.root_dir, '_hook.rb') }
326
- ], hooks
327
-
328
- e = @rt.dynamic_map['/docs/[org]/[repo]/issues']
329
- hooks = @rt.route_hooks(e)
330
- assert_equal [
331
- { kind: :module, fn: File.join(@rt.root_dir, '_hook.rb') },
332
- { kind: :module, fn: File.join(@rt.root_dir, '[org]/[repo]/issues/_hook.rb') }
333
- ], hooks
334
-
335
- e = @rt.dynamic_map['/docs/[org]/[repo]/issues/[id]']
336
- hooks = @rt.route_hooks(e)
337
- assert_equal [
338
- { kind: :module, fn: File.join(@rt.root_dir, '_hook.rb') },
339
- { kind: :module, fn: File.join(@rt.root_dir, '[org]/[repo]/issues/_hook.rb') }
340
- ], hooks
341
- end
342
-
343
291
  def test_routing_root_mounted
344
292
  rt = Syntropy::RoutingTree.new(root_dir: File.join(@root_dir, 'site'), mount_path: '/')
345
293
  router = rt.router_proc
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.13.1
4
+ version: '0.15'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
@@ -85,28 +85,28 @@ dependencies:
85
85
  requirements:
86
86
  - - '='
87
87
  - !ruby/object:Gem::Version
88
- version: 0.14.1
88
+ version: '0.15'
89
89
  type: :runtime
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - '='
94
94
  - !ruby/object:Gem::Version
95
- version: 0.14.1
95
+ version: '0.15'
96
96
  - !ruby/object:Gem::Dependency
97
97
  name: uringmachine
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - '='
101
101
  - !ruby/object:Gem::Version
102
- version: '0.16'
102
+ version: '0.18'
103
103
  type: :runtime
104
104
  prerelease: false
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - '='
108
108
  - !ruby/object:Gem::Version
109
- version: '0.16'
109
+ version: '0.18'
110
110
  - !ruby/object:Gem::Dependency
111
111
  name: listen
112
112
  requirement: !ruby/object:Gem::Requirement
@@ -221,6 +221,7 @@ files:
221
221
  - test/app/_hook.rb
222
222
  - test/app/_layout/default.rb
223
223
  - test/app/_lib/callable.rb
224
+ - test/app/_lib/dep.rb
224
225
  - test/app/_lib/env.rb
225
226
  - test/app/_lib/klass.rb
226
227
  - test/app/_lib/missing-export.rb