asgard 0.1.0 → 0.1.1
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/.loki +4 -2
- data/CHANGELOG.md +28 -1
- data/README.md +304 -140
- data/bin/asgard +1 -20
- data/lib/asgard/base.rb +4 -4
- data/lib/asgard/tasks.rb +6 -0
- data/lib/asgard/version.rb +1 -1
- data/lib/asgard.rb +26 -13
- metadata +7 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7920fc28de5f8644ccc7934f576142d433de3fb5af1ac21b28cbd350d930af66
|
|
4
|
+
data.tar.gz: e651f2956eb2f9fc076548fc04cae92e6835fb508b5960eeda952929d9197253
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 19e473102e5850db42c777b9dd824765b9e4a187f1de6f009d80ae85fa1d6992984587a7ca4e9dc40cb301aadc11093826f913d3c5c7dac8e17a47c47f7cdefe
|
|
7
|
+
data.tar.gz: '09c0f72476c8690f055e6aeb2b052449a4eb9fd415bff06a82e4cd5dc4255cfb8709721a76ae347226b31b8dafcbd3e63a439fcd6f249975f134156f578edb1f'
|
data/.loki
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Asgard gem's own task file.
|
|
3
|
+
# Task is pre-defined by the gem — just reopen it to add tasks.
|
|
2
4
|
|
|
3
|
-
class Tasks
|
|
5
|
+
class Tasks
|
|
4
6
|
var :gem_name, "asgard"
|
|
5
7
|
var :version, -> { Asgard::VERSION }
|
|
6
8
|
|
data/CHANGELOG.md
CHANGED
|
@@ -7,12 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.1] - 2026-05-28
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Parallel dependency execution — wrap deps in an array to run them concurrently:
|
|
14
|
+
`depends_on [:build, :lint]` or `depends_on :setup, [:build, :lint], :deploy`
|
|
15
|
+
- `Asgard.run!(argv)` — single entry point encapsulating find, load, validate, and start
|
|
16
|
+
- `Asgard.load_loki(dir)` — auto-loads all `*.loki` files in a directory alphabetically
|
|
17
|
+
- `Tasks` class pre-defined by the gem (`class Tasks < Asgard::Base`) — task files reopen it without restating the superclass
|
|
18
|
+
- `lib/asgard/tasks.rb` — ships the pre-defined `Tasks` class
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Replaced `SimpleFlow` dependency with `Dagwood` — purpose-built DAG library with no extra dependencies and no Ruby 4 compatibility issues
|
|
23
|
+
- `bin/asgard` simplified to two lines: `require "asgard"` + `Asgard.run!(ARGV)`
|
|
24
|
+
- Task file convention: `.loki` is the project root marker and entry point; `*.loki` files each reopen `class Tasks` and are auto-loaded before `.loki`
|
|
25
|
+
- `Asgard.find_task_files` renamed to `Asgard.find_task_file` (singular — only `.loki` is the entry point)
|
|
26
|
+
- `depends_on` now accepts mixed sequential/parallel stages; bare symbols run sequentially, arrays within the splat run in parallel
|
|
27
|
+
- `run!` handles its own errors — missing `.loki` and circular dependencies produce a clean one-line message and exit 1 rather than a backtrace
|
|
28
|
+
- Thread-safe dep deduplication via class-level `_ran_tasks` Set + Mutex replaces Thor's `@_invocations`
|
|
29
|
+
- Removed `import` macro — task files use Ruby class reopening instead of modules
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
|
|
33
|
+
- `SimpleFlow` dependency (replaced by `Dagwood`)
|
|
34
|
+
- `logger` gem workaround (was only needed for SimpleFlow on Ruby 4)
|
|
35
|
+
- `*.loki` glob fallback in `find_task_file` — only `.loki` is the auto-discovered entry point
|
|
36
|
+
|
|
10
37
|
## [0.1.0] - 2026-05-28
|
|
11
38
|
|
|
12
39
|
### Added
|
|
13
40
|
|
|
14
41
|
- `Asgard::Base` — Thor subclass providing the task DSL
|
|
15
|
-
- `depends_on` — declare recipe dependencies
|
|
42
|
+
- `depends_on` — declare recipe dependencies; dependencies run at most once per invocation
|
|
16
43
|
- `var` — declare static or lazy-evaluated variables available to all recipes
|
|
17
44
|
- `import` — flat-merge a task module into the current class
|
|
18
45
|
- `dotenv` — load a `.env` file into the environment
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Asgard
|
|
2
2
|
|
|
3
|
-
A [just](https://just.systems)-like task runner for Ruby.
|
|
3
|
+
A [just](https://just.systems)-like task runner for Ruby. Built on [Thor](https://github.com/rails/thor) for argument handling and [Dagwood](https://github.com/rewindio/dagwood) for dependency ordering.
|
|
4
4
|
|
|
5
5
|
The name comes from Norse mythology: **Thor** is the CLI framework, **Asgard** is the realm where tasks live, and the task file is named **loki** — because Loki holds all the tricks.
|
|
6
6
|
|
|
@@ -16,241 +16,405 @@ Or add to your Gemfile:
|
|
|
16
16
|
bundle add asgard
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
---
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
## Tasks
|
|
22
|
+
|
|
23
|
+
Every `.loki` file defines tasks as methods inside `class Tasks`. The `Tasks` class is pre-defined by the gem — just reopen it and add methods.
|
|
24
|
+
|
|
25
|
+
### A task with no parameters
|
|
22
26
|
|
|
23
27
|
```ruby
|
|
24
|
-
|
|
28
|
+
class Tasks
|
|
29
|
+
desc "hello", "Say hello"
|
|
30
|
+
def hello = sh 'echo "Hello, World!"'
|
|
31
|
+
end
|
|
32
|
+
```
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
sh <<~SHELL
|
|
30
|
-
brew install redis
|
|
31
|
-
npm install
|
|
32
|
-
bundle install
|
|
33
|
-
SHELL
|
|
34
|
-
end
|
|
34
|
+
```bash
|
|
35
|
+
asgard hello
|
|
36
|
+
```
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
desc "test", "Run the test suite"
|
|
38
|
-
def test
|
|
39
|
-
sh "bundle exec rake test"
|
|
40
|
-
end
|
|
38
|
+
### A task with a positional parameter
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
bundle exec rake release
|
|
49
|
-
SHELL
|
|
50
|
-
end
|
|
40
|
+
Declare positional parameters directly in the method signature. Document them in the `desc` usage string:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
class Tasks
|
|
44
|
+
desc "hello NAME", "Say hello to NAME"
|
|
45
|
+
def hello(name = "World") = sh "echo 'Hello, #{name}!'"
|
|
51
46
|
end
|
|
52
47
|
```
|
|
53
48
|
|
|
54
|
-
Then run tasks from any directory in the project tree:
|
|
55
|
-
|
|
56
49
|
```bash
|
|
57
|
-
asgard
|
|
58
|
-
asgard
|
|
59
|
-
asgard help # list all available tasks
|
|
50
|
+
asgard hello
|
|
51
|
+
asgard hello Alice
|
|
60
52
|
```
|
|
61
53
|
|
|
62
|
-
|
|
54
|
+
### A task with a formal argument declaration
|
|
63
55
|
|
|
64
|
-
|
|
56
|
+
Use `argument` for richer metadata — type checking, enums, and help text:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
class Tasks
|
|
60
|
+
argument :name,
|
|
61
|
+
type: :string,
|
|
62
|
+
default: "World",
|
|
63
|
+
desc: "Name to greet"
|
|
64
|
+
|
|
65
|
+
desc "hello NAME", "Say hello to NAME"
|
|
66
|
+
def hello = sh "echo 'Hello, #{name}!'"
|
|
67
|
+
end
|
|
68
|
+
```
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
2. **`*.loki`** — all matching files loaded alphabetically when no `.loki` exists.
|
|
70
|
+
### A task with named options
|
|
68
71
|
|
|
69
|
-
|
|
72
|
+
Use `method_option` (alias: `option`) for named flags. Access them inside the method via `options[:name]`:
|
|
70
73
|
|
|
74
|
+
```ruby
|
|
75
|
+
class Tasks
|
|
76
|
+
desc "hello NAME", "Say hello to NAME"
|
|
77
|
+
method_option :shout, aliases: "-s", type: :boolean, desc: "Uppercase the output"
|
|
78
|
+
method_option :count, aliases: "-n", type: :numeric, default: 1, desc: "Repeat N times"
|
|
79
|
+
def hello(name = "World")
|
|
80
|
+
message = options[:shout] ? "HELLO, #{name.upcase}!" : "Hello, #{name}!"
|
|
81
|
+
options[:count].times { sh "echo '#{message}'" }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
71
84
|
```
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
asgard hello Alice --shout --count 3
|
|
75
88
|
```
|
|
76
89
|
|
|
77
|
-
|
|
90
|
+
### A task with an extended description
|
|
78
91
|
|
|
92
|
+
`long_desc` provides detailed help shown by `asgard help <task>`:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
class Tasks
|
|
96
|
+
long_desc <<~DESC
|
|
97
|
+
Says hello to NAME.
|
|
98
|
+
Repeats the greeting COUNT times.
|
|
99
|
+
Use --shout to uppercase the output.
|
|
100
|
+
DESC
|
|
101
|
+
desc "hello NAME", "Say hello to NAME"
|
|
102
|
+
method_option :shout, aliases: "-s", type: :boolean, desc: "Uppercase the output"
|
|
103
|
+
method_option :count, aliases: "-n", type: :numeric, default: 1, desc: "Repeat N times"
|
|
104
|
+
def hello(name = "World")
|
|
105
|
+
message = options[:shout] ? "HELLO, #{name.upcase}!" : "Hello, #{name}!"
|
|
106
|
+
options[:count].times { sh "echo '#{message}'" }
|
|
107
|
+
end
|
|
108
|
+
end
|
|
79
109
|
```
|
|
80
|
-
.loki # takes priority, *.loki files are ignored
|
|
81
|
-
```
|
|
82
110
|
|
|
83
|
-
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Dependencies
|
|
84
114
|
|
|
85
|
-
|
|
115
|
+
`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
|
+
|
|
117
|
+
`desc` and `depends_on` are independent — either can come first, both must appear before `def`.
|
|
118
|
+
|
|
119
|
+
### Sequential dependencies
|
|
120
|
+
|
|
121
|
+
Bare symbols run one after another in the order declared:
|
|
86
122
|
|
|
87
123
|
```ruby
|
|
88
|
-
|
|
89
|
-
desc "
|
|
90
|
-
def
|
|
91
|
-
|
|
124
|
+
class Tasks
|
|
125
|
+
desc "build", "Compile the project"
|
|
126
|
+
def build = sh "rake build"
|
|
127
|
+
|
|
128
|
+
depends_on :build
|
|
129
|
+
desc "test", "Run the test suite"
|
|
130
|
+
def test = sh "rake test"
|
|
131
|
+
|
|
132
|
+
depends_on :test
|
|
133
|
+
desc "release", "Publish the gem"
|
|
134
|
+
def release = sh "bundle exec rake release"
|
|
92
135
|
end
|
|
93
136
|
```
|
|
94
137
|
|
|
95
|
-
|
|
138
|
+
```bash
|
|
139
|
+
asgard release # build → test → release
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Parallel dependencies
|
|
96
143
|
|
|
97
|
-
|
|
144
|
+
Wrap symbols in an array to declare they can run concurrently. Asgard waits for all tasks in a parallel group to finish before moving to the next stage:
|
|
98
145
|
|
|
99
146
|
```ruby
|
|
100
|
-
|
|
101
|
-
|
|
147
|
+
class Tasks
|
|
148
|
+
desc "lint", "Check code style"
|
|
149
|
+
def lint = sh "bundle exec rubocop"
|
|
150
|
+
|
|
151
|
+
desc "typecheck", "Run type checks"
|
|
152
|
+
def typecheck = sh "bundle exec srb tc"
|
|
102
153
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
154
|
+
# lint and typecheck run in parallel, test waits for both
|
|
155
|
+
depends_on [:lint, :typecheck]
|
|
156
|
+
desc "test", "Run the test suite"
|
|
157
|
+
def test = sh "bundle exec rake test"
|
|
106
158
|
end
|
|
107
159
|
```
|
|
108
160
|
|
|
109
|
-
|
|
161
|
+
```bash
|
|
162
|
+
asgard test # lint ∥ typecheck → test
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Mixed sequential and parallel
|
|
166
|
+
|
|
167
|
+
Mix bare symbols (sequential) and arrays (parallel) in a single `depends_on` call. Execution proceeds stage by stage — each stage must complete before the next begins:
|
|
110
168
|
|
|
111
169
|
```ruby
|
|
112
|
-
|
|
113
|
-
def setup
|
|
114
|
-
sh
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
170
|
+
class Tasks
|
|
171
|
+
desc "setup", "Install dependencies"; def setup = sh "bundle install"
|
|
172
|
+
desc "lint", "Check code style"; def lint = sh "bundle exec rubocop"
|
|
173
|
+
desc "build", "Compile assets"; def build = sh "rake assets:precompile"
|
|
174
|
+
desc "test", "Run tests"; def test = sh "bundle exec rake test"
|
|
175
|
+
desc "notify", "Post to Slack"; def notify = sh "curl $SLACK_WEBHOOK -d '{\"text\":\"done\"}'"
|
|
176
|
+
|
|
177
|
+
# setup first, then lint+build in parallel, then test, then notify
|
|
178
|
+
depends_on :setup, [:lint, :build], :test, :notify
|
|
179
|
+
desc "ci", "Full CI pipeline"
|
|
180
|
+
def ci = sh "echo 'CI complete'"
|
|
120
181
|
end
|
|
121
182
|
```
|
|
122
183
|
|
|
123
|
-
|
|
184
|
+
```
|
|
185
|
+
asgard ci executes:
|
|
186
|
+
|
|
187
|
+
setup
|
|
188
|
+
↓
|
|
189
|
+
lint ∥ build (concurrent)
|
|
190
|
+
↓
|
|
191
|
+
test
|
|
192
|
+
↓
|
|
193
|
+
notify
|
|
194
|
+
↓
|
|
195
|
+
ci
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Variables
|
|
201
|
+
|
|
202
|
+
`var` declares a named value available to all tasks as a method. Pass a lambda for lazy evaluation — it is called once on first use:
|
|
124
203
|
|
|
125
204
|
```ruby
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
import json
|
|
130
|
-
data = json.load(open("results.json"))
|
|
131
|
-
print(f"Total: {sum(data.values())}")
|
|
132
|
-
PYTHON
|
|
133
|
-
end
|
|
205
|
+
class Tasks
|
|
206
|
+
var :app, "myapp"
|
|
207
|
+
var :version, -> { `git describe --tags`.strip }
|
|
134
208
|
|
|
135
|
-
desc "
|
|
136
|
-
def
|
|
137
|
-
shebang :node, <<~JS
|
|
138
|
-
const esbuild = require("esbuild")
|
|
139
|
-
esbuild.buildSync({ entryPoints: ["src/app.js"], bundle: true, outfile: "dist/app.js" })
|
|
140
|
-
JS
|
|
209
|
+
desc "tag", "Create a release tag"
|
|
210
|
+
def tag = sh "git tag #{app}-#{version}"
|
|
141
211
|
end
|
|
142
212
|
```
|
|
143
213
|
|
|
144
|
-
|
|
214
|
+
---
|
|
145
215
|
|
|
146
|
-
|
|
216
|
+
## Options shared across all tasks
|
|
147
217
|
|
|
148
|
-
|
|
218
|
+
`class_option` defines an option available to every task in the class:
|
|
149
219
|
|
|
150
220
|
```ruby
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
221
|
+
class Tasks
|
|
222
|
+
class_option :dry_run, aliases: "-n", type: :boolean, desc: "Print commands without running"
|
|
223
|
+
|
|
224
|
+
desc "deploy ENV", "Deploy to the given environment"
|
|
225
|
+
def deploy(env = "staging")
|
|
226
|
+
if options[:dry_run]
|
|
227
|
+
puts "Would deploy to #{env}"
|
|
228
|
+
else
|
|
229
|
+
sh "cap #{env} deploy"
|
|
230
|
+
end
|
|
156
231
|
end
|
|
157
232
|
end
|
|
233
|
+
```
|
|
158
234
|
|
|
159
|
-
|
|
160
|
-
require_relative "shared/deploy_tasks"
|
|
235
|
+
---
|
|
161
236
|
|
|
162
|
-
|
|
163
|
-
|
|
237
|
+
## Shell helpers
|
|
238
|
+
|
|
239
|
+
`sh` runs any shell command or multiline heredoc. `shebang` writes a script body to a tempfile and executes it with the given interpreter. Both exit with the command's status code on failure.
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
class Tasks
|
|
243
|
+
desc "setup", "Bootstrap the development environment"
|
|
244
|
+
def setup
|
|
245
|
+
sh <<~SHELL
|
|
246
|
+
brew install redis postgresql
|
|
247
|
+
brew services start redis
|
|
248
|
+
bundle install
|
|
249
|
+
rails db:setup
|
|
250
|
+
SHELL
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
desc "analyze", "Run Python data analysis"
|
|
254
|
+
def analyze
|
|
255
|
+
shebang :python3, <<~PYTHON
|
|
256
|
+
import json
|
|
257
|
+
data = json.load(open("results.json"))
|
|
258
|
+
print(f"Total: {sum(data.values())}")
|
|
259
|
+
PYTHON
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
desc "bundle_assets", "Build frontend assets with esbuild"
|
|
263
|
+
def bundle_assets
|
|
264
|
+
shebang :node, <<~JS
|
|
265
|
+
const esbuild = require("esbuild")
|
|
266
|
+
esbuild.buildSync({ entryPoints: ["src/app.js"], bundle: true, outfile: "dist/app.js" })
|
|
267
|
+
JS
|
|
268
|
+
end
|
|
164
269
|
end
|
|
165
270
|
```
|
|
166
271
|
|
|
167
|
-
|
|
272
|
+
Supported interpreters: `:python3`, `:python`, `:node`, `:ruby`, `:perl`, `:bash`, `:sh`. Any other symbol is passed directly to `system` with a `.tmp` extension.
|
|
273
|
+
|
|
274
|
+
Pass `silent: true` to suppress the command echo:
|
|
168
275
|
|
|
169
276
|
```ruby
|
|
170
|
-
|
|
171
|
-
# invoked as: asgard deploy production
|
|
277
|
+
def build = sh "rake build", silent: true
|
|
172
278
|
```
|
|
173
279
|
|
|
174
|
-
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Environment variables
|
|
283
|
+
|
|
284
|
+
`dotenv` loads a `.env` file into the environment before tasks run:
|
|
175
285
|
|
|
176
286
|
```ruby
|
|
177
|
-
class Tasks
|
|
178
|
-
dotenv
|
|
179
|
-
dotenv ".env.local"
|
|
287
|
+
class Tasks
|
|
288
|
+
dotenv # loads .env
|
|
289
|
+
dotenv ".env.local" # or a specific file
|
|
180
290
|
|
|
181
|
-
desc "check", "Print the app name"
|
|
182
|
-
def check
|
|
183
|
-
sh "echo $APP_NAME"
|
|
184
|
-
end
|
|
291
|
+
desc "check", "Print the app name from .env"
|
|
292
|
+
def check = sh "echo $APP_NAME"
|
|
185
293
|
end
|
|
186
294
|
```
|
|
187
295
|
|
|
188
|
-
|
|
296
|
+
---
|
|
189
297
|
|
|
190
|
-
|
|
298
|
+
## Command aliases
|
|
299
|
+
|
|
300
|
+
`map` creates alternative names for a task:
|
|
191
301
|
|
|
192
302
|
```ruby
|
|
193
|
-
|
|
194
|
-
|
|
303
|
+
class Tasks
|
|
304
|
+
map "-v" => "version"
|
|
305
|
+
map "--v" => "version"
|
|
306
|
+
map "t" => "test"
|
|
307
|
+
|
|
308
|
+
desc "version", "Print the version"
|
|
309
|
+
def version = puts Asgard::VERSION
|
|
195
310
|
end
|
|
196
311
|
```
|
|
197
312
|
|
|
198
|
-
|
|
313
|
+
---
|
|
199
314
|
|
|
200
|
-
|
|
201
|
-
|---|---|
|
|
202
|
-
| `sh(script, silent: false)` | Run a shell command or multiline script |
|
|
203
|
-
| `shebang(interpreter, script, silent: false)` | Write script to a tempfile and execute it |
|
|
315
|
+
## `method_option` types reference
|
|
204
316
|
|
|
205
|
-
|
|
317
|
+
| Type | CLI example | Ruby value |
|
|
318
|
+
|---|---|---|
|
|
319
|
+
| `:string` | `--branch main` | `"main"` |
|
|
320
|
+
| `:boolean` | `--force` / `--no-force` | `true` / `false` |
|
|
321
|
+
| `:numeric` | `--count 3` | `3` |
|
|
322
|
+
| `:array` | `--tags foo bar baz` | `["foo", "bar", "baz"]` |
|
|
323
|
+
| `:hash` | `--vars KEY:val FOO:bar` | `{"KEY"=>"val", "FOO"=>"bar"}` |
|
|
206
324
|
|
|
207
|
-
|
|
325
|
+
Common `method_option` keys: `aliases`, `type`, `default`, `required`, `desc`, `enum`, `banner`.
|
|
208
326
|
|
|
209
|
-
|
|
210
|
-
require "asgard"
|
|
327
|
+
---
|
|
211
328
|
|
|
212
|
-
|
|
213
|
-
dotenv
|
|
329
|
+
## Task files
|
|
214
330
|
|
|
215
|
-
|
|
216
|
-
|
|
331
|
+
Asgard searches the current directory and its ancestors for a `.loki` file. That file marks the project root. All `*.loki` files in the same directory are auto-loaded alphabetically before `.loki` is loaded.
|
|
332
|
+
|
|
333
|
+
### Single file
|
|
334
|
+
|
|
335
|
+
```
|
|
336
|
+
myproject/
|
|
337
|
+
.loki
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Multiple files
|
|
341
|
+
|
|
342
|
+
Split tasks across files — each reopens `class Tasks`:
|
|
217
343
|
|
|
344
|
+
```
|
|
345
|
+
myproject/
|
|
346
|
+
.loki ← entry point, can be empty
|
|
347
|
+
build.loki
|
|
348
|
+
deploy.loki
|
|
349
|
+
test.loki
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
```ruby
|
|
353
|
+
# build.loki
|
|
354
|
+
class Tasks
|
|
355
|
+
desc "build", "Compile the project"
|
|
356
|
+
def build = sh "rake build"
|
|
357
|
+
end
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
```ruby
|
|
361
|
+
# test.loki
|
|
362
|
+
class Tasks
|
|
363
|
+
depends_on :build
|
|
218
364
|
desc "test", "Run the test suite"
|
|
219
|
-
def test
|
|
220
|
-
|
|
221
|
-
|
|
365
|
+
def test = sh "bundle exec rake test"
|
|
366
|
+
end
|
|
367
|
+
```
|
|
222
368
|
|
|
369
|
+
```ruby
|
|
370
|
+
# deploy.loki
|
|
371
|
+
class Tasks
|
|
223
372
|
depends_on :test
|
|
224
|
-
desc "
|
|
225
|
-
def
|
|
226
|
-
|
|
227
|
-
|
|
373
|
+
desc "deploy", "Deploy to production"
|
|
374
|
+
def deploy = sh "cap production deploy"
|
|
375
|
+
end
|
|
376
|
+
```
|
|
228
377
|
|
|
229
|
-
|
|
230
|
-
def build
|
|
231
|
-
sh "bundle exec rake build"
|
|
232
|
-
end
|
|
378
|
+
The `.loki` entry point can be completely empty — it only needs to exist to mark the project root.
|
|
233
379
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
380
|
+
### Explicit loading
|
|
381
|
+
|
|
382
|
+
Load any Ruby or `.loki` file manually from `.loki`:
|
|
383
|
+
|
|
384
|
+
```ruby
|
|
385
|
+
# .loki
|
|
386
|
+
require_relative "shared/helpers"
|
|
387
|
+
require_relative "ci.loki"
|
|
388
|
+
|
|
389
|
+
class Tasks
|
|
390
|
+
# additional tasks
|
|
239
391
|
end
|
|
240
392
|
```
|
|
241
393
|
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## `Asgard` module API
|
|
397
|
+
|
|
398
|
+
| Method | Description |
|
|
399
|
+
|---|---|
|
|
400
|
+
| `Asgard.run!(argv)` | Entry point — finds `.loki`, loads task files, starts CLI |
|
|
401
|
+
| `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 |
|
|
403
|
+
|
|
404
|
+
`run!` handles its own errors — a missing `.loki` or a circular dependency both produce a clean one-line message and exit 1.
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
242
408
|
## Development
|
|
243
409
|
|
|
244
410
|
```bash
|
|
245
411
|
git clone git@github.com:MadBomber/asgard.git
|
|
246
412
|
cd asgard
|
|
247
413
|
bundle install
|
|
248
|
-
bundle exec rake test # run tests
|
|
249
|
-
bundle exec bin/asgard help #
|
|
414
|
+
bundle exec rake test # run tests (95% coverage minimum enforced)
|
|
415
|
+
bundle exec bin/asgard help # exercise the CLI against this gem's own .loki
|
|
250
416
|
```
|
|
251
417
|
|
|
252
|
-
Coverage threshold is enforced at 95% via SimpleCov.
|
|
253
|
-
|
|
254
418
|
## Contributing
|
|
255
419
|
|
|
256
420
|
Bug reports and pull requests are welcome at https://github.com/MadBomber/asgard.
|
data/bin/asgard
CHANGED
|
@@ -2,23 +2,4 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "asgard"
|
|
5
|
-
|
|
6
|
-
task_files = Asgard.find_task_files
|
|
7
|
-
|
|
8
|
-
unless task_files
|
|
9
|
-
warn "asgard: no .loki or *.loki task file found in #{Dir.pwd} or any parent directory"
|
|
10
|
-
exit 1
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
task_files.each { |f| load f }
|
|
14
|
-
|
|
15
|
-
klass = Asgard::Base.subclasses.last
|
|
16
|
-
|
|
17
|
-
unless klass
|
|
18
|
-
warn "asgard: no class inheriting Asgard::Base found in #{task_files.join(', ')}"
|
|
19
|
-
exit 1
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
klass.validate_deps!
|
|
23
|
-
klass._reset_ran!
|
|
24
|
-
klass.start(ARGV)
|
|
5
|
+
Asgard.run!(ARGV)
|
data/lib/asgard/base.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "thor"
|
|
4
4
|
require "set"
|
|
5
|
-
require "
|
|
5
|
+
require "dagwood"
|
|
6
6
|
|
|
7
7
|
module Asgard
|
|
8
8
|
class Base < Thor
|
|
@@ -88,7 +88,7 @@ module Asgard
|
|
|
88
88
|
Dotenv.load(path) if File.exist?(path)
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
# Validate the full dep graph for cycles using
|
|
91
|
+
# Validate the full dep graph for cycles using Dagwood::DependencyGraph.
|
|
92
92
|
def validate_deps!
|
|
93
93
|
return if _deps.empty?
|
|
94
94
|
|
|
@@ -97,7 +97,7 @@ module Asgard
|
|
|
97
97
|
hash[task] = _deps.fetch(task, []).flatten
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
Dagwood::DependencyGraph.new(full_graph).order
|
|
101
101
|
rescue TSort::Cyclic => e
|
|
102
102
|
raise Asgard::CircularDependencyError, e.message
|
|
103
103
|
end
|
|
@@ -132,7 +132,7 @@ module Asgard
|
|
|
132
132
|
stages = self.class._deps[target]
|
|
133
133
|
if stages&.any?
|
|
134
134
|
graph = self.class._build_dep_graph(stages)
|
|
135
|
-
groups =
|
|
135
|
+
groups = Dagwood::DependencyGraph.new(graph).parallel_order
|
|
136
136
|
|
|
137
137
|
groups.each do |group|
|
|
138
138
|
if group.size > 1
|
data/lib/asgard/tasks.rb
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Tasks is the single conventional entry point for all .loki files.
|
|
4
|
+
# It is pre-defined by the gem so .loki files never need to declare a class.
|
|
5
|
+
# Auxiliary *.loki files define modules which are imported into Tasks.
|
|
6
|
+
class Tasks < Asgard::Base; end
|
data/lib/asgard/version.rb
CHANGED
data/lib/asgard.rb
CHANGED
|
@@ -3,30 +3,43 @@
|
|
|
3
3
|
require_relative "asgard/version"
|
|
4
4
|
require_relative "asgard/shell"
|
|
5
5
|
require_relative "asgard/base"
|
|
6
|
+
require_relative "asgard/tasks"
|
|
6
7
|
|
|
7
8
|
module Asgard
|
|
8
9
|
class Error < StandardError; end
|
|
9
10
|
class CircularDependencyError < Error; end
|
|
10
11
|
|
|
11
|
-
# Search the current directory and its ancestors for task
|
|
12
|
-
# Returns
|
|
13
|
-
|
|
14
|
-
# Priority:
|
|
15
|
-
# 1. A single .loki file in the directory (default/hidden task file)
|
|
16
|
-
# 2. All *.loki files in the directory, sorted alphabetically
|
|
17
|
-
def self.find_task_files
|
|
12
|
+
# Search the current directory and its ancestors for a .loki task file.
|
|
13
|
+
# Returns the path string, or nil if not found.
|
|
14
|
+
def self.find_task_file
|
|
18
15
|
dir = Dir.pwd
|
|
19
16
|
loop do
|
|
20
|
-
|
|
21
|
-
return
|
|
22
|
-
|
|
23
|
-
matches = Dir.glob(File.join(dir, "*.loki")).sort
|
|
24
|
-
return matches unless matches.empty?
|
|
25
|
-
|
|
17
|
+
candidate = File.join(dir, ".loki")
|
|
18
|
+
return candidate if File.exist?(candidate)
|
|
26
19
|
parent = File.dirname(dir)
|
|
27
20
|
break if parent == dir
|
|
28
21
|
dir = parent
|
|
29
22
|
end
|
|
30
23
|
nil
|
|
31
24
|
end
|
|
25
|
+
|
|
26
|
+
# Load all *.loki files from dir in alphabetical order.
|
|
27
|
+
# Each file typically reopens class Tasks to add recipes.
|
|
28
|
+
# The .loki entry point is excluded — it is loaded separately by run!.
|
|
29
|
+
def self.load_loki(dir)
|
|
30
|
+
Dir.glob(File.join(dir, "*.loki")).sort.each { |f| load f }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Main entry point invoked by the asgard executable.
|
|
34
|
+
def self.run!(argv)
|
|
35
|
+
task_file = find_task_file or (warn "asgard: no .loki file found in #{Dir.pwd}"; exit 1)
|
|
36
|
+
load_loki(File.dirname(task_file))
|
|
37
|
+
load task_file
|
|
38
|
+
Tasks.validate_deps!
|
|
39
|
+
Tasks._reset_ran!
|
|
40
|
+
Tasks.start(argv)
|
|
41
|
+
rescue CircularDependencyError => e
|
|
42
|
+
warn "asgard: circular dependency — #{e.message}"
|
|
43
|
+
exit 1
|
|
44
|
+
end
|
|
32
45
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: asgard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dewayne VanHoozer
|
|
@@ -9,20 +9,6 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
-
- !ruby/object:Gem::Dependency
|
|
13
|
-
name: logger
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - ">="
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - ">="
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0'
|
|
26
12
|
- !ruby/object:Gem::Dependency
|
|
27
13
|
name: thor
|
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -38,19 +24,19 @@ dependencies:
|
|
|
38
24
|
- !ruby/object:Gem::Version
|
|
39
25
|
version: '1.0'
|
|
40
26
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name:
|
|
27
|
+
name: dagwood
|
|
42
28
|
requirement: !ruby/object:Gem::Requirement
|
|
43
29
|
requirements:
|
|
44
30
|
- - "~>"
|
|
45
31
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '0
|
|
32
|
+
version: '1.0'
|
|
47
33
|
type: :runtime
|
|
48
34
|
prerelease: false
|
|
49
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
36
|
requirements:
|
|
51
37
|
- - "~>"
|
|
52
38
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '0
|
|
39
|
+
version: '1.0'
|
|
54
40
|
- !ruby/object:Gem::Dependency
|
|
55
41
|
name: dotenv
|
|
56
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -66,7 +52,7 @@ dependencies:
|
|
|
66
52
|
- !ruby/object:Gem::Version
|
|
67
53
|
version: '3.0'
|
|
68
54
|
description: 'Asgard brings just-style recipes to Ruby: Thor-powered CLI, dep ordering
|
|
69
|
-
via
|
|
55
|
+
via Dagwood::DependencyGraph, var declarations, dotenv, sh/shebang helpers, and
|
|
70
56
|
importable task modules.'
|
|
71
57
|
email:
|
|
72
58
|
- dewayne@vanhoozer.me
|
|
@@ -88,6 +74,7 @@ files:
|
|
|
88
74
|
- lib/asgard.rb
|
|
89
75
|
- lib/asgard/base.rb
|
|
90
76
|
- lib/asgard/shell.rb
|
|
77
|
+
- lib/asgard/tasks.rb
|
|
91
78
|
- lib/asgard/version.rb
|
|
92
79
|
- sig/asgard.rbs
|
|
93
80
|
homepage: https://github.com/madbomber/asgard
|
|
@@ -113,5 +100,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
113
100
|
requirements: []
|
|
114
101
|
rubygems_version: 4.0.12
|
|
115
102
|
specification_version: 4
|
|
116
|
-
summary: A just-like task runner built on Thor, with recipe dependencies via
|
|
103
|
+
summary: A just-like task runner built on Thor, with recipe dependencies via Dagwood.
|
|
117
104
|
test_files: []
|