cybertrain 0.1.1 → 0.2.0
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/README.md +167 -63
- data/cybertrain/cli/build.rb +22 -4
- data/cybertrain/cli/new_app.rb +17 -12
- data/cybertrain/cli/scaffold.rb +7 -0
- data/cybertrain/cli/templates.rb +7 -7
- data/cybertrain/cli/toolchain.rb +787 -0
- data/cybertrain/cli.rb +106 -25
- data/cybertrain/generator/inflector.rb +151 -86
- data/cybertrain/version.rb +5 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2bfb12d19791bff7ce88504f9ffb5fe6e2d90ae7a55aa24f5571b1909cf11976
|
|
4
|
+
data.tar.gz: 0eaac7a028bbf9be6c013b716ecec76f02c91fb10aa04e855f7b777f250c8712
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 66bf793c97cf16d109519514b10c15d929e627137e58a4663d04ad3e88a746fa12e95b39358f15ebe8ba643107a2f8f30dbee54cbc9e95ea5127a4cc870fb417
|
|
7
|
+
data.tar.gz: 02bcced8e7ab059cb479b06835420eb241271b4447ed0e7d16c2801c6ed50b6b0d3587ff6557ae9b36501a5a108b05c2a4e7d5757caf7887c353ed41e4c2deb7
|
data/README.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="site/assets/brand/lockup-stacked-dark.svg">
|
|
4
|
+
<img alt="CyberTrain" src="site/assets/brand/lockup-stacked-light.svg" width="360">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://saeki-mototsune.github.io/CyberTrain/">Homepage</a> ·
|
|
9
|
+
<a href="https://saeki-mototsune.github.io/CyberTrain/tutorial.html">Tutorial</a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
1
12
|
# cybertrain
|
|
2
13
|
|
|
3
14
|
cybertrain is a Rails-shaped web application framework written natively for
|
|
@@ -25,28 +36,40 @@ Rails" below.
|
|
|
25
36
|
|
|
26
37
|
## Requirements
|
|
27
38
|
|
|
28
|
-
- Spinel `2026.09.12
|
|
29
|
-
in [.github/workflows/ci.yml](.github/workflows/ci.yml));
|
|
30
|
-
other versions.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
- Spinel `2026.09.12` (`Cybertrain::SPINEL_TAG`, the same release as
|
|
40
|
+
`SPINEL_TAG` in [.github/workflows/ci.yml](.github/workflows/ci.yml));
|
|
41
|
+
untested against other versions. You do not install it by hand: the
|
|
42
|
+
`cybertrain` command builds that release into `~/.cybertrain` the first
|
|
43
|
+
time it needs it (`cybertrain setup` does that on request), unless a
|
|
44
|
+
`spinel` of that release is already on `PATH` — see "Installing the CLI".
|
|
45
|
+
- To build it: a C toolchain (`cc`, or the compiler `CC` names) and `make`
|
|
46
|
+
— Spinel compiles every program to C — plus `git` and `curl`.
|
|
34
47
|
- SQLite 3 headers/library (`libsqlite3-dev` on Debian/Ubuntu; present with
|
|
35
48
|
Xcode's command line tools on macOS) — models use Spinel's FFI directly,
|
|
36
|
-
not a gem.
|
|
37
|
-
|
|
49
|
+
not a gem.
|
|
50
|
+
- Ruby 3.2+ and RubyGems, for the `cybertrain` command only (applications
|
|
51
|
+
never run on CRuby).
|
|
52
|
+
- OpenSSL's headers (`libssl-dev`) are optional: without them Spinel builds
|
|
53
|
+
without its `openssl` package, which cybertrain does not use.
|
|
54
|
+
|
|
55
|
+
`cybertrain doctor` checks these tools and headers and the Spinel install;
|
|
56
|
+
when a required one is missing it also prints the install command for your
|
|
57
|
+
platform.
|
|
38
58
|
|
|
39
|
-
The framework has no separate test runner; it tests itself
|
|
59
|
+
The framework has no separate test runner; it tests itself. `spin` comes
|
|
60
|
+
with Spinel, and `cybertrain spin` runs the copy `cybertrain` installs
|
|
61
|
+
without touching `PATH` (or put that copy on `PATH` with the `export
|
|
62
|
+
PATH=...` line `cybertrain setup` prints, and call `spin` directly):
|
|
40
63
|
|
|
41
64
|
```sh
|
|
42
|
-
spin test
|
|
65
|
+
cybertrain spin test
|
|
43
66
|
```
|
|
44
67
|
|
|
45
68
|
compiles and runs every program under `test/` and diffs its output against a
|
|
46
69
|
committed `test/<name>.rb.expected` snapshot — minitest/RSpec can't run under
|
|
47
70
|
Spinel, since they find test methods by reflection and an ahead-of-time
|
|
48
|
-
compiler has nothing to reflect on at run time. `spin test
|
|
49
|
-
test/<name>.rb` rewrites a snapshot from CRuby's output; a few tests that
|
|
71
|
+
compiler has nothing to reflect on at run time. `cybertrain spin test
|
|
72
|
+
--regen test/<name>.rb` rewrites a snapshot from CRuby's output; a few tests that
|
|
50
73
|
touch SQLite through FFI take theirs from the compiled binary instead
|
|
51
74
|
([spikes/NOTES.md](spikes/NOTES.md), rule 23); `script/regen-snapshot
|
|
52
75
|
test/<name>.rb` does that build-then-capture step for you and prints the diff stat.
|
|
@@ -55,38 +78,93 @@ test/<name>.rb` does that build-then-capture step for you and prints the diff st
|
|
|
55
78
|
|
|
56
79
|
```sh
|
|
57
80
|
gem install cybertrain
|
|
81
|
+
cybertrain new blog # also installs Spinel, the first time (a few minutes)
|
|
82
|
+
cd blog
|
|
83
|
+
cybertrain g scaffold article title:string body:text
|
|
84
|
+
cybertrain db migrate
|
|
85
|
+
cybertrain server # http://127.0.0.1:3000
|
|
86
|
+
cybertrain build # production: dist/
|
|
58
87
|
```
|
|
59
88
|
|
|
60
89
|
The `cybertrain` command is plain Ruby and the gem runs it under CRuby
|
|
61
|
-
(3.2 or newer); the framework
|
|
62
|
-
(`cybertrain help` prints them):
|
|
90
|
+
(3.2 or newer); neither the framework nor Spinel is in the gem. Its
|
|
91
|
+
commands (`cybertrain help` prints them):
|
|
63
92
|
|
|
64
|
-
- `cybertrain new NAME
|
|
65
|
-
|
|
93
|
+
- `cybertrain new NAME [--path DIR | --version V | --git URL [--ref R]]
|
|
94
|
+
[--skip-spin]` creates an application in NAME, then runs `spin lock` and
|
|
95
|
+
`spin run gen` in it, installing Spinel first when it is needed.
|
|
66
96
|
- `cybertrain generate scaffold NAME field:type ...` (alias `g scaffold`)
|
|
67
97
|
adds a resource to the application in the current directory.
|
|
68
|
-
- `cybertrain
|
|
69
|
-
generates again (`spin run gen; spin run db -- migrate; spin run gen`)
|
|
70
|
-
|
|
71
|
-
|
|
98
|
+
- `cybertrain db migrate` generates, applies pending migrations, and
|
|
99
|
+
generates again (`spin run gen; spin run db -- migrate; spin run gen`);
|
|
100
|
+
`cybertrain migration` is kept as an alias.
|
|
101
|
+
- `cybertrain db COMMAND...` runs any other database command after `spin
|
|
102
|
+
run gen`: `status`, `rollback [N]` (which generates again afterwards),
|
|
103
|
+
`schema:dump`, `create`.
|
|
72
104
|
- `cybertrain server [PORT]` generates, then starts the development server
|
|
73
105
|
(`spin run gen; spin run NAME`, or `spin run NAME -- PORT`; default port
|
|
74
106
|
3000).
|
|
75
107
|
- `cybertrain build` builds NAME with `app/views/` embedded and assembles
|
|
76
108
|
`dist/` (the binary, `public/`, `storage/`, `tmp/`).
|
|
109
|
+
- `cybertrain spin ARGS...` runs `spin` with the pinned Spinel from any
|
|
110
|
+
directory (`cybertrain spin test`, `cybertrain spin run gen -- --check`).
|
|
111
|
+
- `cybertrain setup [--force]` installs Spinel `2026.09.12` into
|
|
112
|
+
`~/.cybertrain` unless a `spinel` of that release is already on `PATH`;
|
|
113
|
+
`--force` rebuilds the copy under `~/.cybertrain`.
|
|
114
|
+
- `cybertrain doctor` checks the C toolchain, the SQLite headers and the
|
|
115
|
+
Spinel install.
|
|
116
|
+
- `cybertrain version` prints the version.
|
|
117
|
+
|
|
118
|
+
`db`, `server` and `build` run inside an application; NAME is the
|
|
119
|
+
`[package] name` in its `spin.toml`.
|
|
120
|
+
|
|
121
|
+
Every command that runs `spin` (`new`, `db`, `server`, `build`, `spin`) first makes
|
|
122
|
+
sure a Spinel of the release in `Cybertrain::SPINEL_TAG` is available. A
|
|
123
|
+
`spinel` on `PATH` (with a `spin`) whose `spinel --version` reports that
|
|
124
|
+
release is used as it is; otherwise the copy `cybertrain` keeps under
|
|
125
|
+
`~/.cybertrain/spinel/2026.09.12/` is used; otherwise `cybertrain` builds
|
|
126
|
+
that copy from the release tag (`git clone`, `make deps`, `make -j`, `make
|
|
127
|
+
install PREFIX=...`), once, in a few minutes. The build log is
|
|
128
|
+
`~/.cybertrain/log/spinel-2026.09.12-build.log` (its last lines are printed
|
|
129
|
+
if a step fails); if a tool the build needs is missing, `cybertrain` says
|
|
130
|
+
which and prints the install command for your platform before it starts. A
|
|
131
|
+
`spinel` of another release on `PATH` is never used or touched:
|
|
132
|
+
`cybertrain` keeps its own copy (and says so when it installs it).
|
|
133
|
+
|
|
134
|
+
`cybertrain setup` does the same on request and, whenever its own copy is
|
|
135
|
+
the one in use, prints the line that puts it on `PATH`, for running `spin`
|
|
136
|
+
yourself (`~/.cybertrain/bin` holds `spinel` and `spin` links to the release
|
|
137
|
+
in use, so the line survives a release bump; `cybertrain spin ...` needs no
|
|
138
|
+
`PATH` at all):
|
|
77
139
|
|
|
78
|
-
|
|
79
|
-
|
|
140
|
+
```sh
|
|
141
|
+
export PATH="$HOME/.cybertrain/bin:$PATH"
|
|
142
|
+
```
|
|
80
143
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
144
|
+
Two environment variables override all this: `CYBERTRAIN_HOME` replaces
|
|
145
|
+
`~/.cybertrain` (a path without spaces, quotes, `$` or `:`, which Spinel's
|
|
146
|
+
Makefile and `PATH` cannot take), and `CYBERTRAIN_SPINEL_HOME=PREFIX` forces
|
|
147
|
+
an existing install — `cybertrain` then uses the `spinel` and `spin` in `PREFIX/bin` (or
|
|
148
|
+
in `PREFIX` itself, if `spinel` is directly in it) and stops with an error
|
|
149
|
+
if they are not that release.
|
|
150
|
+
|
|
151
|
+
`cybertrain new` writes the application with its `spin.toml` pointing at
|
|
152
|
+
the release matching the CLI (`cybertrain = { git =
|
|
153
|
+
"https://github.com/saeki-mototsune/cybertrain", ref = "v0.2.0" }`), makes
|
|
154
|
+
sure Spinel is installed, then runs `spin lock` (spin fetches the framework
|
|
155
|
+
into its cache, `~/.cache/spin/packages/`, and pins the commit in
|
|
156
|
+
`spin.lock`) and `spin run gen`, much as `rails new` runs `bundle install`,
|
|
157
|
+
and prints the next steps. Nothing of the framework is copied into the app;
|
|
158
|
+
commit `spin.toml` and `spin.lock`. If Spinel or one of the two `spin`
|
|
159
|
+
steps fails, the files stay, `new` exits with 1 and prints what to run once
|
|
160
|
+
the cause is fixed (for Spinel: `cybertrain setup`, then `cybertrain spin
|
|
161
|
+
lock && cybertrain spin run gen` in the new directory).
|
|
162
|
+
|
|
163
|
+
`--skip-spin` only writes the files: no Spinel install, no `spin lock`, no
|
|
164
|
+
`spin run gen`. `--path DIR` depends on a local checkout instead,
|
|
165
|
+
`--git URL [--ref R]` on the framework at URL (at branch or tag R), and
|
|
166
|
+
`--version V` on an index version once cybertrain is on a `spin-index`.
|
|
167
|
+
Give only one of `--path`, `--version` and `--git`; `--ref` needs `--git`.
|
|
90
168
|
|
|
91
169
|
Working on cybertrain itself, run the CLI from the checkout (`spin install`
|
|
92
170
|
builds `bin/cybertrain.rb` into `~/.local/bin/cybertrain`; `ruby -I.
|
|
@@ -119,14 +197,15 @@ blog/
|
|
|
119
197
|
`bin/blog.rb` is the application's one entry point, the program `spin build
|
|
120
198
|
blog` compiles: `./blog` serves, `./blog migrate` and `./blog db status`
|
|
121
199
|
manage the database. `bin/gen.rb` runs the generator (`spin run gen`), and
|
|
122
|
-
`bin/db.rb` runs the migrator during development (`cybertrain
|
|
200
|
+
`bin/db.rb` runs the migrator during development (`cybertrain db migrate`
|
|
123
201
|
uses it, since `bin/blog.rb` cannot compile until the first migration has
|
|
124
202
|
generated `gen/models/`). `gen/` already holds `views.rb`, the committed,
|
|
125
203
|
empty table of embedded views (see "How it works").
|
|
126
204
|
|
|
127
205
|
Apps created before `cybertrain build` existed have `bin/server.rb` and
|
|
128
206
|
no `bin/<name>.rb`: copy `bin/<name>.rb` from a fresh `cybertrain new` (with
|
|
129
|
-
the same NAME), delete `bin/server.rb
|
|
207
|
+
the same NAME), delete `bin/server.rb`, add `/dist/` to `.gitignore` (the old
|
|
208
|
+
template did not ignore it) and run `cybertrain spin run gen`.
|
|
130
209
|
|
|
131
210
|
`ApplicationController` starts with the one thing every controller inherits:
|
|
132
211
|
|
|
@@ -210,7 +289,7 @@ end
|
|
|
210
289
|
### Generate, migrate, run
|
|
211
290
|
|
|
212
291
|
```sh
|
|
213
|
-
cybertrain
|
|
292
|
+
cybertrain db migrate # spin run gen; spin run db -- migrate; spin run gen
|
|
214
293
|
cybertrain server # http://127.0.0.1:3000; rebuilds on Ruby edits, views reload without a rebuild
|
|
215
294
|
```
|
|
216
295
|
|
|
@@ -218,14 +297,15 @@ cybertrain server # http://127.0.0.1:3000; rebuilds on Ruby edits, views
|
|
|
218
297
|
writes `gen/`; `spin run db -- migrate` applies `db/migrate/*.rb` and
|
|
219
298
|
rewrites `db/schema.rb`.
|
|
220
299
|
|
|
221
|
-
Re-run `spin run gen` (and commit `gen/`) after touching the
|
|
222
|
-
or a controller/model's callbacks and ivars — the dev server does this for
|
|
300
|
+
Re-run `cybertrain spin run gen` (and commit `gen/`) after touching the
|
|
301
|
+
schema, routes, or a controller/model's callbacks and ivars — the dev server does this for
|
|
223
302
|
you automatically (see "How it works").
|
|
224
303
|
|
|
225
304
|
### Add comments, nested under articles
|
|
226
305
|
|
|
227
306
|
```sh
|
|
228
307
|
cybertrain generate scaffold comment commenter:string body:text article:references
|
|
308
|
+
cybertrain db migrate # the scaffold wrote a migration for the comments table
|
|
229
309
|
```
|
|
230
310
|
|
|
231
311
|
`article:references` adds an `article_id` column, index and foreign key —
|
|
@@ -352,8 +432,8 @@ merge.
|
|
|
352
432
|
**Views are interpreted, not compiled.** `app/views/**/*.html.erb` files look
|
|
353
433
|
like Rails ERB but are parsed into an AST at request time, then walked by a
|
|
354
434
|
tree-walking interpreter. Where the source comes from depends on the mode:
|
|
355
|
-
in development (and test) the engine reads `app/views/` from disk and
|
|
356
|
-
re-parses a template when it changes, so view edits need no rebuild.
|
|
435
|
+
in development (and test) the engine reads `app/views/` from disk, and in
|
|
436
|
+
development re-parses a template when it changes, so view edits need no rebuild.
|
|
357
437
|
`cybertrain build` runs `spin run gen -- --embed-views`, which writes every
|
|
358
438
|
template's source into `gen/views.rb` as a string table for that one `spin
|
|
359
439
|
build`, then runs `spin run gen` again to restore the empty table that is
|
|
@@ -381,9 +461,10 @@ adapter today (PostgreSQL via `libpq` FFI is noted as possible future work).
|
|
|
381
461
|
`cybertrain build`) also polls `app/**/*.rb`, `config/**/*.rb`, `db/schema.rb`
|
|
382
462
|
and `gen/**/*.rb` every half second (views are excluded — the engine reloads
|
|
383
463
|
those itself — and so is `gen/views.rb`, empty in development). A change runs `spin run gen && spin build NAME` in the
|
|
384
|
-
background; success
|
|
385
|
-
|
|
386
|
-
|
|
464
|
+
background; success requests a restart (the same flag an external `kill -HUP`
|
|
465
|
+
sets): a monitor thread stops the listener and drains open requests, then the
|
|
466
|
+
main thread `execv`s the new binary on the same port and PID, invisible to a
|
|
467
|
+
client mid-session. A failed build keeps serving the old binary and banners the
|
|
387
468
|
compiler output on every HTML response. None of this loads in production.
|
|
388
469
|
|
|
389
470
|
## Differences from Rails
|
|
@@ -391,7 +472,7 @@ compiler output on every HTML response. None of this loads in production.
|
|
|
391
472
|
| Rails | cybertrain |
|
|
392
473
|
| --- | --- |
|
|
393
474
|
| `rails console` | No console — Spinel has no `eval` |
|
|
394
|
-
| Edit code, the running app picks it up | Ruby needs a rebuild; `cybertrain server` in development does this for you (rebuild,
|
|
475
|
+
| Edit code, the running app picks it up | Ruby needs a rebuild; `cybertrain server` in development does this for you (rebuild, stop, `execv`) |
|
|
395
476
|
| Edit a view, no reload needed | Same — views are parsed from disk per request in development |
|
|
396
477
|
| `def new` | `def new_action` (`new` would shadow `Klass.new(ctx)`) |
|
|
397
478
|
| `before_action { do_thing }` (implicit `self`) | `before_action { \|c\| c.do_thing }` — no `instance_exec` on a stored block, so callbacks take the controller/record explicitly |
|
|
@@ -458,7 +539,7 @@ systemd, Caddy with HTTPS, redeploys, rollbacks and backups.
|
|
|
458
539
|
App tests are plain Spinel programs under `test/`, like the framework's own:
|
|
459
540
|
|
|
460
541
|
```ruby
|
|
461
|
-
|
|
542
|
+
require_relative "support/blog_test" # the app, BLOG and Cybertrain::Test
|
|
462
543
|
|
|
463
544
|
test "title must be present" do
|
|
464
545
|
article = Article.new(body: "a body long enough to pass length")
|
|
@@ -473,29 +554,47 @@ full HTTP flows, `Cybertrain::Test::Client` drives an app in-process (no
|
|
|
473
554
|
socket) with a cookie jar, so sessions and CSRF behave as behind a browser:
|
|
474
555
|
|
|
475
556
|
```ruby
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
client.
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
557
|
+
require_relative "support/blog_test" # the app, BLOG and Cybertrain::Test
|
|
558
|
+
|
|
559
|
+
test "POST /articles creates an article and redirects to it" do
|
|
560
|
+
client = Cybertrain::Test::Client.new(BLOG)
|
|
561
|
+
client.get("/articles/new")
|
|
562
|
+
res = client.post("/articles", { "authenticity_token" => BlogTest.form_token(client),
|
|
563
|
+
"article[title]" => "Hello Rails",
|
|
564
|
+
"article[body]" => "I am on Rails! This is my first article." })
|
|
565
|
+
assert_redirected_to res, BlogTest.article_path(Article.last)
|
|
566
|
+
assert_response res, :see_other
|
|
567
|
+
end
|
|
485
568
|
```
|
|
486
569
|
|
|
487
|
-
|
|
488
|
-
`test/support/blog_test.rb`)
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
570
|
+
Both snippets start from the example's helper,
|
|
571
|
+
[`examples/blog/test/support/blog_test.rb`](examples/blog/test/support/blog_test.rb):
|
|
572
|
+
copy it into an app's `test/support/`, where files are required, not run.
|
|
573
|
+
It migrates a fresh `storage/test.sqlite3`, boots the app as `BLOG`, loads
|
|
574
|
+
`Cybertrain::Test` and its client, and reads the CSRF token out of the last
|
|
575
|
+
rendered form (`BlogTest.form_token`). End each test file with
|
|
576
|
+
`Cybertrain::Test.run!`; `examples/blog/test/articles.rb` and `comments.rb`
|
|
577
|
+
show the full pattern.
|
|
578
|
+
|
|
579
|
+
Run with `cybertrain spin test`: it compiles each `test/*.rb` into its own
|
|
580
|
+
program and diffs its output (stdout and stderr together) against
|
|
581
|
+
`test/<name>.rb.expected`. Regenerate with `cybertrain spin test --regen
|
|
582
|
+
test/<name>.rb`; FFI/database tests can't run under CRuby, so their snapshot
|
|
583
|
+
comes from the compiled binary instead: `rm -f build/test/<name>; cybertrain
|
|
584
|
+
spin test test/<name>.rb; [ -x build/test/<name> ] && ./build/test/<name> >
|
|
585
|
+
test/<name>.rb.expected 2>&1` (`cybertrain spin test` exits non-zero while the
|
|
586
|
+
snapshot is stale, so it is followed by `;`, not `&&`; the `[ -x ]` check skips
|
|
587
|
+
the capture when the compile failed, so the old snapshot is kept; in the
|
|
588
|
+
framework checkout, `script/regen-snapshot test/<name>.rb` does exactly this and
|
|
589
|
+
prints the diff stat).
|
|
496
590
|
|
|
497
591
|
## Learn more
|
|
498
592
|
|
|
593
|
+
- [Homepage](https://saeki-mototsune.github.io/CyberTrain/) and
|
|
594
|
+
[tutorial](https://saeki-mototsune.github.io/CyberTrain/tutorial.html) —
|
|
595
|
+
the source lives in [site/](site/), published by
|
|
596
|
+
[.github/workflows/pages.yml](.github/workflows/pages.yml); the logo files and
|
|
597
|
+
brand notes are in [site/assets/brand/](site/assets/brand/BRAND.md).
|
|
499
598
|
- [docs/design.md](docs/design.md) — the design record (Japanese): every
|
|
500
599
|
decision, what was rejected and why, and the Spinel constraints behind it.
|
|
501
600
|
- [docs/template-language.md](docs/template-language.md) — the full template
|
|
@@ -516,8 +615,13 @@ before the gem is pushed:
|
|
|
516
615
|
1. Bump `Cybertrain::VERSION` (`cybertrain/version.rb`) and `version` in
|
|
517
616
|
`spin.toml` together (CI checks they match); spin caches a git
|
|
518
617
|
dependency by that version.
|
|
519
|
-
2. Merge to `main`, then tag and push: `git tag v0.
|
|
520
|
-
3. `gem build cybertrain.gemspec && gem push cybertrain-0.
|
|
618
|
+
2. Merge to `main`, then tag and push: `git tag v0.2.0 && git push origin v0.2.0`.
|
|
619
|
+
3. `gem build cybertrain.gemspec && gem push cybertrain-0.2.0.gem`.
|
|
620
|
+
|
|
621
|
+
`Cybertrain::SPINEL_TAG` (also in `cybertrain/version.rb`) is the Spinel
|
|
622
|
+
release `cybertrain setup` installs. It must equal `SPINEL_TAG` in
|
|
623
|
+
[.github/workflows/ci.yml](.github/workflows/ci.yml), which CI checks, so
|
|
624
|
+
change the two together when the framework moves to another Spinel release.
|
|
521
625
|
|
|
522
626
|
## License
|
|
523
627
|
|
data/cybertrain/cli/build.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# `cybertrain build`: dist/ = the app binary with app/views embedded, plus
|
|
2
|
-
# public/. Also the command lists `cybertrain
|
|
2
|
+
# public/. Also the command lists `cybertrain db` and `server` run. Plain Ruby:
|
|
3
3
|
# runs under CRuby (the gem) and compiles under Spinel (spin install).
|
|
4
4
|
module Cybertrain
|
|
5
5
|
module CLI
|
|
@@ -29,10 +29,28 @@ module Cybertrain
|
|
|
29
29
|
["spin run gen -- --embed-views", "spin build #{name}", "spin run gen"]
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
#
|
|
33
|
-
# migration has produced gen/models.
|
|
32
|
+
# `cybertrain db ARGS`, through bin/db.rb (the app binary cannot compile
|
|
33
|
+
# before the first migration has produced gen/models). bin/db.rb only
|
|
34
|
+
# knows the migrations gen/migrations.rb lists, so gen runs first;
|
|
35
|
+
# migrate and rollback rewrite db/schema.rb, which gen/models is
|
|
36
|
+
# derived from, so gen runs again after them.
|
|
37
|
+
def self.db_commands(args)
|
|
38
|
+
words = args.map { |arg| quote_arg(arg) }.join(" ")
|
|
39
|
+
commands = ["spin run gen", "spin run db -- #{words}"]
|
|
40
|
+
commands << "spin run gen" if args[0] == "migrate" || args[0] == "rollback"
|
|
41
|
+
commands
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# `cybertrain migration`: the same as `cybertrain db migrate`.
|
|
34
45
|
def self.migration_commands
|
|
35
|
-
["
|
|
46
|
+
db_commands(["migrate"])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# A word sh reads as itself stays bare; anything else is single-quoted.
|
|
50
|
+
def self.quote_arg(text)
|
|
51
|
+
return text if !text.empty? && text.match(/\A[A-Za-z0-9_:.\/-]+\z/)
|
|
52
|
+
|
|
53
|
+
shell_quote(text)
|
|
36
54
|
end
|
|
37
55
|
|
|
38
56
|
# port is "" (the app's default, 3000) or a port? string.
|
data/cybertrain/cli/new_app.rb
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# `cybertrain new blog`: writes the skeleton of an application package.
|
|
2
2
|
require "cybertrain/generator/inflector"
|
|
3
3
|
require "cybertrain/cli/templates"
|
|
4
|
+
require "cybertrain/cli/build"
|
|
5
|
+
require "cybertrain/cli/toolchain"
|
|
4
6
|
|
|
5
7
|
module Cybertrain
|
|
6
8
|
module CLI
|
|
7
9
|
module NewApp
|
|
8
|
-
KEEP_DIRS = ["db/migrate", "app/models", "app/helpers", "gen", "storage", "tmp", "test"]
|
|
9
|
-
|
|
10
10
|
# dir is the directory to create ("blog" or "path/to/blog"; its last
|
|
11
11
|
# component names the package). framework_dep is the TOML value that
|
|
12
12
|
# spin.toml's `cybertrain =` line gets: `{ path = "/abs/cybertrain" }` or
|
|
@@ -47,28 +47,33 @@ module Cybertrain
|
|
|
47
47
|
|
|
48
48
|
# What `rails new` does with `bundle install`: resolve and lock the
|
|
49
49
|
# framework (fetching it into spin's cache, ~/.cache/spin), then write
|
|
50
|
-
# gen/ so `spin build` works straight away.
|
|
51
|
-
#
|
|
50
|
+
# gen/ so `spin build` works straight away. Installs Spinel first when
|
|
51
|
+
# this machine has none of the pinned release (Toolchain.ensure!).
|
|
52
|
+
# Returns false when a step failed.
|
|
52
53
|
def self.bootstrap(dir)
|
|
53
|
-
unless
|
|
54
|
-
puts "skip spin lock / spin run gen:
|
|
55
|
-
puts "
|
|
56
|
-
return
|
|
54
|
+
unless Toolchain.ensure!
|
|
55
|
+
puts "skip spin lock / spin run gen: Spinel #{Cybertrain::SPINEL_TAG} is not available"
|
|
56
|
+
puts " fix the problem above (`cybertrain doctor` lists the checks), then: cybertrain setup && #{recovery_command(dir)}"
|
|
57
|
+
return false
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
puts "run spin lock && spin run gen"
|
|
60
61
|
return true if system(bootstrap_command(dir))
|
|
61
62
|
|
|
62
|
-
puts "error: bootstrapping #{dir} failed; fix the cause, then run: #{
|
|
63
|
+
puts "error: bootstrapping #{dir} failed; fix the cause, then run: #{recovery_command(dir)}"
|
|
63
64
|
false
|
|
64
65
|
end
|
|
65
66
|
|
|
67
|
+
# Run by bootstrap once Toolchain.ensure! has put spin on this
|
|
68
|
+
# process's PATH.
|
|
66
69
|
def self.bootstrap_command(dir)
|
|
67
|
-
"cd #{shell_quote(dir)} && spin lock && spin run gen"
|
|
70
|
+
"cd #{Build.shell_quote(dir)} && spin lock && spin run gen"
|
|
68
71
|
end
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
# The same steps for the user to run by hand: through `cybertrain
|
|
74
|
+
# spin`, since the Spinel cybertrain installed is not on their PATH.
|
|
75
|
+
def self.recovery_command(dir)
|
|
76
|
+
"cd #{Build.shell_quote(dir)} && cybertrain spin lock && cybertrain spin run gen"
|
|
72
77
|
end
|
|
73
78
|
end
|
|
74
79
|
end
|
data/cybertrain/cli/scaffold.rb
CHANGED
|
@@ -92,6 +92,13 @@ module Cybertrain
|
|
|
92
92
|
@fields = fields
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
+
# The index route helper's name without _path, as Rails' scaffold
|
|
96
|
+
# names it: the plural, or "<plural>_index" for a word that is its own
|
|
97
|
+
# plural (sheep_index_path), matching the routes DSL.
|
|
98
|
+
def index_helper
|
|
99
|
+
@plural == @singular ? "#{@plural}_index" : @plural
|
|
100
|
+
end
|
|
101
|
+
|
|
95
102
|
# The column the model validates for presence ("" when there is none).
|
|
96
103
|
def first_string_field
|
|
97
104
|
@fields.each { |f| return f.field_name if f.field_type == "string" }
|
data/cybertrain/cli/templates.rb
CHANGED
|
@@ -92,17 +92,17 @@ module Cybertrain
|
|
|
92
92
|
|
|
93
93
|
```sh
|
|
94
94
|
cybertrain generate scaffold post title:string body:text
|
|
95
|
-
cybertrain
|
|
95
|
+
cybertrain db migrate # gen, apply db/migrate, gen again
|
|
96
96
|
cybertrain server # http://127.0.0.1:3000 (`cybertrain server 4000` for another port)
|
|
97
97
|
cybertrain build # dist/: the binary (views embedded) + public/
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
`spin run gen` (which `
|
|
100
|
+
`spin run gen` (which `db`, `server` and `build` run for you)
|
|
101
101
|
regenerates `gen/` from db/schema.rb, config/routes.rb and app/;
|
|
102
102
|
commit `gen/`. In development views under `app/views/` are read at
|
|
103
103
|
run time: edit them without rebuilding. `cybertrain db status` and
|
|
104
104
|
`cybertrain db rollback 1` reach the other database commands.
|
|
105
|
-
`cybertrain
|
|
105
|
+
`cybertrain db migrate` runs migrations through `bin/db.rb`; a
|
|
106
106
|
deployed `dist/#{package}` runs them with `./#{package} migrate`.
|
|
107
107
|
|
|
108
108
|
To deploy, copy `dist/` to a machine with the same OS and CPU, then
|
|
@@ -326,7 +326,7 @@ module Cybertrain
|
|
|
326
326
|
def destroy
|
|
327
327
|
@#{s}.destroy
|
|
328
328
|
flash[:notice] = "#{human} was successfully destroyed."
|
|
329
|
-
redirect_to #{
|
|
329
|
+
redirect_to #{res.index_helper}_path, status: :see_other
|
|
330
330
|
end
|
|
331
331
|
|
|
332
332
|
private
|
|
@@ -371,7 +371,7 @@ module Cybertrain
|
|
|
371
371
|
end
|
|
372
372
|
buf << "<p>\n"
|
|
373
373
|
buf << " <%= link_to \"Edit this #{words}\", edit_#{s}_path(@#{s}) %> |\n"
|
|
374
|
-
buf << " <%= link_to \"Back to #{res.plural.tr("_", " ")}\", #{res.
|
|
374
|
+
buf << " <%= link_to \"Back to #{res.plural.tr("_", " ")}\", #{res.index_helper}_path %>\n"
|
|
375
375
|
buf << "</p>\n\n"
|
|
376
376
|
buf << "<%= button_to \"Destroy this #{words}\", #{s}_path(@#{s}), method: :delete %>\n"
|
|
377
377
|
buf
|
|
@@ -382,7 +382,7 @@ module Cybertrain
|
|
|
382
382
|
buf = +""
|
|
383
383
|
buf << "<h1>New #{s.tr("_", " ")}</h1>\n\n"
|
|
384
384
|
buf << "<%= render \"form\", #{s}: @#{s} %>\n\n"
|
|
385
|
-
buf << "<p><%= link_to \"Back to #{res.plural.tr("_", " ")}\", #{res.
|
|
385
|
+
buf << "<p><%= link_to \"Back to #{res.plural.tr("_", " ")}\", #{res.index_helper}_path %></p>\n"
|
|
386
386
|
buf
|
|
387
387
|
end
|
|
388
388
|
|
|
@@ -393,7 +393,7 @@ module Cybertrain
|
|
|
393
393
|
buf << "<%= render \"form\", #{s}: @#{s} %>\n\n"
|
|
394
394
|
buf << "<p>\n"
|
|
395
395
|
buf << " <%= link_to \"Show this #{s.tr("_", " ")}\", #{s}_path(@#{s}) %> |\n"
|
|
396
|
-
buf << " <%= link_to \"Back to #{res.plural.tr("_", " ")}\", #{res.
|
|
396
|
+
buf << " <%= link_to \"Back to #{res.plural.tr("_", " ")}\", #{res.index_helper}_path %>\n"
|
|
397
397
|
buf << "</p>\n"
|
|
398
398
|
buf
|
|
399
399
|
end
|