syntropy 0.3 → 0.4

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -2
  3. data/CHANGELOG.md +8 -0
  4. data/README.md +30 -11
  5. data/TODO.md +89 -0
  6. data/bin/syntropy +2 -2
  7. data/cmd/setup/template/site/.gitignore +57 -0
  8. data/cmd/setup/template/site/Dockerfile +32 -0
  9. data/cmd/setup/template/site/Gemfile +3 -0
  10. data/cmd/setup/template/site/README.md +0 -0
  11. data/cmd/setup/template/site/bin/console +0 -0
  12. data/cmd/setup/template/site/bin/restart +0 -0
  13. data/cmd/setup/template/site/bin/server +0 -0
  14. data/cmd/setup/template/site/bin/start +0 -0
  15. data/cmd/setup/template/site/bin/stop +0 -0
  16. data/cmd/setup/template/site/docker-compose.yml +51 -0
  17. data/cmd/setup/template/site/proxy/Dockerfile +5 -0
  18. data/cmd/setup/template/site/proxy/etc/Caddyfile +7 -0
  19. data/cmd/setup/template/site/proxy/etc/tls_auto +2 -0
  20. data/cmd/setup/template/site/proxy/etc/tls_cloudflare +4 -0
  21. data/cmd/setup/template/site/proxy/etc/tls_custom +1 -0
  22. data/cmd/setup/template/site/proxy/etc/tls_selfsigned +1 -0
  23. data/cmd/setup/template/site/site/_layout/default.rb +11 -0
  24. data/cmd/setup/template/site/site/about.md +6 -0
  25. data/cmd/setup/template/site/site/articles/cage.rb +29 -0
  26. data/cmd/setup/template/site/site/articles/index.rb +3 -0
  27. data/cmd/setup/template/site/site/assets/css/style.css +40 -0
  28. data/cmd/setup/template/site/site/assets/img/syntropy.png +0 -0
  29. data/cmd/setup/template/site/site/index.rb +15 -0
  30. data/docker-compose.yml +51 -0
  31. data/lib/syntropy/app.rb +93 -24
  32. data/lib/syntropy/errors.rb +16 -2
  33. data/lib/syntropy/module.rb +26 -5
  34. data/lib/syntropy/request_extensions.rb +96 -0
  35. data/lib/syntropy/rpc_api.rb +26 -9
  36. data/lib/syntropy/side_run.rb +46 -0
  37. data/lib/syntropy/version.rb +1 -1
  38. data/lib/syntropy.rb +14 -49
  39. data/syntropy.gemspec +1 -1
  40. data/test/app/baz.rb +3 -0
  41. data/test/test_app.rb +57 -7
  42. data/test/test_side_run.rb +43 -0
  43. data/test/test_validation.rb +1 -1
  44. metadata +31 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43d7686b3b9f5f09c819bebb9e46fdceb45c8c0a66441b1e12b130fa376defc5
4
- data.tar.gz: f616e5dcdedc8a08fb23d5a52c1a92296cd348f8555fede1ac31ba0859c1be28
3
+ metadata.gz: 50c0c34b9bdad39e7116f404c5d4efa7a8a79aa5911e95432efc62c8c90e6592
4
+ data.tar.gz: 72e0cb6947b187628e86be0af3f8fd5440c79f5c862b614e7074b45ae4dabf60
5
5
  SHA512:
6
- metadata.gz: 1f6d14df3cbbaa74d9ec897cb50fd35caed0f48b6410f2abb13fad79947b50bbf33c32aa6358ab6464ac5b90e861fffacb345805d487cd67f5bd77b36cd7295b
7
- data.tar.gz: af07c19a78d94fe37403246b302f95f047aeb12c764e8914e43f5ed1e1b1245cc0678a01e964e895fdc7aef0e775194c6ee9744939113352db07eca166693cc1
6
+ metadata.gz: f83fa7756c09e12eb87bea546142881d0dba682f214fcaa8ea78b865756232952d8d5a87c308cea0a4d39c4f608a101983c5a11807e99754b3efa19aadd61cad
7
+ data.tar.gz: 244d7a878c2e83e2a7ebfc5c0e4a7cd30b429497c7456211a80306ec2b2c3996050111c11dd71e54e9ca63b81e7d14e9475d528127b7d8538255b73e75ad50de
data/.rubocop.yml CHANGED
@@ -7,8 +7,16 @@ AllCops:
7
7
  - "test/**/*.rb"
8
8
  - "examples/**/*.rb"
9
9
  - "Gemfile*"
10
- # Style/LambdaCall:
11
- # Enabled: false
10
+ Style/LambdaCall:
11
+ Enabled: false
12
+
13
+ Style/BlockDelimiters:
14
+ Enabled: false
15
+
16
+ Style/Semicolon:
17
+ Exclude:
18
+ - cmd/setup/template/site/site/articles/cage.rb
19
+
12
20
  # Style/ModuleFunction:
13
21
  # Enabled: false
14
22
  # Style/RegexpLiteral:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.4 2025-07-03
2
+
3
+ - Improve errors API
4
+ - Add HTTP method validation
5
+ - Refactor Qeweney::Request extensions
6
+ - Add side_run API for running tasks on a side thread
7
+ - Add support for rendering markdown with layout
8
+
1
9
  ## 0.3 2025-06-25
2
10
 
3
11
  - Implement module reloading on file change
data/README.md CHANGED
@@ -22,22 +22,42 @@
22
22
  | Syntropy: A tendency towards complexity, structure, order, organization of
23
23
  ever more advantageous and orderly patterns.
24
24
 
25
- Syntropy is a WIP web framework for building multi-page and single-page apps.
25
+ Syntropy is a web framework for building multi-page and single-page apps.
26
26
  Syntropy uses file tree-based routing, and provides controllers for a number of
27
27
  common patterns, such as a SPA with client-side rendering, a standard
28
28
  server-rendered MPA, a REST API etc.
29
29
 
30
+ Syntropy also provides tools for working with lists of items represented as
31
+ files (ala Jekyll and other static site generators), allowing you to build
32
+ read-only apps (such as a markdown blog) without using a database.
33
+
34
+ For interactive apps, Syntropy provides basic tools for working with SQLite
35
+ databases in a concurrent environment.
36
+
37
+ Syntropy is based on:
38
+
39
+ - [UringMachine](https://github.com/digital-fabric/uringmachine) - a lean mean
40
+ [io_uring](https://unixism.net/loti/what_is_io_uring.html) machine for Ruby.
41
+ - [TP2](https://github.com/noteflakes/tp2) - an io_uring-based web server for
42
+ concurrent Ruby apps.
43
+ - [Qeweney](https://github.com/digital-fabric/qeweney) a uniform interface for
44
+ working with HTTP requests and responses.
45
+ - [Papercraft](https://github.com/digital-fabric/papercraft) HTML templating
46
+ with plain Ruby.
47
+ - [Extralite](https://github.com/digital-fabric/extralite) a fast and innovative
48
+ SQLite wrapper for Ruby.
49
+
30
50
  ## Routing
31
51
 
32
- Routing is performed automatically by following the tree structure of the
33
- Syntropy app. A simple example:
52
+ Syntropy routes request by following the tree structure of the Syntropy app. A
53
+ simple example:
34
54
 
35
55
  ```
36
56
  site/
37
57
  ├ _layout/
38
58
  | └ default.rb
39
59
  ├ _articles/
40
- | └ 2025-06-01-hello_world.md
60
+ | └ 2025-01-01-hello_world.md
41
61
  ├ api/
42
62
  | └ v1.rb
43
63
  ├ assets/
@@ -50,9 +70,8 @@ site/
50
70
  └ robots.txt
51
71
  ```
52
72
 
53
- The routing follows the file hierarchy, and Syntropy knows how to serve static
54
- asset files (CSS, JS, images...) as well as render markdown files and run custom
55
- Ruby code.
73
+ Syntropy knows how to serve static asset files (CSS, JS, images...) as well as
74
+ render markdown files and run modules written in Ruby.
56
75
 
57
76
  ## What does a Syntropic Ruby module look like?
58
77
 
@@ -67,7 +86,7 @@ def articles
67
86
  Syntropy.stamped_file_entries('/_articles')
68
87
  end
69
88
 
70
- @@layout.apply(title: 'archive') {
89
+ export @@layout.apply(title: 'archive') {
71
90
  div {
72
91
  ul {
73
92
  articles.each { |article|
@@ -98,8 +117,8 @@ class APIV1 < Syntropy::RPCAPI
98
117
  end
99
118
  end
100
119
 
101
- APIV1.new(Syntropy.env.open_db)
120
+ export APIV1
102
121
  ```
103
122
 
104
- Basically, the return value of the module is a template or a resource that
105
- responds to the request.
123
+ Basically, the exported value can be a template, a callable or a class that
124
+ responds to the request.
data/TODO.md CHANGED
@@ -0,0 +1,89 @@
1
+ - Middleware
2
+
3
+ ```Ruby
4
+ # site/_middleware.rb or site/_hook.rb
5
+ export ->(req, &app) do
6
+ app.call(req)
7
+ rescue Syntropy::Error => e
8
+ render_error_page(req, e.http_status)
9
+ end
10
+
11
+ # an alternative, at least for errors is a _error.rb file:
12
+ # site/_error.rb
13
+ # Just a normal callable:
14
+ #
15
+ export ->(req, err) do
16
+ render_error_page(req, err.http_status)
17
+ end
18
+ ```
19
+
20
+
21
+ - CLI tool for setting up a site repo:
22
+
23
+ ```bash
24
+ # clone a newly created repo
25
+ ~/repo$ git clone https://github.com/foo/bar
26
+ ...
27
+ ~/repo$ syntropy setup bar
28
+
29
+ (syntropy banner)
30
+
31
+ Setting up Syntropy project in /home/sharon/repo/bar:
32
+
33
+ bar/
34
+ bin/
35
+ start
36
+ stop
37
+ restart
38
+ console
39
+ server
40
+ docker-compose.yml
41
+ Dockerfile
42
+ Gemfile
43
+ proxy/
44
+
45
+ README.md
46
+ site/
47
+ _layout/
48
+ default.rb
49
+ _lib/
50
+ about.md
51
+ articles/
52
+ long-form.md
53
+ assets/
54
+ js/
55
+ css/
56
+ style.css
57
+ img/
58
+ syntropy.png
59
+ index.rb
60
+ ```
61
+
62
+ - Some of the files might need templating, but we can maybe do without, or at
63
+ least make it as generic as possible.
64
+
65
+ - `syntropy setup` steps:
66
+
67
+ 1. Verify existence of target directory
68
+ 2. Copy files from Syntropy template to target directory
69
+ 3. Do chmod +x for bin/*
70
+ 4. Do bundle install in the target directory
71
+ 5. Show some information with regard to how to get started working with the
72
+ repo
73
+
74
+ - `syntropy provision` steps:
75
+
76
+ 1. Verify Ubuntu 22.x or higher
77
+ 2. Install git, docker, docker-compose
78
+
79
+ - `syntropy deploy` steps:
80
+
81
+ 1. Verify no uncommitted changes.
82
+ 2. SSH to remote machine.
83
+ 2.1. If not exists, clone repo
84
+ 2.2. Otherwise, verify remote machine repo is on same branch as local repo
85
+ 2.3. Do a git pull (what about credentials?)
86
+ 2.4. If gem bundle has changed, do a docker compose build
87
+ 2.5. If docker compose services are running, restart
88
+ 2.6. Otherwise, start services
89
+ 2.7. Verify service is running correctly
data/bin/syntropy CHANGED
@@ -57,8 +57,8 @@ if !File.directory?(opts[:location])
57
57
  end
58
58
 
59
59
 
60
-
61
- opts[:machine] = UM.new
60
+ # We set Syntropy.machine so we can reference it from anywhere
61
+ opts[:machine] = Syntropy.machine = UM.new
62
62
  opts[:logger] = opts[:logger] && TP2::Logger.new(opts[:machine], **opts)
63
63
 
64
64
  app = Syntropy::App.new(opts[:machine], opts[:location], '/', opts)
@@ -0,0 +1,57 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
48
+ # Gemfile.lock
49
+ # .ruby-version
50
+ # .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
+ # .rubocop-https?--*
57
+ log
@@ -0,0 +1,32 @@
1
+ ARG RUBY_BASE_IMAGE=ruby:3.4.1-alpine
2
+ ARG GEM_CACHE_IMAGE=${RUBY_BASE_IMAGE}
3
+
4
+ # base image
5
+ FROM ${RUBY_BASE_IMAGE} AS base
6
+ RUN apk add --update sqlite-dev openssl-dev tzdata bash curl zip git
7
+ RUN apk add --update build-base
8
+ RUN gem install bundler:2.6.9
9
+
10
+ # gem cache
11
+ FROM ${GEM_CACHE_IMAGE} AS gem-cache
12
+ RUN mkdir -p /usr/local/bundle
13
+
14
+ FROM base AS gems
15
+ COPY --from=gem-cache /usr/local/bundle /usr/local/bundle
16
+ COPY Gemfile Gemfile.lock ./
17
+ RUN bundle install --jobs=4 --retry=5
18
+
19
+ # Final backend image
20
+ FROM base AS deploy
21
+
22
+ RUN adduser -D app
23
+ RUN chown app:app /home/app
24
+ WORKDIR /home/app
25
+ USER app
26
+
27
+ RUN mkdir -p /tmp
28
+ COPY --from=gems --chown=app:app /usr/local/bundle /usr/local/bundle
29
+
30
+ EXPOSE 1234
31
+
32
+ CMD ["bundle", "exec", "tp2", "."]
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'syntropy', '0.3'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,51 @@
1
+ services:
2
+ backend:
3
+ build: .
4
+ privileged: true
5
+ restart: always
6
+ ports:
7
+ - 127.0.0.1:1234:1234
8
+ # expose:
9
+ # - 1234
10
+ volumes:
11
+ - .:/home/app
12
+ deploy:
13
+ # replicas: 1
14
+ resources:
15
+ limits:
16
+ memory: 500M
17
+ # restart: unless-stopped
18
+ logging:
19
+ driver: "json-file"
20
+ options:
21
+ max-size: "1M"
22
+ max-file: "10"
23
+
24
+ # healthcheck:
25
+ # test: "curl 'http://localhost:1234/?q=ping'"
26
+ # interval: "30s"
27
+ # timeout: "3s"
28
+ # start_period: "5s"
29
+ # retries: 3
30
+
31
+ proxy:
32
+ depends_on:
33
+ - backend
34
+ build:
35
+ context: ./proxy
36
+ dockerfile: Dockerfile
37
+ restart: always
38
+ volumes:
39
+ - ./proxy/etc/Caddyfile:/etc/caddy/Caddyfile
40
+ ports:
41
+ - "80:80"
42
+ - "443:443"
43
+ - "443:443/udp"
44
+ # env_file:
45
+ # - ./conf/caddy.env
46
+ # - ./conf/caddy_sensitive.env
47
+ logging:
48
+ driver: "json-file"
49
+ options:
50
+ max-size: "1M"
51
+ max-file: "10"
@@ -0,0 +1,5 @@
1
+ FROM caddy:2.10.0-builder AS builder
2
+ RUN xcaddy build
3
+
4
+ FROM caddy:2.10.0
5
+ COPY --from=builder /usr/bin/caddy /usr/bin/caddy
@@ -0,0 +1,7 @@
1
+ localhost {
2
+ reverse_proxy noteflakescom-backend-1:1234
3
+ }
4
+
5
+ my-awesome-domain.com {
6
+ reverse_proxy noteflakescom-backend-1:1234
7
+ }
@@ -0,0 +1,2 @@
1
+ tls {$TLS_AUTO_EMAIL}
2
+
@@ -0,0 +1,4 @@
1
+ tls {
2
+ dns cloudflare {env.CLOUDFLARE_API_TOKEN}
3
+ }
4
+
@@ -0,0 +1 @@
1
+ tls {$TLS_CUSTOM_CERT} {$TLS_CUSTOM_KEY}
@@ -0,0 +1 @@
1
+ tls internal
@@ -0,0 +1,11 @@
1
+ export templ { |*a, **b|
2
+ html {
3
+ head {
4
+ title 'My awesome Syntropy website'
5
+ link rel: 'stylesheet', type: 'text/css', href: '/assets/css/style.css'
6
+ }
7
+ body {
8
+ emit_yield(*a, **b)
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+ # About my site
5
+
6
+ Lorem ipsum my awesome site.
@@ -0,0 +1,29 @@
1
+ layout = import('_layout/default')
2
+
3
+ poem = [
4
+ " in ten\xA0", 'M', 'inutes',
5
+ ' ', 'C', 'ome back: you will',
6
+ 'have taught me chi', 'N', 'ese',
7
+ ' (s', 'A', 'tie).',
8
+ ' shall I ret', 'U', 'rn the favor?',
9
+ ' ', 'G', 'ive you',
10
+ ' ot', 'H', 'er lessons',
11
+ ' (', 'T', 'ing!)?',
12
+ ' ', 'O', 'r would you prefer',
13
+ ' sile', 'N', 'ce?',
14
+ ]
15
+
16
+ export layout.apply {
17
+ article(class: 'mesostic') {
18
+ h2 'For William McN. who studied with Ezra Pound'
19
+
20
+ content {
21
+ span(_for: poem) { text it }
22
+ }
23
+
24
+ author {
25
+ span "-\xA0"
26
+ a 'John cage', href: 'https://en.wikipedia.org/wiki/John_Cage'
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,3 @@
1
+ export templ {
2
+ h1 'Articles'
3
+ }
@@ -0,0 +1,40 @@
1
+ body {
2
+ font-family: sans-serif;
3
+ }
4
+
5
+ article.mesostic {
6
+ width: 600px;
7
+ margin: 2em auto;
8
+ font-size: 1.4em;
9
+
10
+ * {
11
+ font-family: monospace;
12
+ }
13
+
14
+ h2 {
15
+ text-align: center;
16
+ }
17
+
18
+ content {
19
+ display: grid;
20
+ margin: 2em 0;
21
+ grid-template-columns: 1fr auto 1fr;
22
+
23
+ span {
24
+ display: inline-block;
25
+ }
26
+ span:nth-child(3n + 1) {
27
+ text-align: right;
28
+ }
29
+
30
+ span:nth-child(3n + 2) {
31
+ text-align: center;
32
+ font-weight: bold;
33
+ }
34
+ }
35
+
36
+ author {
37
+ display: block;
38
+ text-align: right;
39
+ }
40
+ }
@@ -0,0 +1,15 @@
1
+ layout = import('_layout/default')
2
+
3
+ export layout.apply {
4
+ h1 'Hello from Syntropy'
5
+ p {
6
+ span "Here's an "
7
+ a 'about', href: 'about'
8
+ span ' page.'
9
+ }
10
+ p {
11
+ span "Here's an "
12
+ a 'article', href: 'articles/cage'
13
+ span ' page.'
14
+ }
15
+ }
@@ -0,0 +1,51 @@
1
+ services:
2
+ backend:
3
+ build: .
4
+ privileged: true
5
+ restart: always
6
+ ports:
7
+ - 127.0.0.1:1234:1234
8
+ # expose:
9
+ # - 1234
10
+ volumes:
11
+ - .:/home/app
12
+ deploy:
13
+ # replicas: 1
14
+ resources:
15
+ limits:
16
+ memory: 500M
17
+ # restart: unless-stopped
18
+ logging:
19
+ driver: "json-file"
20
+ options:
21
+ max-size: "1M"
22
+ max-file: "10"
23
+
24
+ # healthcheck:
25
+ # test: "curl 'http://localhost:1234/?q=ping'"
26
+ # interval: "30s"
27
+ # timeout: "3s"
28
+ # start_period: "5s"
29
+ # retries: 3
30
+
31
+ proxy:
32
+ depends_on:
33
+ - backend
34
+ build:
35
+ context: ./proxy
36
+ dockerfile: Dockerfile
37
+ restart: always
38
+ volumes:
39
+ - ./proxy/etc/Caddyfile:/etc/caddy/Caddyfile
40
+ ports:
41
+ - "80:80"
42
+ - "443:443"
43
+ - "443:443/udp"
44
+ # env_file:
45
+ # - ./conf/caddy.env
46
+ # - ./conf/caddy_sensitive.env
47
+ logging:
48
+ driver: "json-file"
49
+ options:
50
+ max-size: "1M"
51
+ max-file: "10"