dr_talon 0.1.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 +7 -0
- data/CHANGELOG.md +60 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +270 -0
- data/Rakefile +10 -0
- data/exe/talon +6 -0
- data/lib/dr_talon/cli/engine.rb +274 -0
- data/lib/dr_talon/cli/generate.rb +29 -0
- data/lib/dr_talon/cli.rb +343 -0
- data/lib/dr_talon/config.rb +29 -0
- data/lib/dr_talon/credentials.rb +94 -0
- data/lib/dr_talon/deps/dependency.rb +238 -0
- data/lib/dr_talon/deps/git.rb +69 -0
- data/lib/dr_talon/deps/installer.rb +207 -0
- data/lib/dr_talon/deps/manifest.rb +220 -0
- data/lib/dr_talon/deps/tree.rb +97 -0
- data/lib/dr_talon/deps.rb +62 -0
- data/lib/dr_talon/doctor.rb +161 -0
- data/lib/dr_talon/engine/fetchers.rb +251 -0
- data/lib/dr_talon/engine/git_ignore.rb +63 -0
- data/lib/dr_talon/engine/importer.rb +103 -0
- data/lib/dr_talon/engine/inspector.rb +113 -0
- data/lib/dr_talon/engine/installation.rb +77 -0
- data/lib/dr_talon/engine/materializer.rb +172 -0
- data/lib/dr_talon/engine/spec.rb +38 -0
- data/lib/dr_talon/engine/store.rb +119 -0
- data/lib/dr_talon/engine.rb +50 -0
- data/lib/dr_talon/errors.rb +5 -0
- data/lib/dr_talon/file_copy.rb +49 -0
- data/lib/dr_talon/generators/new_project.rb +289 -0
- data/lib/dr_talon/generators/talon_code.rb +92 -0
- data/lib/dr_talon/http.rb +120 -0
- data/lib/dr_talon/paths.rb +115 -0
- data/lib/dr_talon/project.rb +101 -0
- data/lib/dr_talon/runner.rb +188 -0
- data/lib/dr_talon/talon_framework.rb +103 -0
- data/lib/dr_talon/templates/bare/main_tests.rb.tt +7 -0
- data/lib/dr_talon/templates/bare/rubocop.yml.tt +8 -0
- data/lib/dr_talon/templates/bare/solargraph.yml.tt +24 -0
- data/lib/dr_talon/templates/lib/app/main.rb.tt +13 -0
- data/lib/dr_talon/templates/lib/lib/library.rb.tt +4 -0
- data/lib/dr_talon/templates/lib/lib/version.rb.tt +3 -0
- data/lib/dr_talon/templates/lib/rubocop.yml.tt +9 -0
- data/lib/dr_talon/templates/lib/solargraph.yml.tt +23 -0
- data/lib/dr_talon/templates/lib/test/test_helper.rb.tt +1 -0
- data/lib/dr_talon/templates/lib/test/tests.rb.tt +8 -0
- data/lib/dr_talon/templates/shared/Gemfile.tt +10 -0
- data/lib/dr_talon/templates/shared/README.md.tt +45 -0
- data/lib/dr_talon/templates/shared/editorconfig.tt +9 -0
- data/lib/dr_talon/templates/shared/gitignore.tt +19 -0
- data/lib/dr_talon/templates/talon/game/dependencies.rb.tt +3 -0
- data/lib/dr_talon/templates/talon/game/main.rb.tt +9 -0
- data/lib/dr_talon/templates/talon/game/spin.rb.tt +7 -0
- data/lib/dr_talon/templates/talon/game/title.rb.tt +8 -0
- data/lib/dr_talon/templates/talon/game/title_tests.rb.tt +16 -0
- data/lib/dr_talon/templates/talon/generate/component.rb.tt +19 -0
- data/lib/dr_talon/templates/talon/generate/component_tests.rb.tt +15 -0
- data/lib/dr_talon/templates/talon/generate/prefab.rb.tt +11 -0
- data/lib/dr_talon/templates/talon/generate/prefab_tests.rb.tt +15 -0
- data/lib/dr_talon/templates/talon/generate/scene.rb.tt +14 -0
- data/lib/dr_talon/templates/talon/generate/scene_tests.rb.tt +13 -0
- data/lib/dr_talon/test_report.rb +76 -0
- data/lib/dr_talon/version.rb +5 -0
- data/lib/dr_talon.rb +17 -0
- data/sig/dr_talon.rbs +524 -0
- metadata +142 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b0f3e2b0a809454db387d0d2782ea7374d93b8ecab4f68088b43775ea000ec04
|
|
4
|
+
data.tar.gz: 6505483d2a0afc819e039687ad651cf09a18eb4956ae8b0110546ba3ec9e5ae7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 5f3aae9f5c691833c3d43e76708855baf876358e2bf890b5bd1ee8563d565d48f0844110164ba672991799cba460971af7b5f1e57086f985333c3cf769c7c36e
|
|
7
|
+
data.tar.gz: f64e5e291bbae8b123015cc1e4eb504f8dc63ed053221f772ab241c56e5b336cc6c7fd2330d404daa3ed169ab8b04d31ef63224016886569a884484690460611
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
## [Unreleased]
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-10-11
|
|
4
|
+
|
|
5
|
+
First release: `talon`, a Rails-style CLI for DragonRuby Game Toolkit.
|
|
6
|
+
|
|
7
|
+
### Engines
|
|
8
|
+
|
|
9
|
+
- `talon engine import <zip|dir>` caches a DragonRuby engine as `VERSION-TIER-PLATFORM`, reading the version from
|
|
10
|
+
`CHANGELOG-CURR.txt`, the platform from the engine directory, and the tier from the zip name (or `--tier`).
|
|
11
|
+
Executable bits and the hidden `.dragonruby/` directory are kept; re-importing the same zip is a no-op.
|
|
12
|
+
- `talon engine fetch` downloads the latest engine for this machine: Pro and Indie from dragonruby.org with your
|
|
13
|
+
account login, the free Jam license from DragonRuby's itch.io page with no account, and Standard with itch.io's
|
|
14
|
+
butler. The tier defaults to your newest cached engine's.
|
|
15
|
+
- `talon engine login` / `logout` check and save, or delete, a dragonruby.org login in `credentials.yml` (mode 0600;
|
|
16
|
+
refused if others can read it). `DRAGONRUBY_USERNAME` / `DRAGONRUBY_PASSWORD` take precedence.
|
|
17
|
+
- `talon engine list`, `which`, `uninstall` (asks first, since the cache may be the only copy of an old version).
|
|
18
|
+
- `talon engine use [ENGINE]` pins an engine in `.dragonruby-version` (version and tier only, so it works on every
|
|
19
|
+
OS) and copies it into the project, cloning files on btrfs/XFS. Installed files are tracked in
|
|
20
|
+
`.talon/engine.yml`, so switching engines removes exactly those; `mygame/` and your own files are never touched,
|
|
21
|
+
and differing files stop the install unless `--force`. `talon engine current` shows pinned vs installed.
|
|
22
|
+
|
|
23
|
+
### Projects
|
|
24
|
+
|
|
25
|
+
- `talon new NAME` creates a game in DragonRuby's layout from the cached engine's own `mygame/`, with a starter
|
|
26
|
+
test, RuboCop and Solargraph config, `game_metadata.txt` filled in (`--title`, `--dev-id`, `--dev-title`,
|
|
27
|
+
`config.yml`, or asked for in a terminal, since `dragonruby-publish` needs them), and `git init` /
|
|
28
|
+
`bundle install`. It asks whether to commit the engine; if not, a managed `.gitignore` block
|
|
29
|
+
ignores it and `talon engine use` keeps the block current.
|
|
30
|
+
- `talon new NAME --lib` creates a library (the repository root is the game directory) with `lib/`, a demo
|
|
31
|
+
`app/main.rb` and a DragonRuby test.
|
|
32
|
+
- `talon new NAME --template talon` creates a game on the [Talon](https://github.com/asimmons91/talon) framework,
|
|
33
|
+
with Talon vendored, a `Title` scene, a `Spin` component and a test.
|
|
34
|
+
- `talon generate scene|component|prefab NAME` (alias `talon g`) writes Talon code and a DragonRuby test, and adds
|
|
35
|
+
the require to `app/main.rb`.
|
|
36
|
+
- `talon doctor` checks the pin, the cached and installed engine, dependencies, and `game_metadata.txt`.
|
|
37
|
+
- `config.yml` settings: `default_template`, `dev_id`, `dev_title`.
|
|
38
|
+
|
|
39
|
+
### Running, testing and building
|
|
40
|
+
|
|
41
|
+
- `talon run [ARGS...]` runs the game; arguments such as `--record` pass through to `dragonruby`.
|
|
42
|
+
- `talon test [FILES...]` runs each `*_tests.rb` with `dragonruby --test`, headless by default, and reads
|
|
43
|
+
DragonRuby's output (it always exits 0) to report failures, files that don't load, files with no tests and
|
|
44
|
+
timeouts, exiting non-zero on any of them.
|
|
45
|
+
- `talon build` packages every platform with `dragonruby-publish --only-package`; `talon serve` serves the newest
|
|
46
|
+
HTML5 build with `dragonruby-httpd`; `talon clean` deletes `builds/`, `logs/`, `tmp/` and `errors/`.
|
|
47
|
+
|
|
48
|
+
### Dependencies
|
|
49
|
+
|
|
50
|
+
- Declared in the game directory's `dependencies.rb` with a Foodchain-compatible DSL (`github`, `git`, `url`,
|
|
51
|
+
`path`), vendored into `vendor/`, and locked below `__END__` to commit SHAs (or SHA-256s for URLs) with a checksum
|
|
52
|
+
of the vendored files. Foodchain's ETag locks are replaced on the first install.
|
|
53
|
+
- `talon add SOURCE` (`github:owner/repo`, `git:URL`, `url:URL`, `path:DIR`; `--path`, `--ref`, `--as`) installs a
|
|
54
|
+
dependency and prints the line to require it; `talon remove`, `talon update`, `talon outdated`.
|
|
55
|
+
- `talon install` restores the pinned engine if it's missing, then the locked dependencies. Locally edited vendored
|
|
56
|
+
files stop it unless `--force`.
|
|
57
|
+
- GitHub dependencies resolve refs with `git ls-remote` and download one tarball per dependency.
|
|
58
|
+
|
|
59
|
+
[Unreleased]: https://github.com/asimmons91/dr_talon/compare/v0.1.0...HEAD
|
|
60
|
+
[0.1.0]: https://github.com/asimmons91/dr_talon/releases/tag/v0.1.0
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"dr_talon" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["austin_simmons@fastmail.com"](mailto:"austin_simmons@fastmail.com").
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Austin Simmons
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# dr_talon
|
|
2
|
+
|
|
3
|
+
`talon` is a Rails-style command line tool for [DragonRuby Game Toolkit](https://dragonruby.org):
|
|
4
|
+
|
|
5
|
+
- **Engines.** It keeps a cache of DragonRuby versions, downloads new ones, and pins one per project, so every
|
|
6
|
+
clone runs the same engine.
|
|
7
|
+
- **Projects.** `talon new` scaffolds games and libraries; `talon run`, `test`, `build` and `serve` replace the
|
|
8
|
+
usual shell scripts. `talon test` reports failures properly, even though DragonRuby itself always exits 0.
|
|
9
|
+
- **Dependencies.** It vendors DragonRuby libraries from GitHub, git, URLs or local paths, locked to exact commits.
|
|
10
|
+
- **Talon.** Optionally, it scaffolds games on the [Talon](https://github.com/asimmons91/talon) framework and
|
|
11
|
+
generates scenes, components and prefabs.
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- Ruby 3.2 or newer
|
|
16
|
+
- git, for GitHub and git dependencies
|
|
17
|
+
- A DragonRuby engine. Any license works, including the free [Jam license](#downloading-engines), which `talon`
|
|
18
|
+
can download for you.
|
|
19
|
+
|
|
20
|
+
`talon` is developed and tested on Linux. It handles macOS and Windows paths and engine names, but has seen less
|
|
21
|
+
use there.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
gem install dr_talon
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The gem installs a `talon` executable. `talon help` lists every command, and `talon help COMMAND` describes one.
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
talon engine fetch --tier jam # or: talon engine import ~/Downloads/dragonruby-pro-linux-amd64.zip
|
|
35
|
+
talon new my_game # asks whether to commit the engine, and your itch.io name
|
|
36
|
+
cd my_game
|
|
37
|
+
talon run # ./dragonruby mygame
|
|
38
|
+
talon test # *_tests.rb in mygame/test/ and mygame/tests/ (one starter test)
|
|
39
|
+
talon build # package every platform your license allows into builds/
|
|
40
|
+
talon doctor # check the engine, dependencies and game metadata
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`dragonruby-publish` won't package a game until `game_metadata.txt` names its developer, so `talon new` asks for
|
|
44
|
+
your itch.io username and display name, unless you pass `--dev-id` and `--dev-title` or set them in your
|
|
45
|
+
[config](#configuration). It only asks in a terminal; scripts aren't prompted.
|
|
46
|
+
|
|
47
|
+
## Engines
|
|
48
|
+
|
|
49
|
+
DragonRuby only offers its latest release for download, so `talon` keeps a cache of every engine you fetch or
|
|
50
|
+
import. That cache is how you get back to an older version.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
talon engine import ~/Downloads/dragonruby-pro-linux-amd64.zip # the zip as downloaded, or an extracted directory
|
|
54
|
+
talon engine list
|
|
55
|
+
talon engine which 7.23 # 7.23, 7.23-pro or 7.23-pro-linux-amd64
|
|
56
|
+
talon engine uninstall 7.23 # asks first; --yes to skip
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Engines are identified as `VERSION-TIER-PLATFORM`, for example `7.23-pro-linux-amd64`. The version comes from
|
|
60
|
+
`CHANGELOG-CURR.txt` and the platform from the engine directory name. The tier comes from the zip or directory
|
|
61
|
+
name; pass `--tier pro|indie|standard|jam` if the name doesn't include it.
|
|
62
|
+
|
|
63
|
+
### Downloading engines
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
talon engine fetch --tier jam # the free Jam license; no account needed
|
|
67
|
+
talon engine login # once: saves your dragonruby.org login (mode 600)
|
|
68
|
+
talon engine fetch # latest release, same tier as your newest cached engine
|
|
69
|
+
talon engine fetch --tier indie
|
|
70
|
+
talon engine fetch --tier standard # via itch.io's butler, after `butler login`
|
|
71
|
+
talon engine logout
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- **Jam.** The [Jam license](https://dragonruby.itch.io/dragonruby-gtk/devlog/1620970/dragonruby-jam-license-free-to-all)
|
|
75
|
+
is free and can be used commercially. It has Standard's full API but only exports to the web. `talon` downloads
|
|
76
|
+
it the same way the Download button on [DragonRuby's itch.io page](https://dragonruby.itch.io/dragonruby-gtk#demo)
|
|
77
|
+
does, for Linux (x64 and arm64), macOS and Windows. If itch.io changes that page, download the zip by hand and
|
|
78
|
+
run `talon engine import`.
|
|
79
|
+
- **Pro and Indie.** These come from dragonruby.org's subscription API with your account's login. `talon` looks
|
|
80
|
+
for it in `DRAGONRUBY_USERNAME` and `DRAGONRUBY_PASSWORD`, then in the file saved by `talon engine login`, and
|
|
81
|
+
otherwise asks for it (offering to save it). The saved file holds the password in plain text, so it must be
|
|
82
|
+
readable only by you; `talon` refuses to use it otherwise.
|
|
83
|
+
- **Standard.** This comes from itch.io through [butler](https://itch.io/docs/butler/).
|
|
84
|
+
|
|
85
|
+
### Pinning an engine to a project
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cd my_game
|
|
89
|
+
talon engine use 7.23-pro # pin in .dragonruby-version and install the engine here
|
|
90
|
+
talon engine use # after cloning: install whatever is pinned
|
|
91
|
+
talon engine current # pinned vs installed
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
DragonRuby expects its engine files next to the game, so `talon engine use` copies the cached engine into the
|
|
95
|
+
project. On btrfs and XFS the files are cloned (reflinked), so each copy takes almost no extra disk space.
|
|
96
|
+
|
|
97
|
+
- **The pin.** `.dragonruby-version` holds only the version and tier (`7.23-pro`), so it works on every OS.
|
|
98
|
+
- **What gets copied.** Games in DragonRuby's layout (`mygame/app/main.rb`) get every engine file except
|
|
99
|
+
`mygame/`. Libraries, whose root is the game directory (`app/main.rb`), get just the `dragonruby` binary and
|
|
100
|
+
`font.ttf`.
|
|
101
|
+
- **Switching versions.** The installed files are listed in `.talon/engine.yml`. Switching engines removes exactly
|
|
102
|
+
those files and never touches `mygame/` or your other files. If an existing file would be overwritten and
|
|
103
|
+
differs from the engine's copy, the install stops until you pass `--force`.
|
|
104
|
+
|
|
105
|
+
## Projects
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
talon new my_game # a game, in DragonRuby's layout (mygame/)
|
|
109
|
+
talon new my-lib --lib # a library: lib/, app/main.rb, test/
|
|
110
|
+
talon new my_game --template talon # a game on the Talon framework (see below)
|
|
111
|
+
talon help new # all options
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`talon new` pins the newest cached engine (or `--engine 7.23-pro`) and installs it. It also:
|
|
115
|
+
|
|
116
|
+
- copies the engine's own sample game into `mygame/`
|
|
117
|
+
- adds a starter test, `mygame/tests/main_tests.rb`
|
|
118
|
+
- fills in `game_metadata.txt`: `gameid` and `gametitle` from the name, and `devid`/`devtitle` from
|
|
119
|
+
`--dev-id`/`--dev-title`, your config, or by asking
|
|
120
|
+
- adds RuboCop and Solargraph config
|
|
121
|
+
- runs `git init` and `bundle install` (`--skip-git`, `--skip-bundle`)
|
|
122
|
+
|
|
123
|
+
It asks whether to commit the engine to git, which DragonRuby recommends for private and commercial games. Pass
|
|
124
|
+
`--commit-engine` or `--no-commit-engine` to skip the question. If you don't commit it, the engine's files go in a
|
|
125
|
+
marked section of `.gitignore`, and anyone who clones the project runs `talon install` to restore it. Libraries
|
|
126
|
+
never commit the engine.
|
|
127
|
+
|
|
128
|
+
`talon doctor` checks that the pinned engine is cached and installed, that dependencies are installed and
|
|
129
|
+
unmodified, and that `game_metadata.txt` is ready to publish.
|
|
130
|
+
|
|
131
|
+
### Running, testing and building
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
talon run # ./dragonruby mygame (or . for libraries); extra args such as --record pass through
|
|
135
|
+
talon test # every *_tests.rb in test/ and tests/ under the game directory
|
|
136
|
+
talon test tests/player_tests.rb --no-headless --timeout 30 --verbose
|
|
137
|
+
talon build # dragonruby-publish --only-package: into builds/, never uploaded
|
|
138
|
+
talon serve # the newest HTML5 build at http://localhost:8080/ (--port)
|
|
139
|
+
talon clean # delete builds/, logs/, tmp/ and errors/
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
DragonRuby's test runner always exits 0, so `talon test` reads its output instead. It prints each file's result
|
|
143
|
+
and the details of any failure, and it exits non-zero if a test fails, a file doesn't load, a file has no tests,
|
|
144
|
+
or a run passes `--timeout` (default 120 seconds).
|
|
145
|
+
|
|
146
|
+
Tests run headless by default, using SDL's dummy video and audio drivers, so no window opens and they work in CI.
|
|
147
|
+
Pass `--no-headless` for tests that need real rendering.
|
|
148
|
+
|
|
149
|
+
## Dependencies
|
|
150
|
+
|
|
151
|
+
DragonRuby doesn't load gems, so libraries are vendored into the game: their files are copied into `vendor/` inside
|
|
152
|
+
the game directory and committed with it.
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
talon add github:asimmons91/talon --path lib # vendor/talon, then prints the require line
|
|
156
|
+
talon add github:someone/tweens --ref v1.2.0 --as tweens
|
|
157
|
+
talon add url:https://example.com/easing.rb
|
|
158
|
+
talon add path:../my_lib/lib # a local library, copied on every install
|
|
159
|
+
talon install # after cloning: the engine, then locked dependencies
|
|
160
|
+
talon update [NAME...] # move to the newest commit of each ref
|
|
161
|
+
talon outdated
|
|
162
|
+
talon remove tweens
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Dependencies are declared in `dependencies.rb` in the game directory (`mygame/` for games). The format is
|
|
166
|
+
compatible with [Foodchain](https://github.com/pvande/foodchain), so existing files keep working:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
github "asimmons91/talon", path: "lib", ref: "main", as: "talon"
|
|
170
|
+
github :asimmons91, :talon, "lib", destination: "vendor/talon" # Foodchain style
|
|
171
|
+
git "https://codeberg.org/someone/lib.git", path: "src", ref: "v2"
|
|
172
|
+
url "https://example.com/easing.rb"
|
|
173
|
+
path "../my_lib/lib"
|
|
174
|
+
|
|
175
|
+
__END__
|
|
176
|
+
|
|
177
|
+
# The lines below pin the versions of your installed dependencies.
|
|
178
|
+
...
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`talon` owns everything below `__END__`. Each dependency gets one tab-separated line with its key, ref,
|
|
182
|
+
destination, locked commit (or a SHA-256 for `url`), and a checksum of the vendored files.
|
|
183
|
+
|
|
184
|
+
- `talon install` reproduces exactly those versions.
|
|
185
|
+
- If you edit a vendored file, `talon` notices and won't overwrite it unless you pass `--force`.
|
|
186
|
+
- GitHub dependencies resolve refs with `git ls-remote` and download one tarball each, so they don't use up the
|
|
187
|
+
GitHub API rate limit. Private repositories need `git:` with an SSH URL instead.
|
|
188
|
+
|
|
189
|
+
## Talon games
|
|
190
|
+
|
|
191
|
+
[Talon](https://github.com/asimmons91/talon) is a scene, game object and component framework for DragonRuby.
|
|
192
|
+
`talon` works with any DragonRuby game; Talon support is opt-in.
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
talon new my_game --template talon # Talon vendored, a Title scene, a Spin component and a test
|
|
196
|
+
talon generate scene MainMenu # app/scenes/main_menu.rb + tests/scenes/main_menu_tests.rb
|
|
197
|
+
talon generate component Health # app/components/health.rb + test
|
|
198
|
+
talon g prefab EnemyShip # app/prefabs/enemy_ship.rb + test (spawn(:ship, EnemyShip))
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
DragonRuby only loads files that are explicitly required, so generators add each new file's `require` to a
|
|
202
|
+
marked section of `app/main.rb`, ordered components, then prefabs, then scenes. If that section is missing, they
|
|
203
|
+
print the line for you to paste.
|
|
204
|
+
|
|
205
|
+
Generators only run in projects whose `dependencies.rb` includes Talon. To use them in an existing game, run
|
|
206
|
+
`talon add github:asimmons91/talon --path lib` first.
|
|
207
|
+
|
|
208
|
+
## Configuration
|
|
209
|
+
|
|
210
|
+
`~/.config/dr_talon/config.yml` (see below for other platforms):
|
|
211
|
+
|
|
212
|
+
```yaml
|
|
213
|
+
default_template: bare # or talon, or lib
|
|
214
|
+
dev_id: my_itchio_username # devid in game_metadata.txt
|
|
215
|
+
dev_title: My Name # devtitle in game_metadata.txt
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Where talon keeps its files
|
|
219
|
+
|
|
220
|
+
| | Linux | macOS | Windows |
|
|
221
|
+
|---|---|---|---|
|
|
222
|
+
| Engines (data) | `$XDG_DATA_HOME/dr_talon` (`~/.local/share/dr_talon`) | `~/Library/Application Support/dr_talon` | `%LOCALAPPDATA%\dr_talon` |
|
|
223
|
+
| Downloads (cache) | `$XDG_CACHE_HOME/dr_talon` (`~/.cache/dr_talon`) | `~/Library/Caches/dr_talon` | `%LOCALAPPDATA%\dr_talon\cache` |
|
|
224
|
+
| Config and login | `$XDG_CONFIG_HOME/dr_talon` (`~/.config/dr_talon`) | `~/Library/Application Support/dr_talon` | `%APPDATA%\dr_talon` |
|
|
225
|
+
|
|
226
|
+
On macOS, explicitly set `XDG_*` variables take precedence. Setting `DR_TALON_HOME` puts everything under that one
|
|
227
|
+
directory (`data/`, `cache/`, `config/`).
|
|
228
|
+
|
|
229
|
+
## Development
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
bin/setup # install dependencies
|
|
233
|
+
bundle exec rake # tests + standardrb
|
|
234
|
+
bundle exec exe/talon # try the CLI from the checkout
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The tests never need a real DragonRuby engine or the network. They build small fake engines, use local git
|
|
238
|
+
repositories in place of GitHub, and run HTTP against a local server. Real DragonRuby output is kept as fixtures in
|
|
239
|
+
`test/fixtures/dragonruby/`. The design and its decisions are in [PLAN.md](PLAN.md).
|
|
240
|
+
|
|
241
|
+
### Releasing
|
|
242
|
+
|
|
243
|
+
Add changes to the `## [Unreleased]` section of `CHANGELOG.md` as you go. To release:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
bundle exec rake release:prepare[minor] # or major, patch, or an explicit version like 1.2.3
|
|
247
|
+
git push origin main v0.2.0 # the command it prints
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`release:prepare` runs the tests, bumps `version.rb` and `Gemfile.lock`, moves the Unreleased notes under a dated
|
|
251
|
+
heading, and commits and tags the release. The first release is `rake release:prepare[0.1.0]`: the current
|
|
252
|
+
version, since it was never tagged.
|
|
253
|
+
|
|
254
|
+
Pushing the tag runs `.github/workflows/release.yml`. It checks that the tag, version and changelog agree, runs
|
|
255
|
+
the tests, publishes the gem to RubyGems.org with [Trusted Publishing](https://guides.rubygems.org/trusted-publishing/),
|
|
256
|
+
and creates the GitHub release from that version's changelog notes.
|
|
257
|
+
|
|
258
|
+
One-time setup: on RubyGems.org, add a trusted publisher for this repository with the workflow `release.yml` and the
|
|
259
|
+
environment `release`. Use a "pending" trusted publisher, since the gem has never been pushed.
|
|
260
|
+
|
|
261
|
+
## Contributing
|
|
262
|
+
|
|
263
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/asimmons91/dr_talon. This project is
|
|
264
|
+
intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the
|
|
265
|
+
[code of conduct](https://github.com/asimmons91/dr_talon/blob/main/CODE_OF_CONDUCT.md).
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
270
|
+
DragonRuby Game Toolkit itself is commercial software under its own license; `talon` doesn't include any part of it.
|
data/Rakefile
ADDED
data/exe/talon
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "thor"
|
|
5
|
+
require "tmpdir"
|
|
6
|
+
|
|
7
|
+
module DrTalon
|
|
8
|
+
class CLI < Thor
|
|
9
|
+
# +talon engine ...+: manage the cache of DragonRuby engines.
|
|
10
|
+
class Engine < Thor
|
|
11
|
+
def self.exit_on_failure?
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
desc "import PATH", "Add a DragonRuby zip (or an extracted engine directory) to the cache"
|
|
16
|
+
long_desc <<~DESC
|
|
17
|
+
Copies a DragonRuby distribution into talon's engine cache. PATH can be the
|
|
18
|
+
zip as downloaded or a directory it was already extracted to.
|
|
19
|
+
|
|
20
|
+
The version is read from CHANGELOG-CURR.txt and the platform from the
|
|
21
|
+
engine directory name. The tier is read from the zip or directory name
|
|
22
|
+
(e.g. dragonruby-pro-linux-amd64.zip); pass --tier when it isn't there.
|
|
23
|
+
|
|
24
|
+
DragonRuby only offers its latest release for download, so import every
|
|
25
|
+
version you may want to go back to.
|
|
26
|
+
DESC
|
|
27
|
+
method_option :tier, enum: DrTalon::Engine::TIERS, desc: "License tier, when the file name doesn't say"
|
|
28
|
+
method_option :platform, desc: "Engine platform, e.g. linux-amd64, when the directory name doesn't say"
|
|
29
|
+
method_option :force, type: :boolean, default: false, desc: "Replace a cached engine with the same id"
|
|
30
|
+
def import(path)
|
|
31
|
+
result = with_errors do
|
|
32
|
+
importer = DrTalon::Engine::Importer.new(store, tier: options[:tier], platform: options[:platform], force: options[:force])
|
|
33
|
+
importer.import(path)
|
|
34
|
+
end
|
|
35
|
+
installation = result.installation
|
|
36
|
+
|
|
37
|
+
case result.status
|
|
38
|
+
when :unchanged then say "#{installation.id} is already cached from the same zip."
|
|
39
|
+
when :replaced then say "Replaced #{installation.id} at #{installation.path}"
|
|
40
|
+
else say "Imported #{installation.id} to #{installation.path}"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
desc "fetch", "Download the latest engine and add it to the cache"
|
|
45
|
+
long_desc <<~DESC
|
|
46
|
+
Downloads the newest DragonRuby release for this machine and imports it.
|
|
47
|
+
|
|
48
|
+
Jam (DragonRuby's free license: Standard's API, web export only) comes
|
|
49
|
+
from the free downloads on DragonRuby's itch.io page; no account needed.
|
|
50
|
+
|
|
51
|
+
Pro and Indie come from dragonruby.org with your account's username and
|
|
52
|
+
password, taken from DRAGONRUBY_USERNAME and DRAGONRUBY_PASSWORD, from
|
|
53
|
+
`talon engine login`, or asked for.
|
|
54
|
+
|
|
55
|
+
Standard comes from itch.io with butler (https://itch.io/docs/butler/),
|
|
56
|
+
after `butler login`.
|
|
57
|
+
|
|
58
|
+
The tier defaults to that of your newest cached engine. DragonRuby only
|
|
59
|
+
offers its latest release, so fetching regularly is how older versions
|
|
60
|
+
stay available in the cache.
|
|
61
|
+
DESC
|
|
62
|
+
method_option :tier, enum: DrTalon::Engine::TIERS, desc: "License tier (default: your newest cached engine's)"
|
|
63
|
+
method_option :keep_zip, type: :boolean, default: false, desc: "Keep the downloaded zip in talon's cache directory"
|
|
64
|
+
def fetch
|
|
65
|
+
tier = options[:tier] || store.latest_native&.tier ||
|
|
66
|
+
raise(Thor::Error, "Pass --tier #{DrTalon::Engine::TIERS.join("|")} (no cached engine to take it from). " \
|
|
67
|
+
"Jam is free and needs no account.")
|
|
68
|
+
|
|
69
|
+
result = with_errors { fetch_and_import(tier) }
|
|
70
|
+
installation = result.installation
|
|
71
|
+
|
|
72
|
+
if result.status == :unchanged
|
|
73
|
+
say "#{installation.id} is already cached; it's the latest #{tier} release."
|
|
74
|
+
else
|
|
75
|
+
say "Fetched #{installation.id} to #{installation.path}", :green
|
|
76
|
+
say "Use it in a project with `talon engine use #{installation.version}-#{installation.tier}`."
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
desc "login", "Save a dragonruby.org login for fetching Pro/Indie engines"
|
|
81
|
+
long_desc <<~DESC
|
|
82
|
+
Asks for your dragonruby.org username and password, checks them against
|
|
83
|
+
the download API, and saves them to credentials.yml in talon's config
|
|
84
|
+
directory, readable only by you (mode 0600). The password is stored in
|
|
85
|
+
plain text there; DRAGONRUBY_USERNAME and DRAGONRUBY_PASSWORD are an
|
|
86
|
+
alternative that stores nothing.
|
|
87
|
+
DESC
|
|
88
|
+
method_option :tier, enum: %w[pro indie], default: "pro", desc: "Tier to check the login against"
|
|
89
|
+
def login
|
|
90
|
+
credentials = ask_credentials
|
|
91
|
+
with_errors { DrTalon::Engine::Fetchers::DragonRubyAPI.new(options[:tier]).verify(credentials) }
|
|
92
|
+
credentials.save(Paths.new.credentials_file)
|
|
93
|
+
say "Saved the login for #{credentials.username} to #{Paths.new.credentials_file} (mode 600)."
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
desc "logout", "Delete the saved dragonruby.org login"
|
|
97
|
+
def logout
|
|
98
|
+
path = Paths.new.credentials_file
|
|
99
|
+
say DrTalon::Credentials.delete(path) ? "Deleted #{path}." : "No saved login at #{path}."
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
desc "list", "List cached engines"
|
|
103
|
+
def list
|
|
104
|
+
installations = with_errors { store.installations }
|
|
105
|
+
|
|
106
|
+
if installations.empty?
|
|
107
|
+
say "No engines cached yet. Add one with `talon engine import <zip>`."
|
|
108
|
+
return
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
pinned = pinned_installation
|
|
112
|
+
rows = installations.map do |installation|
|
|
113
|
+
marker = (installation == pinned) ? "*" : ""
|
|
114
|
+
[marker, installation.id, installation.version, installation.tier, installation.platform, installation.built_on.to_s]
|
|
115
|
+
end
|
|
116
|
+
print_table([["", "ID", "VERSION", "TIER", "PLATFORM", "BUILT"], *rows])
|
|
117
|
+
say "\n* pinned by this project" if pinned
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
desc "which [ENGINE]", "Print the path of a cached engine (default: this project's pin)"
|
|
121
|
+
def which(spec = nil)
|
|
122
|
+
say with_errors { spec ? store.find(spec) : store.find_native(required_pin) }.path
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
desc "use [ENGINE]", "Pin an engine for this project and install it here"
|
|
126
|
+
long_desc <<~DESC
|
|
127
|
+
Writes ENGINE (e.g. 7.23-pro) to .dragonruby-version and copies the cached
|
|
128
|
+
engine into the project, replacing the previously installed one. Without
|
|
129
|
+
ENGINE, installs the version already pinned, e.g. after cloning.
|
|
130
|
+
|
|
131
|
+
Only files talon installed before are removed. Files in mygame/ are never
|
|
132
|
+
touched, and other project files that differ from the engine's stop the
|
|
133
|
+
install unless you pass --force.
|
|
134
|
+
|
|
135
|
+
Run this in a DragonRuby project: a directory with .dragonruby-version,
|
|
136
|
+
mygame/app/main.rb (full layout) or app/main.rb (game-only layout).
|
|
137
|
+
DESC
|
|
138
|
+
method_option :force, type: :boolean, default: false, desc: "Overwrite project files that differ from the engine's"
|
|
139
|
+
def use(spec = nil)
|
|
140
|
+
project = with_errors { Project.locate }
|
|
141
|
+
installation, previous, result = with_errors do
|
|
142
|
+
requested = spec ? DrTalon::Engine::Spec.parse(spec) : required_pin(project)
|
|
143
|
+
installation = store.find_native(requested, default_tier: project.pin&.tier)
|
|
144
|
+
materializer = DrTalon::Engine::Materializer.new(project)
|
|
145
|
+
previous = materializer.manifest&.engine
|
|
146
|
+
result = materializer.install(installation, force: options[:force])
|
|
147
|
+
DrTalon::Engine::GitIgnore.refresh(project.path(".gitignore"), materializer.manifest.files)
|
|
148
|
+
[installation, previous, result]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
repinned = project.pin&.then { |pin| "#{pin.version}-#{pin.tier}" } != "#{installation.version}-#{installation.tier}"
|
|
152
|
+
project.pin!(installation) if repinned
|
|
153
|
+
|
|
154
|
+
details = ["#{result.copied} files"]
|
|
155
|
+
details << "#{result.cloned} as reflinks" if result.cloned.positive?
|
|
156
|
+
if previous == installation.id
|
|
157
|
+
say "Reinstalled #{installation.id} into #{project.root} (#{details.join(", ")})"
|
|
158
|
+
else
|
|
159
|
+
details << "replaced #{previous}" if previous
|
|
160
|
+
say "Installed #{installation.id} into #{project.root} (#{details.join(", ")})"
|
|
161
|
+
end
|
|
162
|
+
say "Pinned #{installation.version}-#{installation.tier} in #{Project::PIN_FILE}" if repinned
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
desc "current", "Show the engine this project pins and the one installed"
|
|
166
|
+
def current
|
|
167
|
+
project = with_errors { Project.locate }
|
|
168
|
+
pin = with_errors { project.pin }
|
|
169
|
+
manifest = with_errors { DrTalon::Engine::Materializer.new(project).manifest }
|
|
170
|
+
|
|
171
|
+
say "Pinned: #{pin || "nothing (no #{Project::PIN_FILE})"}"
|
|
172
|
+
say "Installed: #{manifest&.engine || "nothing"}"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
desc "uninstall ENGINE", "Remove an engine from the cache"
|
|
176
|
+
long_desc <<~DESC
|
|
177
|
+
Deletes a cached engine. DragonRuby only offers its latest release for
|
|
178
|
+
download, so the cache may be your only copy of older versions; you're
|
|
179
|
+
asked to confirm unless you pass --yes.
|
|
180
|
+
DESC
|
|
181
|
+
method_option :yes, type: :boolean, aliases: "-y", default: false, desc: "Don't ask for confirmation"
|
|
182
|
+
def uninstall(spec)
|
|
183
|
+
installation = with_errors { store.find(spec) }
|
|
184
|
+
|
|
185
|
+
unless options[:yes] || yes?("Remove #{installation.id}? The cache may be your only copy of this version. [y/N]")
|
|
186
|
+
say "Kept #{installation.id}."
|
|
187
|
+
return
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
store.remove(installation)
|
|
191
|
+
say "Removed #{installation.id}."
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
no_commands do
|
|
195
|
+
def store
|
|
196
|
+
@store ||= DrTalon::Engine::Store.new(Paths.new.engines_dir)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def required_pin(project = Project.find)
|
|
200
|
+
pin = project&.pin
|
|
201
|
+
return pin if pin
|
|
202
|
+
|
|
203
|
+
raise DrTalon::Error, "No engine given and no #{Project::PIN_FILE} found. Pass one, e.g. `7.23-pro`."
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def pinned_installation
|
|
207
|
+
pin = Project.find&.pin
|
|
208
|
+
pin && store.find_native(pin)
|
|
209
|
+
rescue DrTalon::Error
|
|
210
|
+
nil
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def fetch_and_import(tier)
|
|
214
|
+
fetcher = DrTalon::Engine::Fetchers.for(tier)
|
|
215
|
+
credentials = fetcher.credentials? ? DrTalon::Credentials.lookup || ask_credentials(offer_to_save: true) : nil
|
|
216
|
+
say "Downloading the latest #{tier} engine…" unless $stdout.tty?
|
|
217
|
+
|
|
218
|
+
path = begin
|
|
219
|
+
fetcher.fetch(credentials: credentials) { |received, total| show_progress(received, total) }
|
|
220
|
+
rescue DrTalon::HTTP::Unauthorized
|
|
221
|
+
source = {env: "DRAGONRUBY_USERNAME/DRAGONRUBY_PASSWORD", file: Paths.new.credentials_file, prompt: "what you typed"}
|
|
222
|
+
raise DrTalon::Error, "dragonruby.org rejected the login from #{source.fetch(credentials.source)}. " \
|
|
223
|
+
"Check it, or run `talon engine login`."
|
|
224
|
+
ensure
|
|
225
|
+
finish_progress
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
begin
|
|
229
|
+
DrTalon::Engine::Importer.new(store, tier: tier).import(path)
|
|
230
|
+
ensure
|
|
231
|
+
FileUtils.rm_rf(path) unless options[:keep_zip] && File.file?(path)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def ask_credentials(offer_to_save: false)
|
|
236
|
+
unless $stdin.tty?
|
|
237
|
+
raise Thor::Error, "No dragonruby.org login. Set #{DrTalon::Credentials::ENV_USERNAME} and " \
|
|
238
|
+
"#{DrTalon::Credentials::ENV_PASSWORD}, or run `talon engine login` in a terminal."
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
username = ask("dragonruby.org username:").to_s.strip
|
|
242
|
+
password = ask("dragonruby.org password:", echo: false).to_s
|
|
243
|
+
say ""
|
|
244
|
+
raise Thor::Error, "A username and password are needed." if username.empty? || password.empty?
|
|
245
|
+
|
|
246
|
+
credentials = DrTalon::Credentials.new(username: username, password: password, source: :prompt)
|
|
247
|
+
if offer_to_save && yes?("Save this login to #{Paths.new.credentials_file} (mode 600)? [y/N]")
|
|
248
|
+
credentials.save(Paths.new.credentials_file)
|
|
249
|
+
end
|
|
250
|
+
credentials
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def show_progress(received, total)
|
|
254
|
+
return unless $stdout.tty?
|
|
255
|
+
|
|
256
|
+
@progress_shown = true
|
|
257
|
+
amount = total ? "#{received * 100 / total}% of #{total / 1_048_576} MB" : "#{received / 1_048_576} MB"
|
|
258
|
+
print "\rDownloading… #{amount} "
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def finish_progress
|
|
262
|
+
puts if @progress_shown
|
|
263
|
+
@progress_shown = false
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def with_errors
|
|
267
|
+
yield
|
|
268
|
+
rescue DrTalon::Error => e
|
|
269
|
+
raise Thor::Error, e.message
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|