syntropy 0.2 → 0.3

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: a9a2e580917d4b05e8470695a75ab399326c6b8f7ee3b576526181c36791ee81
4
- data.tar.gz: eb95e089e426bdb618981694cf8240911f30821a90d5ae9ef8f4bf4d79670a44
3
+ metadata.gz: 43d7686b3b9f5f09c819bebb9e46fdceb45c8c0a66441b1e12b130fa376defc5
4
+ data.tar.gz: f616e5dcdedc8a08fb23d5a52c1a92296cd348f8555fede1ac31ba0859c1be28
5
5
  SHA512:
6
- metadata.gz: a85e2502bd313a10b5533ec5b8f124cfa72a3c4494f7ddcd912e0939973e2f72df458e6891543c5cbcf106718254561523c7f561ee1fd23bf348cab701696d4a
7
- data.tar.gz: 60391470703441eccb1bdba52edfba754225171bcf65a08d146d01742e1ab8f7b8f6215cfc093ed4aee4b6e99c0f967cb9a6bf1f19274f6f4d34f8ae95a6e2c4
6
+ metadata.gz: 1f6d14df3cbbaa74d9ec897cb50fd35caed0f48b6410f2abb13fad79947b50bbf33c32aa6358ab6464ac5b90e861fffacb345805d487cd67f5bd77b36cd7295b
7
+ data.tar.gz: af07c19a78d94fe37403246b302f95f047aeb12c764e8914e43f5ed1e1b1245cc0678a01e964e895fdc7aef0e775194c6ee9744939113352db07eca166693cc1
data/.rubocop.yml CHANGED
@@ -35,8 +35,8 @@ AllCops:
35
35
  # Style/MultilineBlockChain:
36
36
  # Enabled: false
37
37
 
38
- # Lint/RescueException:
39
- # Enabled: false
38
+ Lint/RescueException:
39
+ Enabled: false
40
40
 
41
41
  # Lint/InheritException:
42
42
  # Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.3 2025-06-25
2
+
3
+ - Implement module reloading on file change
4
+
1
5
  ## 0.2 2025-06-24
2
6
 
3
7
  - Add CLI tool
data/TODO.md CHANGED
@@ -1,97 +0,0 @@
1
- ## Server tool
2
-
3
- ```bash
4
- $ bundle exec syntropy --dev ./site
5
- $ bundle exec syntropy --workers 4 ./site
6
- ```
7
-
8
- And also a config file:
9
-
10
- ```bash
11
- $ bundle exec syntropy site.rb
12
- ```
13
-
14
- And the config file:
15
-
16
- ```ruby
17
- # site.rb
18
- Syntropy.config do
19
- root './site'
20
- workers 4
21
- log { |req| }
22
- end
23
- ```
24
-
25
- ## Lightweight model API on top of Extralite
26
-
27
- - DB connection pool
28
- - Lightweight means 90% features with 10% effort:
29
-
30
- ```ruby
31
- Posts = Syntropy::Relation.new('posts')
32
-
33
- posts = Posts.order_by(:stamp, :desc).all(db)
34
-
35
- post = Posts.where(id: 13).first(db)
36
-
37
- id = Posts.insert(db, title: 'foo', body: 'bar')
38
-
39
- Posts.where(id: id).update(db, body: 'baz')
40
-
41
- Posts.where(id: id).delete(db)
42
- ```
43
-
44
- The whole `db` argument thing is very limiting. For easier usage we integrate
45
- the db connection pool as dependency injection the model:
46
-
47
- ```ruby
48
- db_pool = E2::ConnectionPool.new(fn)
49
- Posts = Syntropy::Dataset.new(db_pool, 'posts')
50
-
51
- Posts[id: 1] #=> { id: 1, title: 'foo', body: 'bar' }
52
- Posts.find(id: 1) #=>
53
-
54
- Posts.to_a #=> [...]
55
- Posts.order_by(:stamp, :desc).to_a #=> [...]
56
-
57
- id = Posts.insert(title: 'foo', body: 'bar')
58
-
59
- post = Posts.where(id: id)
60
- post.values #=> { id: 1, title: 'foo', body: 'bar' }
61
- post.update(body: 'baz') #=> 1
62
- post.delete
63
- ```
64
-
65
- So basically it's a bit similar to Sequel datasets, but there's no "object instance as single row". The instance is the entire set of rows in the table, or a subset thereof:
66
-
67
- ```ruby
68
- Posts.where(...).order_by(...).select(...).from(rowset)
69
- ```
70
-
71
- How about CTEs?
72
-
73
- ```ruby
74
- Users = Syntrop::Dataset.new(db_pool, 'users')
75
-
76
- GroupIdRowset = Syntropy::Dataset {
77
- with(
78
- foo: Users,
79
- bar: -> {
80
- select user_id, group
81
- from foo
82
- },
83
- baz: -> {
84
- select id
85
- from bar
86
- where user_id == bar.select(:user_id)
87
- }
88
- )
89
-
90
- select_all
91
- from baz
92
- where id == :group_id
93
- }
94
-
95
- users = GroupIdRowset.bind(group_id: 5).to_a
96
-
97
- ```
data/bin/syntropy CHANGED
@@ -56,6 +56,10 @@ if !File.directory?(opts[:location])
56
56
  exit
57
57
  end
58
58
 
59
+
60
+
59
61
  opts[:machine] = UM.new
60
- app = Syntropy::App.new(opts[:machine], opts[:location], '/', watch_files: 0.05)
61
- TP2.run(opts) { app.(it) }
62
+ opts[:logger] = opts[:logger] && TP2::Logger.new(opts[:machine], **opts)
63
+
64
+ app = Syntropy::App.new(opts[:machine], opts[:location], '/', opts)
65
+ TP2.run(opts) { app.call(it) }
data/lib/syntropy/app.rb CHANGED
@@ -12,22 +12,20 @@ module Syntropy
12
12
  class App
13
13
  attr_reader :route_cache
14
14
 
15
- def initialize(machine, src_path, mount_path, env = {})
15
+ def initialize(machine, src_path, mount_path, opts = {})
16
16
  @machine = machine
17
- @src_path = src_path
17
+ @src_path = File.expand_path(src_path)
18
18
  @mount_path = mount_path
19
19
  @route_cache = {}
20
- @env = env
20
+ @opts = opts
21
21
 
22
22
  @relative_path_re = calculate_relative_path_re(mount_path)
23
- if (wf = env[:watch_files])
24
- period = wf.is_a?(Numeric) ? wf : 0.1
25
- machine.spin do
26
- Syntropy.file_watch(@machine, src_path, period: period) { invalidate_cache(it) }
27
- rescue Exception => e
28
- p e
29
- p e.backtrace
30
- end
23
+ @machine.spin do
24
+ # we do startup stuff asynchronously, in order to first let TP2 do its
25
+ # setup tasks
26
+ @machine.sleep 0.25
27
+ @opts[:logger]&.call("Serving from #{File.expand_path(@src_path)}")
28
+ start_file_watcher if opts[:watch_files]
31
29
  end
32
30
  end
33
31
 
@@ -42,15 +40,6 @@ module Syntropy
42
40
  entry
43
41
  end
44
42
 
45
- def invalidate_cache(fn)
46
- invalidated_keys = []
47
- @route_cache.each do |k, v|
48
- invalidated_keys << k if v[:fn] == fn
49
- end
50
-
51
- invalidated_keys.each { @route_cache.delete(it) }
52
- end
53
-
54
43
  def call(req)
55
44
  entry = find_route(req.path)
56
45
  render_entry(req, entry)
@@ -62,6 +51,32 @@ module Syntropy
62
51
 
63
52
  private
64
53
 
54
+ def start_file_watcher
55
+ @opts[:logger]&.call('Watching for module file changes...', nil)
56
+ wf = @opts[:watch_files]
57
+ period = wf.is_a?(Numeric) ? wf : 0.1
58
+ @machine.spin do
59
+ Syntropy.file_watch(@machine, @src_path, period: period) do
60
+ @opts[:logger]&.call("Detected changed file: #{it}")
61
+ invalidate_cache(it)
62
+ rescue Exception => e
63
+ p e
64
+ p e.backtrace
65
+ exit!
66
+ end
67
+ end
68
+ end
69
+
70
+ def invalidate_cache(fn)
71
+ invalidated_keys = []
72
+ @route_cache.each do |k, v|
73
+ @opts[:logger]&.call("Invalidate cache for #{k}", nil)
74
+ invalidated_keys << k if v[:fn] == fn
75
+ end
76
+
77
+ invalidated_keys.each { @route_cache.delete(it) }
78
+ end
79
+
65
80
  def calculate_relative_path_re(mount_path)
66
81
  mount_path = '' if mount_path == '/'
67
82
  /^#{mount_path}(?:\/(.*))?$/
@@ -95,7 +110,7 @@ module Syntropy
95
110
  end
96
111
 
97
112
  def file_entry(fn)
98
- { fn: fn, kind: FILE_KINDS[File.extname(fn)] || :static }
113
+ { fn: File.expand_path(fn), kind: FILE_KINDS[File.extname(fn)] || :static }
99
114
  end
100
115
 
101
116
  def find_index_entry(dir)
@@ -138,19 +153,23 @@ module Syntropy
138
153
  when :not_found
139
154
  req.respond('Not found', ':status' => Qeweney::Status::NOT_FOUND)
140
155
  when :static
141
- entry[:mime_type] ||= Qeweney::MimeTypes[File.extname(entry[:fn])]
142
- req.respond(IO.read(entry[:fn]), 'Content-Type' => entry[:mime_type])
156
+ respond_static(req, entry)
143
157
  when :markdown
144
158
  body = render_markdown(IO.read(entry[:fn]))
145
159
  req.respond(body, 'Content-Type' => 'text/html')
146
160
  when :module
147
- call_module(entry, req)
161
+ call_module(req, entry)
148
162
  else
149
- raise "Invalid entry kind"
163
+ raise 'Invalid entry kind'
150
164
  end
151
165
  end
152
166
 
153
- def call_module(entry, req)
167
+ def respond_static(req, entry)
168
+ entry[:mime_type] ||= Qeweney::MimeTypes[File.extname(entry[:fn])]
169
+ req.respond(IO.read(entry[:fn]), 'Content-Type' => entry[:mime_type])
170
+ end
171
+
172
+ def call_module(req, entry)
154
173
  entry[:code] ||= load_module(entry)
155
174
  if entry[:code] == :invalid
156
175
  req.respond(nil, ':status' => Qeweney::Status::INTERNAL_SERVER_ERROR)
@@ -165,17 +184,13 @@ module Syntropy
165
184
  end
166
185
 
167
186
  def load_module(entry)
168
- loader = Syntropy::ModuleLoader.new(@src_path, @env)
187
+ loader = Syntropy::ModuleLoader.new(@src_path, @opts)
169
188
  ref = entry[:fn].gsub(%r{^#{@src_path}\/}, '').gsub(/\.rb$/, '')
170
189
  o = loader.load(ref)
171
190
  # klass = Class.new
172
191
  # o = klass.instance_eval(body, entry[:fn], 1)
173
192
 
174
- if o.is_a?(Papercraft::HTML)
175
- return wrap_template(o)
176
- else
177
- return o
178
- end
193
+ o.is_a?(Papercraft::HTML) ? wrap_template(o) : o
179
194
  end
180
195
 
181
196
  def wrap_template(templ)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Syntropy
4
- VERSION = '0.2'
4
+ VERSION = '0.3'
5
5
  end
data/syntropy.gemspec CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |s|
25
25
  s.add_dependency 'json', '2.12.2'
26
26
  s.add_dependency 'papercraft', '1.4'
27
27
  s.add_dependency 'qeweney', '0.21'
28
- s.add_dependency 'tp2', '0.12.2'
28
+ s.add_dependency 'tp2', '0.12.3.1'
29
29
  s.add_dependency 'uringmachine', '0.15'
30
30
 
31
31
  s.add_dependency 'listen', '3.9.0'
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.2'
4
+ version: '0.3'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
@@ -71,14 +71,14 @@ dependencies:
71
71
  requirements:
72
72
  - - '='
73
73
  - !ruby/object:Gem::Version
74
- version: 0.12.2
74
+ version: 0.12.3.1
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - '='
80
80
  - !ruby/object:Gem::Version
81
- version: 0.12.2
81
+ version: 0.12.3.1
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: uringmachine
84
84
  requirement: !ruby/object:Gem::Requirement