asgard 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/.github/workflows/deploy-github-pages.yml +52 -0
- data/CHANGELOG.md +56 -3
- data/CLAUDE.md +117 -0
- data/README.md +152 -10
- data/docs/api.md +131 -0
- data/docs/assets/css/custom.css +93 -0
- data/docs/assets/images/asgard.jpg +0 -0
- data/docs/changelog.md +100 -0
- data/docs/dependencies.md +221 -0
- data/docs/environment.md +113 -0
- data/docs/examples.md +140 -0
- data/docs/getting-started.md +180 -0
- data/docs/helpers.md +154 -0
- data/docs/index.md +85 -0
- data/docs/options.md +180 -0
- data/docs/shell.md +208 -0
- data/docs/subcommands.md +181 -0
- data/docs/task-files.md +254 -0
- data/docs/tasks.md +284 -0
- data/docs/variables.md +122 -0
- data/examples/.loki +2 -0
- data/examples/concurrent.loki +58 -0
- data/examples/db_subcommands.loki +74 -0
- data/examples/kitchen_sink.loki +164 -0
- data/examples/server_subcommands.loki +56 -0
- data/lib/asgard/base.rb +95 -30
- data/lib/asgard/shell.rb +3 -1
- data/lib/asgard/tasks.rb +29 -1
- data/lib/asgard/version.rb +1 -1
- data/lib/asgard.rb +11 -6
- data/mkdocs.yml +164 -0
- metadata +29 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 839554cd5b16759477245ef561d2769b6d09ea555aa592c8086c2de6bc54450b
|
|
4
|
+
data.tar.gz: 419b813aaccbd3afbe5bc70be2ada0ba4b99ce3f5fdcc0b5b246b461bc3bd76f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8eb7270b4a6668ea0e111f739de651753e927347dd91d5a85275a6b2e014b01ba4c3fc84261174d97f221bf36ac23a4868f3b56ba3df80041a96bf9fa9115397
|
|
7
|
+
data.tar.gz: 5fb697c0ac1dd087b1d8b521680a7a3ec277b633dfee7dab612dfce6da6ce4a7d1377b916049913f082c2f233567ef3a311cfe60b6dc1945c891ac122b1add43
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Deploy Documentation to GitHub Pages
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- main
|
|
6
|
+
- develop
|
|
7
|
+
paths:
|
|
8
|
+
- "docs/**"
|
|
9
|
+
- "mkdocs.yml"
|
|
10
|
+
- ".github/workflows/deploy-github-pages.yml"
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
pages: write
|
|
16
|
+
id-token: write
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
deploy:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout code
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
with:
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
|
|
27
|
+
- name: Setup Python
|
|
28
|
+
uses: actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: 3.x
|
|
31
|
+
|
|
32
|
+
- name: Install dependencies
|
|
33
|
+
run: |
|
|
34
|
+
pip install mkdocs
|
|
35
|
+
pip install mkdocs-material
|
|
36
|
+
pip install mkdocs-macros-plugin
|
|
37
|
+
pip install mike
|
|
38
|
+
|
|
39
|
+
- name: Configure Git
|
|
40
|
+
run: |
|
|
41
|
+
git config --local user.email "action@github.com"
|
|
42
|
+
git config --local user.name "GitHub Action"
|
|
43
|
+
|
|
44
|
+
- name: Build MkDocs site
|
|
45
|
+
run: mkdocs build
|
|
46
|
+
|
|
47
|
+
- name: Deploy to GitHub Pages
|
|
48
|
+
uses: peaceiris/actions-gh-pages@v4
|
|
49
|
+
with:
|
|
50
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
51
|
+
publish_dir: ./site
|
|
52
|
+
keep_files: true
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Fixed (round 2)
|
|
11
|
+
|
|
12
|
+
- **Subcommand deps not validated at startup** — `run!` only called `Tasks.validate_deps!`, so circular dependencies and undefined dep names in subcommand groups (classes that inherit from `Asgard::Base` or `Tasks`) were silently ignored. `run!` now snapshots `Asgard::Base.subclasses` before loading task files and validates every newly defined subclass alongside `Tasks`.
|
|
13
|
+
- **Parallel dep thread orphaned on exception** — when a parallel dep group contained one fast-failing task and one slow task, `threads.each(&:join)` re-raised the first thread's exception and abandoned the remaining threads. The slow thread continued running unsupervised after the caller saw the exception. The join loop now collects all thread exceptions before re-raising the first, ensuring every thread completes before execution exits the group.
|
|
14
|
+
- **Dep with required arguments gave a cryptic runtime error** — `depends_on :build` where `build(name)` has required parameters caused `Thor::InvocationError` at task invocation time with no indication of where the problem was. `validate_deps!` now checks the arity of every dep task via `instance_method.parameters` and raises `Asgard::Error` at startup with the task name and required argument count.
|
|
15
|
+
- **Orphaned `depends_on` silently discarded** — a `depends_on` declaration at the end of a class body (or `.loki` file) with no following `desc`/`def` left `@_pending_deps` non-empty but was ignored by `validate_deps!` due to an early `return if _deps.empty?` guard. `validate_deps!` now checks `@_pending_deps` before that guard and raises `Asgard::Error` naming the orphaned dependencies.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **Parallel dep race condition** — when two parallel tasks shared a common dependency, the second thread could start before the shared dep finished executing. `_ran_tasks` (a single Set) has been replaced with `_running` / `_done` Sets and a per-task `ConditionVariable`. Threads that arrive at an already-running dep now wait for its completion rather than skipping it; the `ensure` block broadcasts completion whether the task succeeds or raises.
|
|
20
|
+
- **`depends_on` silently dropped before `var` or `no_commands`** — Thor uses an integer counter for `@no_commands` that resets to `0` (truthy in Ruby) after any `no_commands` block. The `method_added` guard now checks `@usage` instead: pending deps are only consumed when a command-defining method is added (one preceded by `desc`), so `var` declarations and `no_commands` helpers placed between `depends_on` and `def` no longer silently steal the dependency.
|
|
21
|
+
- **`shebang` ignored its `silent:` keyword argument** — the parameter was accepted but never referenced; the script body is now echoed to stdout unless `silent: true` is passed, matching the behavior of `sh`.
|
|
22
|
+
- **`var` lambdas re-evaluated on every access** — the accessor method now caches its result in a per-instance variable on first call. Lambdas used for computed values (e.g. `` -> { `git describe --tags`.strip } ``) now run exactly once per instance.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **`validate_deps!` detects undefined dependency names** — `depends_on :nonexistent` previously passed validation silently and produced no error at runtime. `validate_deps!` now raises `Asgard::Error` listing every dep name that does not correspond to a defined task. `run!` catches this alongside `CircularDependencyError` and exits with a clean message.
|
|
27
|
+
|
|
28
|
+
## [0.2.0] - 2026-05-29
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- `*.loki` files are no longer auto-loaded by default. Pass `--auto-load` to `asgard` to load all `*.loki` files from the project root alphabetically before `.loki`. This is a breaking change for projects using the multi-file layout.
|
|
33
|
+
- Added `--auto-load` as a built-in CLI flag in `Tasks`, visible in `asgard help`
|
|
34
|
+
|
|
35
|
+
## [0.1.2] - 2026-05-29
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- `--version` built-in CLI flag — prints `Asgard::VERSION` and exits; implemented as a `_`-prefixed method in `Tasks` per the gem-owned naming convention
|
|
40
|
+
- `--debug` and `--verbose` built-in `class_option` declarations on `Tasks` — set `$DEBUG`/`$VERBOSE` before any task runs via the `invoke_command` hook in `Asgard::Base`
|
|
41
|
+
- `debug?` and `verbose?` private predicate helpers on `Tasks` — thin wrappers around `$DEBUG` and `$VERBOSE` for use inside task bodies
|
|
42
|
+
- `_` prefix convention for gem-owned methods in `Tasks` — built-in methods use `_` prefix to distinguish them from user-defined tasks
|
|
43
|
+
- `run!` guards against direct invocation of `_`-prefixed commands with a clean error message and exit 1
|
|
44
|
+
- `examples/` directory with working `.loki` files:
|
|
45
|
+
- `kitchen_sink.loki` — demonstrates the full Thor DSL (all option types, `long_desc`, `class_option`, `default_task`, `map`, `depends_on`, `var`, `no_commands`, `private`)
|
|
46
|
+
- `server_subcommands.loki` — subcommand group for server management
|
|
47
|
+
- `db_subcommands.loki` — subcommand group for database management with `depends_on` chaining
|
|
48
|
+
- README sections: Helper methods, Subcommands, Thor wrapper callout
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- Replaced `warn`/`exit 1` with `abort` throughout `run!` — `Kernel#warn` is silenced when `$VERBOSE = nil`, which is the default in Ruby 4.0; `abort` writes to `$stderr` regardless
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
|
|
56
|
+
- `--debug` and `--verbose` promoted from mapped tasks to `class_option` — they now work as modifiers alongside other commands (e.g. `asgard build --debug`) rather than as standalone commands
|
|
57
|
+
- Removed all references to `just` task runner and `recipe` terminology; Asgard uses "task" throughout
|
|
58
|
+
- `depends_on` parameter renamed from `*recipes` to `*tasks` for consistency
|
|
59
|
+
|
|
10
60
|
## [0.1.1] - 2026-05-28
|
|
11
61
|
### Added
|
|
12
62
|
|
|
@@ -39,8 +89,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
39
89
|
### Added
|
|
40
90
|
|
|
41
91
|
- `Asgard::Base` — Thor subclass providing the task DSL
|
|
42
|
-
- `depends_on` — declare
|
|
43
|
-
- `var` — declare static or lazy-evaluated variables available to all
|
|
92
|
+
- `depends_on` — declare task dependencies; dependencies run at most once per invocation
|
|
93
|
+
- `var` — declare static or lazy-evaluated variables available to all tasks
|
|
44
94
|
- `import` — flat-merge a task module into the current class
|
|
45
95
|
- `dotenv` — load a `.env` file into the environment
|
|
46
96
|
- `sh` — run a shell command or multiline heredoc script; exits with the command's status on failure
|
|
@@ -52,5 +102,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
52
102
|
- 100% test coverage enforced via SimpleCov (95% minimum threshold)
|
|
53
103
|
- Quality task in `.loki` runs flog after tests
|
|
54
104
|
|
|
55
|
-
[Unreleased]: https://github.com/MadBomber/asgard/compare/v0.
|
|
105
|
+
[Unreleased]: https://github.com/MadBomber/asgard/compare/v0.2.0...HEAD
|
|
106
|
+
[0.2.0]: https://github.com/MadBomber/asgard/compare/v0.1.2...v0.2.0
|
|
107
|
+
[0.1.2]: https://github.com/MadBomber/asgard/compare/v0.1.1...v0.1.2
|
|
108
|
+
[0.1.1]: https://github.com/MadBomber/asgard/compare/v0.1.0...v0.1.1
|
|
56
109
|
[0.1.0]: https://github.com/MadBomber/asgard/releases/tag/v0.1.0
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What Asgard Is
|
|
6
|
+
|
|
7
|
+
Asgard is a Ruby task runner. Users define tasks in `.loki` files by reopening the pre-defined `Tasks` class. The name is intentional: Thor handles the CLI, Asgard is where tasks live, and Loki (the `.loki` file) holds all the tricks.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bundle install
|
|
13
|
+
bundle exec rake test # run tests (enforces 95% SimpleCov coverage)
|
|
14
|
+
bundle exec rake quality # test + flog complexity check
|
|
15
|
+
bundle exec rake build # build .gem into pkg/
|
|
16
|
+
bundle exec rake install # install locally
|
|
17
|
+
|
|
18
|
+
# or use the gem's own .loki file:
|
|
19
|
+
asgard test
|
|
20
|
+
asgard quality
|
|
21
|
+
asgard release
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Single test: `ruby -Ilib:test test/test_asgard.rb`
|
|
25
|
+
|
|
26
|
+
## Architecture
|
|
27
|
+
|
|
28
|
+
### Entry Point Flow
|
|
29
|
+
|
|
30
|
+
`bin/asgard` → `Asgard.run!(ARGV)` (`lib/asgard.rb`):
|
|
31
|
+
1. Walk CWD + ancestors for `.loki` (marker only, not a task file)
|
|
32
|
+
2. If `--auto-load` is in argv: glob + sort `*.loki` files and load each alphabetically
|
|
33
|
+
3. Load `.loki` itself last
|
|
34
|
+
4. `Tasks.validate_deps!` — build full dep graph, raise `CircularDependencyError` if cyclic
|
|
35
|
+
5. `Tasks._reset_ran!` — clear execution tracking
|
|
36
|
+
6. `Tasks.start(argv)` — Thor dispatches the command
|
|
37
|
+
|
|
38
|
+
### Core Classes
|
|
39
|
+
|
|
40
|
+
| File | Role |
|
|
41
|
+
|------|------|
|
|
42
|
+
| `lib/asgard/base.rb` | DSL engine; inherits Thor, includes Shell |
|
|
43
|
+
| `lib/asgard/shell.rb` | `sh` / `shebang` helpers |
|
|
44
|
+
| `lib/asgard/tasks.rb` | `class Tasks < Asgard::Base` — the convention class users reopen; also holds gem-owned built-in tasks |
|
|
45
|
+
|
|
46
|
+
### Naming Convention for Gem-Owned Methods
|
|
47
|
+
|
|
48
|
+
Any task or method defined by Asgard itself inside `Tasks` (i.e. not by the user's `.loki` files) must be prefixed with `_`. This distinguishes built-in gem behavior from user-defined tasks and prevents naming collisions.
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# lib/asgard/tasks.rb — gem-owned built-ins use _ prefix
|
|
52
|
+
class Tasks < Asgard::Base
|
|
53
|
+
desc "--version", "Show version"
|
|
54
|
+
map "--version" => :_version
|
|
55
|
+
def _version
|
|
56
|
+
puts Asgard::VERSION
|
|
57
|
+
exit
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`method_added` in `Base` already skips `_`-prefixed methods when attaching dependency metadata, so built-ins are naturally excluded from the dependency graph.
|
|
63
|
+
|
|
64
|
+
Do not define `_`-prefixed methods in user `.loki` files — that namespace is reserved for the gem.
|
|
65
|
+
|
|
66
|
+
### DSL Mechanics (`lib/asgard/base.rb`)
|
|
67
|
+
|
|
68
|
+
**`depends_on`** stores stages in `@_pending_deps`. On `method_added`, those stages are popped and stored in `@_deps[method_name]`. Bare symbols are sequential stages; arrays within a `depends_on` call are parallel stages:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
depends_on :a, [:b, :c], :d # stages: [[:a], [:b, :c], [:d]]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**`var`** stores values or lambdas in `@_vars` and creates an instance method that evaluates the lambda once on first access.
|
|
75
|
+
|
|
76
|
+
**`invoke_command`** (Thor dispatch hook):
|
|
77
|
+
1. Atomically check `@_ran_tasks` Set (with `@_ran_mutex`); return early if already run
|
|
78
|
+
2. Resolve `@_deps` stages → `_build_dep_graph` → `Dagwood::DependencyGraph#parallel_order`
|
|
79
|
+
3. For each parallel group: spawn one thread per task, join; single-task groups run inline
|
|
80
|
+
4. Execute the target task
|
|
81
|
+
|
|
82
|
+
**`_build_dep_graph(stages)`** converts stages to a DAG hash:
|
|
83
|
+
- `[[:a], [:b, :c], [:d]]` → `{ a: [], b: [:a], c: [:a], d: [:b, :c] }`
|
|
84
|
+
|
|
85
|
+
### Dependency Resolution
|
|
86
|
+
|
|
87
|
+
Dagwood topologically sorts the DAG and returns parallel groups. The thread-safe deduplication (`_ran_tasks` Set + Mutex) ensures each task runs exactly once even when multiple tasks share a common dependency.
|
|
88
|
+
|
|
89
|
+
### Shell Helpers
|
|
90
|
+
|
|
91
|
+
- `sh(script, silent: false)` — single-line strings use `system(script)`; multi-line strings pipe through `bash -c`; exits with the command's status on failure
|
|
92
|
+
- `shebang(interpreter, script)` — writes script to a tempfile and executes with the named interpreter (`:python3`, `:node`, `:ruby`, `:perl`, `:bash`, etc.)
|
|
93
|
+
|
|
94
|
+
## Testing
|
|
95
|
+
|
|
96
|
+
All tests are in `test/test_asgard.rb` (one file, ~11 named classes). SimpleCov minimum is 95%; the Rakefile configures this with a prelude that loads coverage before the library.
|
|
97
|
+
|
|
98
|
+
Key test patterns: tests frequently subclass `Asgard::Base` directly (not `Tasks`) to test the engine in isolation, and use `capture_io` for output assertions.
|
|
99
|
+
|
|
100
|
+
## The `.loki` Format
|
|
101
|
+
|
|
102
|
+
A `.loki` file is plain Ruby that reopens `Tasks`:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
class Tasks
|
|
106
|
+
var :gem_name, "asgard"
|
|
107
|
+
|
|
108
|
+
desc "test", "Run tests"
|
|
109
|
+
def test = sh "bundle exec rake test"
|
|
110
|
+
|
|
111
|
+
depends_on :test
|
|
112
|
+
desc "release", "Build and release"
|
|
113
|
+
def release = sh "bundle exec rake release"
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
By default, only `.loki` is loaded. Pass `--auto-load` to `asgard` to also load all `*.loki` files in the same directory alphabetically before `.loki` is loaded. The bare `.loki` file serves as the project root marker — its content is always loaded last.
|
data/README.md
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
# Asgard
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
> [!INFO]
|
|
4
|
+
> See the [CHANGELOG](CHANGELOG.md) for the latest changes. The [examples directory](examples/) contains working `.loki` files demonstrating the full feature set.
|
|
5
|
+
|
|
6
|
+
<br>
|
|
7
|
+
<table>
|
|
8
|
+
<tr>
|
|
9
|
+
<td width="40%" align="center" valign="top">
|
|
10
|
+
<img src="docs/assets/images/asgard.jpg" alt="Asgard"><br>
|
|
11
|
+
<em>"Loki writes the tricks. Asgard runs them."</em>
|
|
12
|
+
</td>
|
|
13
|
+
<td width="60%" valign="top">
|
|
14
|
+
<strong>Key Features</strong><br>
|
|
15
|
+
|
|
16
|
+
- <strong>Thor-Powered CLI</strong> — every Thor DSL feature available inside <code>.loki</code> task files<br>
|
|
17
|
+
- <strong>Task Dependencies</strong> — sequential, parallel, and mixed dependency graphs via <code>depends_on</code><br>
|
|
18
|
+
- <strong>Concurrent Execution</strong> — parallel task groups run in native Ruby threads<br>
|
|
19
|
+
- <strong>Subcommands</strong> — group related tasks under a named namespace<br>
|
|
20
|
+
- <strong>Variables</strong> — static values and lazy-evaluated lambdas via <code>var</code><br>
|
|
21
|
+
- <strong>Shell Helpers</strong> — <code>sh</code> for any shell command or heredoc; <code>shebang</code> for polyglot scripts<br>
|
|
22
|
+
- <strong>Dotenv Support</strong> — load <code>.env</code> files into the environment with <code>dotenv</code><br>
|
|
23
|
+
- <strong>Auto-Discovery</strong> — <code>.loki</code> root marker searched from CWD upward through parent directories<br>
|
|
24
|
+
- <strong>Multi-File Tasks</strong> — split tasks across <code>*.loki</code> files, loaded on demand with <code>--auto-load</code><br>
|
|
25
|
+
- <strong>Built-in Flags</strong> — <code>--version</code>, <code>--debug</code>, and <code>--verbose</code> available on every task<br>
|
|
26
|
+
</td>
|
|
27
|
+
</tr>
|
|
28
|
+
</table>
|
|
29
|
+
|
|
30
|
+
<p>Asgard is a <a href="https://github.com/rails/thor">Thor</a>-based task runner for Ruby projects. Define tasks in <code>.loki</code> files, declare dependencies between them, and let Asgard handle ordering and concurrent execution. Anything Thor can do — subcommands, typed options, argument validation — is available inside a <code>.loki</code> file.</p>
|
|
6
31
|
|
|
7
32
|
## Installation
|
|
8
33
|
|
|
@@ -53,7 +78,7 @@ asgard hello Alice
|
|
|
53
78
|
|
|
54
79
|
### A task with a formal argument declaration
|
|
55
80
|
|
|
56
|
-
Use `argument` for richer metadata — type checking, enums, and help text:
|
|
81
|
+
Use `argument` for richer metadata — type checking, enums, and help text. **Warning: `argument` is a class-level declaration that applies to every task in the class**, not just the one below it. It is best suited for single-command CLIs or when every task genuinely shares the same positional input. In multi-task files, prefer method signature parameters instead.
|
|
57
82
|
|
|
58
83
|
```ruby
|
|
59
84
|
class Tasks
|
|
@@ -114,7 +139,7 @@ end
|
|
|
114
139
|
|
|
115
140
|
`depends_on` declares what must run before a task. Each dependency runs at most once per `asgard` invocation regardless of how many tasks declare it. Circular dependencies are caught at startup.
|
|
116
141
|
|
|
117
|
-
`desc` and `depends_on` are independent — either can come first, both must appear before `def`.
|
|
142
|
+
`desc` and `depends_on` are independent — either can come first, both must appear before `def`. `var` declarations between `depends_on` and `def` are safe and do not consume the pending dependency.
|
|
118
143
|
|
|
119
144
|
### Sequential dependencies
|
|
120
145
|
|
|
@@ -213,6 +238,61 @@ end
|
|
|
213
238
|
|
|
214
239
|
---
|
|
215
240
|
|
|
241
|
+
## Helper methods
|
|
242
|
+
|
|
243
|
+
Private methods are callable from any task in the same class but are never registered as commands — they won't appear in `--help` output and can't be invoked from the CLI.
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
class Tasks
|
|
247
|
+
desc "build", "Compile and package"
|
|
248
|
+
def build
|
|
249
|
+
compile("src")
|
|
250
|
+
package(version)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
desc "release", "Build and publish"
|
|
254
|
+
def release
|
|
255
|
+
build
|
|
256
|
+
sh "gem push pkg/myapp-#{version}.gem"
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
private
|
|
260
|
+
|
|
261
|
+
def compile(dir)
|
|
262
|
+
sh "gcc -O2 -o bin/myapp #{dir}/*.c"
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def package(ver)
|
|
266
|
+
sh "tar czf pkg/myapp-#{ver}.tar.gz bin/"
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Helpers can also be shared across multiple `.loki` files by extracting them into a plain Ruby file and loading it explicitly:
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
# shared/helpers.rb
|
|
275
|
+
module BuildHelpers
|
|
276
|
+
private
|
|
277
|
+
|
|
278
|
+
def compile(dir)
|
|
279
|
+
sh "gcc -O2 -o bin/myapp #{dir}/*.c"
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# .loki
|
|
284
|
+
require_relative "shared/helpers"
|
|
285
|
+
|
|
286
|
+
class Tasks
|
|
287
|
+
include BuildHelpers
|
|
288
|
+
|
|
289
|
+
desc "build", "Compile the project"
|
|
290
|
+
def build = compile("src")
|
|
291
|
+
end
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
216
296
|
## Options shared across all tasks
|
|
217
297
|
|
|
218
298
|
`class_option` defines an option available to every task in the class:
|
|
@@ -271,10 +351,14 @@ end
|
|
|
271
351
|
|
|
272
352
|
Supported interpreters: `:python3`, `:python`, `:node`, `:ruby`, `:perl`, `:bash`, `:sh`. Any other symbol is passed directly to `system` with a `.tmp` extension.
|
|
273
353
|
|
|
274
|
-
Pass `silent: true` to suppress the
|
|
354
|
+
Pass `silent: true` to both `sh` and `shebang` to suppress the script echo:
|
|
275
355
|
|
|
276
356
|
```ruby
|
|
277
|
-
def build
|
|
357
|
+
def build = sh "rake build", silent: true
|
|
358
|
+
def analyze = shebang :python3, <<~PY, silent: true
|
|
359
|
+
import json
|
|
360
|
+
print(json.load(open("data.json")))
|
|
361
|
+
PY
|
|
278
362
|
```
|
|
279
363
|
|
|
280
364
|
---
|
|
@@ -312,6 +396,64 @@ end
|
|
|
312
396
|
|
|
313
397
|
---
|
|
314
398
|
|
|
399
|
+
## Subcommands
|
|
400
|
+
|
|
401
|
+
Group related tasks under a common name using Thor's `subcommand` method. Define a subcommand class that inherits from `Tasks`, then register it with a name and description.
|
|
402
|
+
|
|
403
|
+
```ruby
|
|
404
|
+
class DeployCommands < Tasks
|
|
405
|
+
desc "staging", "Deploy to staging"
|
|
406
|
+
def staging = sh "cap staging deploy"
|
|
407
|
+
|
|
408
|
+
desc "production", "Deploy to production"
|
|
409
|
+
def production = sh "cap production deploy"
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
class Tasks
|
|
413
|
+
desc "deploy SUBCOMMAND", "Deploy the application"
|
|
414
|
+
subcommand "deploy", DeployCommands
|
|
415
|
+
end
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
asgard deploy # shows deploy subcommand help
|
|
420
|
+
asgard deploy staging
|
|
421
|
+
asgard deploy production
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Subcommand tasks have all the same access to helper methods like `sh`, `shebang`, `depends_on`, `var`, and the built-in `--debug`/`--verbose` class options as normal tasks.
|
|
425
|
+
|
|
426
|
+
`depends_on` only works within a subcommand group exactly as it does at the top level:
|
|
427
|
+
|
|
428
|
+
```ruby
|
|
429
|
+
class DBCommands < Tasks
|
|
430
|
+
desc "migrate", "Run pending migrations"
|
|
431
|
+
def migrate = sh "rails db:migrate"
|
|
432
|
+
|
|
433
|
+
desc "seed", "Load seed data"
|
|
434
|
+
def seed = sh "rails db:seed"
|
|
435
|
+
|
|
436
|
+
depends_on :migrate, :seed
|
|
437
|
+
desc "reset", "Migrate then seed"
|
|
438
|
+
def reset = puts "Done."
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
class Tasks
|
|
442
|
+
desc "db SUBCOMMAND", "Manage the database"
|
|
443
|
+
subcommand "db", DBCommands
|
|
444
|
+
end
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
asgard db reset # migrate → seed → reset
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Each subcommand group can have its own `desc`, `long_desc`, `option`, `class_option`, and `map` declarations, all scoped to that group.
|
|
452
|
+
|
|
453
|
+
See [`examples/server_subcommands.loki`](examples/server_subcommands.loki) and [`examples/db_subcommands.loki`](examples/db_subcommands.loki) for full working examples.
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
315
457
|
## `method_option` types reference
|
|
316
458
|
|
|
317
459
|
| Type | CLI example | Ruby value |
|
|
@@ -328,7 +470,7 @@ Common `method_option` keys: `aliases`, `type`, `default`, `required`, `desc`, `
|
|
|
328
470
|
|
|
329
471
|
## Task files
|
|
330
472
|
|
|
331
|
-
Asgard searches the current directory and its ancestors for a `.loki` file. That file marks the project root.
|
|
473
|
+
Asgard searches the current directory and its ancestors for a `.loki` file. That file marks the project root. `*.loki` files in the same directory are loaded only when `asgard` is invoked with `--auto-load`.
|
|
332
474
|
|
|
333
475
|
### Single file
|
|
334
476
|
|
|
@@ -399,9 +541,9 @@ end
|
|
|
399
541
|
|---|---|
|
|
400
542
|
| `Asgard.run!(argv)` | Entry point — finds `.loki`, loads task files, starts CLI |
|
|
401
543
|
| `Asgard.find_task_file` | Returns path to `.loki` searching from CWD upward, or nil |
|
|
402
|
-
| `Asgard.load_loki(dir)` | Loads all `*.loki` files in dir alphabetically |
|
|
544
|
+
| `Asgard.load_loki(dir)` | Loads all `*.loki` files in dir alphabetically — called by `run!` only when `--auto-load` is passed |
|
|
403
545
|
|
|
404
|
-
`run!` handles its own errors — a missing `.loki
|
|
546
|
+
`run!` handles its own errors — a missing `.loki`, a circular dependency, or a `depends_on` that names a task that doesn't exist all produce a clean one-line message and exit 1.
|
|
405
547
|
|
|
406
548
|
---
|
|
407
549
|
|
data/docs/api.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
This page documents the public Ruby API for the Asgard gem. Most users interact with Asgard through the CLI and the task DSL — this page is primarily useful when integrating Asgard into tooling or extending it programmatically.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## `Asgard` Module Methods
|
|
8
|
+
|
|
9
|
+
These class methods are defined on the `Asgard` module itself.
|
|
10
|
+
|
|
11
|
+
| Method | Signature | Description |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `run!` | `Asgard.run!(argv)` | Main entry point. Finds `.loki`, loads all task files, validates the dependency graph, and dispatches via Thor. Handles its own errors: missing `.loki` and circular dependencies both produce a clean one-line message and `exit 1`. |
|
|
14
|
+
| `find_task_file` | `Asgard.find_task_file → String, nil` | Searches `Dir.pwd` and each ancestor directory for a `.loki` file. Returns the absolute path string of the first match, or `nil` if none is found. |
|
|
15
|
+
| `load_loki` | `Asgard.load_loki(dir)` | Loads all `*.loki` files in `dir` alphabetically, excluding `.loki` itself. Called by `run!` only when `--auto-load` is present in `argv`. |
|
|
16
|
+
|
|
17
|
+
### `run!` Details
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
Asgard.run!(ARGV)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`run!` guards against direct invocation of `_`-prefixed commands before any files are loaded:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
abort "asgard: unknown command '#{argv.first}'" if argv.first&.start_with?("_")
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
After loading task files, it calls `Tasks.validate_deps!` (circular dependency check) and `Tasks._reset_ran!` (clears per-invocation deduplication state) before starting Thor.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## `Asgard::Base` DSL Class Methods
|
|
34
|
+
|
|
35
|
+
`Asgard::Base` is a `Thor` subclass that provides the task DSL. It is the superclass of `Tasks`. All DSL methods are class methods (called in the class body).
|
|
36
|
+
|
|
37
|
+
| Method | Signature | Description |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `depends_on` | `depends_on(*tasks)` | Declare prerequisites for the next `def`. Bare symbols run sequentially; arrays within the splat run as a parallel group. |
|
|
40
|
+
| `var` | `var(name, value = nil, &block)` | Declare a named variable. If `value` responds to `call` (lambda/proc) or a block is given, the value is computed lazily on first access. Accessible in task bodies as a method. |
|
|
41
|
+
| `import` | `import(mod)` | Include a module into the current class (thin alias for `include`). |
|
|
42
|
+
| `dotenv` | `dotenv(path = ".env")` | Load the specified `.env` file into `ENV` using the dotenv gem. Silently skipped if the file does not exist. Called at class-load time. |
|
|
43
|
+
| `sh` | `sh(script, silent: false)` | Instance method. Run a shell command or multiline heredoc. Single-line → `system(script)`; multiline → `system("bash", "-c", script)`. Exits with the command's status on failure. |
|
|
44
|
+
| `shebang` | `shebang(interpreter, script, silent: false)` | Instance method. Write `script` to a tempfile and execute it with `interpreter`. See the [Shell Helpers](shell.md) page for the full interpreter table. |
|
|
45
|
+
| `validate_deps!` | `Tasks.validate_deps!` | Build and topologically sort the full dependency graph using Dagwood. Raises `Asgard::CircularDependencyError` on cycles. Called by `run!` at startup. |
|
|
46
|
+
| `_reset_ran!` | `Tasks._reset_ran!` | Clear the per-invocation task deduplication set. Called by `run!` before dispatching. Thread-safe via Mutex. |
|
|
47
|
+
|
|
48
|
+
### `depends_on` Argument Shapes
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
depends_on :build # single sequential dep
|
|
52
|
+
depends_on :clean, :build # two sequential deps
|
|
53
|
+
depends_on [:lint, :typecheck] # lint and typecheck run in parallel
|
|
54
|
+
depends_on :setup, [:lint, :build], :test # setup, then lint+build concurrently, then test
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## `Tasks` Built-ins
|
|
60
|
+
|
|
61
|
+
`Tasks` is pre-defined by the gem as `class Tasks < Asgard::Base`. It adds the following:
|
|
62
|
+
|
|
63
|
+
| Item | Type | Description |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `class_option :debug` | class option | `--debug` flag. Sets `$DEBUG = true` before any task runs. Boolean, default `false`. |
|
|
66
|
+
| `class_option :verbose` | class option | `--verbose` flag. Sets `$VERBOSE = true` before any task runs. Boolean, default `false`. |
|
|
67
|
+
| `_version` | private task method | Implements `--version`. Prints `Asgard::VERSION` and exits. Registered via `map "--version" => :_version`. Uses `_` prefix convention. |
|
|
68
|
+
| `debug?` | private instance method | Returns `$DEBUG`. Available in all task bodies and subcommand classes that inherit from `Tasks`. |
|
|
69
|
+
| `verbose?` | private instance method | Returns `$VERBOSE`. Available in all task bodies and subcommand classes that inherit from `Tasks`. |
|
|
70
|
+
| `--auto-load` | CLI flag (consumed by `run!`) | Triggers loading of all `*.loki` files before the main `.loki` and the requested task. Consumed by `run!` before Thor dispatch. |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## `Asgard::Base` Internal Class Methods
|
|
75
|
+
|
|
76
|
+
These are implementation details exposed for extensibility. Prefer the DSL methods above in normal use.
|
|
77
|
+
|
|
78
|
+
| Method | Description |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `_deps` | Hash mapping task name symbols to their stage arrays. Set by `depends_on` + `method_added`. |
|
|
81
|
+
| `_vars` | Hash mapping var name symbols to their static values or callables. |
|
|
82
|
+
| `_ran_tasks` | `Set` of task name symbols that have already run in the current invocation. |
|
|
83
|
+
| `_ran_mutex` | `Mutex` protecting `_ran_tasks` for thread-safe deduplication. |
|
|
84
|
+
| `_build_dep_graph(stages)` | Translates the stage array (from `_deps`) into a Dagwood-compatible hash. |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## `invoke_command` Hook
|
|
89
|
+
|
|
90
|
+
`Asgard::Base` overrides Thor's `invoke_command` to implement dependency resolution and deduplication:
|
|
91
|
+
|
|
92
|
+
1. Sets `$DEBUG` / `$VERBOSE` from `options` if the corresponding flags are present.
|
|
93
|
+
2. Checks `_ran_tasks` — skips if this task has already run.
|
|
94
|
+
3. Marks the task as ran.
|
|
95
|
+
4. Resolves dependency stages from `_deps`, builds the Dagwood graph, and executes groups (parallel groups in threads, sequential groups one at a time).
|
|
96
|
+
5. Calls `command.run(self, *args)` to execute the task itself.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Error Classes
|
|
101
|
+
|
|
102
|
+
| Class | Superclass | Description |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `Asgard::Error` | `StandardError` | Base error class for all Asgard errors. |
|
|
105
|
+
| `Asgard::CircularDependencyError` | `Asgard::Error` | Raised by `validate_deps!` when a cycle is detected in the dependency graph. `run!` catches this and calls `abort` with a clean message. |
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
begin
|
|
109
|
+
Asgard.run!(ARGV)
|
|
110
|
+
rescue Asgard::CircularDependencyError => e
|
|
111
|
+
# This is already handled inside run! — you only need this
|
|
112
|
+
# if you call validate_deps! directly in your own tooling.
|
|
113
|
+
abort "circular dependency: #{e.message}"
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Dependencies
|
|
120
|
+
|
|
121
|
+
| Gem | Version | Purpose |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| [thor](https://github.com/rails/thor) | `~> 1.0` | CLI framework; provides the full task DSL |
|
|
124
|
+
| [dagwood](https://rubygems.org/gems/dagwood) | `~> 1.0` | DAG library for dependency graph resolution and topological sort |
|
|
125
|
+
| [dotenv](https://github.com/bkeepers/dotenv) | `~> 3.0` | `.env` file loading |
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Ruby Version Requirement
|
|
130
|
+
|
|
131
|
+
Asgard requires **Ruby >= 3.2.0**.
|