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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c25b1efcac0de2f525140327f64753c1cb7b0cf82904ea6c4e1be1114b619bf
4
- data.tar.gz: 857e98381996d06968944ed6bafb6b35d86073523672ac6d987ce2fb85a8cc79
3
+ metadata.gz: 7920fc28de5f8644ccc7934f576142d433de3fb5af1ac21b28cbd350d930af66
4
+ data.tar.gz: e651f2956eb2f9fc076548fc04cae92e6835fb508b5960eeda952929d9197253
5
5
  SHA512:
6
- metadata.gz: 383ececde0765e801b1e84dc239895ee0d2ffe491fc6d0c0c4d3a583f0a0912f64ca8dc4a4a6469f12efcae93358302ada9aeb648b6a95ada4fe866c205306b0
7
- data.tar.gz: 3d2ecd3d0dcc3acdaa1ecd6dd6457ce02c23b086c36f178b9aa378ec620069ae99e9ecae3d824d26f0d22fb5906a4b34bc1831a2040abf24bab81833080b470e
6
+ metadata.gz: 19e473102e5850db42c777b9dd824765b9e4a187f1de6f009d80ae85fa1d6992984587a7ca4e9dc40cb301aadc11093826f913d3c5c7dac8e17a47c47f7cdefe
7
+ data.tar.gz: '09c0f72476c8690f055e6aeb2b052449a4eb9fd415bff06a82e4cd5dc4255cfb8709721a76ae347226b31b8dafcbd3e63a439fcd6f249975f134156f578edb1f'
data/.loki CHANGED
@@ -1,6 +1,8 @@
1
- # default task filename for the asgard task runner
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 < Asgard::Base
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 resolved via `SimpleFlow::DependencyGraph`; dependencies run at most once per invocation
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. Define project recipes in a `.loki` file and run them with the `asgard` command. Built on [Thor](https://github.com/rails/thor) for argument handling and [SimpleFlow](https://github.com/madbomber/simple_flow) for dependency ordering.
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
- ## Quick Start
19
+ ---
20
20
 
21
- Create a `.loki` file at your project root. `sh` runs any shell command — a single line or a multiline heredoc:
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
- # filename: .loki
28
+ class Tasks
29
+ desc "hello", "Say hello"
30
+ def hello = sh 'echo "Hello, World!"'
31
+ end
32
+ ```
25
33
 
26
- class Tasks < Asgard::Base
27
- desc "deps", "Install project dependencies"
28
- def deps
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
- depends_on :deps
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
- depends_on :test
43
- desc "release", "Tag and publish the gem"
44
- def release
45
- sh <<~SHELL
46
- git tag v$(ruby -r./lib/my_gem/version -e 'puts MyGem::VERSION')
47
- git push --tags
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 test # runs deps, then test
58
- asgard release # runs deps, then test, then release
59
- asgard help # list all available tasks
50
+ asgard hello
51
+ asgard hello Alice
60
52
  ```
61
53
 
62
- ## Task Files
54
+ ### A task with a formal argument declaration
63
55
 
64
- Asgard searches the current directory and its ancestors for task files, in this order:
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
- 1. **`.loki`** the hidden default. Found alone, takes priority over everything.
67
- 2. **`*.loki`** — all matching files loaded alphabetically when no `.loki` exists.
70
+ ### A task with named options
68
71
 
69
- This means you can split a large task set across multiple files:
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
- deploy.loki
73
- test.loki
74
- build.loki # loaded as: build.loki, deploy.loki, test.loki
85
+
86
+ ```bash
87
+ asgard hello Alice --shout --count 3
75
88
  ```
76
89
 
77
- Or use a single hidden default:
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
- ## Features
111
+ ---
112
+
113
+ ## Dependencies
84
114
 
85
- ### Task dependencies
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
- depends_on :build
89
- desc "test", "Run tests"
90
- def test
91
- sh "bundle exec rake test"
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
- Dependencies run before the recipe, at most once per invocation regardless of how many recipes declare them. Circular dependencies are caught at startup via `SimpleFlow::DependencyGraph`.
138
+ ```bash
139
+ asgard release # build → test → release
140
+ ```
141
+
142
+ ### Parallel dependencies
96
143
 
97
- ### Variables
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
- var :app, "myapp"
101
- var :version, -> { `git describe --tags`.strip } # lazy, evaluated on first use
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
- desc "tag", "Create a git tag"
104
- def tag
105
- sh "git tag #{version}"
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
- ### Multi-line shell scripts
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
- desc "setup", "Bootstrap the dev environment"
113
- def setup
114
- sh <<~SHELL
115
- brew install redis postgresql
116
- brew services start redis
117
- bundle install
118
- rails db:setup
119
- SHELL
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
- ### Embedded scripts in other languages
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
- desc "analyze", "Run data analysis"
127
- def analyze
128
- shebang :python3, <<~PYTHON
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 "bundle", "Build frontend assets"
136
- def bundle_assets
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
- Supported interpreters: `:python3`, `:python`, `:node`, `:ruby`, `:perl`, `:bash`, `:sh`. Any other symbol is passed directly to `system` with a `.tmp` extension.
214
+ ---
145
215
 
146
- ### Importing task modules
216
+ ## Options shared across all tasks
147
217
 
148
- Split tasks into reusable modules and include them flat (all tasks in the same namespace):
218
+ `class_option` defines an option available to every task in the class:
149
219
 
150
220
  ```ruby
151
- # shared/deploy_tasks.rb
152
- module DeployTasks
153
- def self.included(base)
154
- base.desc "deploy", "Deploy to production"
155
- base.define_method(:deploy) { sh "cap production deploy" }
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
- # .loki
160
- require_relative "shared/deploy_tasks"
235
+ ---
161
236
 
162
- class Tasks < Asgard::Base
163
- import DeployTasks
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
- For namespaced subcommands, use Thor's `register`:
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
- register DeployTasks, "deploy", "deploy COMMAND", "Deployment tasks"
171
- # invoked as: asgard deploy production
277
+ def build = sh "rake build", silent: true
172
278
  ```
173
279
 
174
- ### Dotenv
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 < Asgard::Base
178
- dotenv # loads .env from CWD
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
- ### Echo suppression
296
+ ---
189
297
 
190
- Pass `silent: true` to suppress the command echo (equivalent to `just`'s `@` prefix):
298
+ ## Command aliases
299
+
300
+ `map` creates alternative names for a task:
191
301
 
192
302
  ```ruby
193
- def build
194
- sh "bundle exec rake build", silent: true # runs quietly, output still shown
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
- ## Shell helpers
313
+ ---
199
314
 
200
- | Method | Description |
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
- Both exit with the command's status code on failure.
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
- ## Full example `.loki`
325
+ Common `method_option` keys: `aliases`, `type`, `default`, `required`, `desc`, `enum`, `banner`.
208
326
 
209
- ```ruby
210
- require "asgard"
327
+ ---
211
328
 
212
- class Tasks < Asgard::Base
213
- dotenv
329
+ ## Task files
214
330
 
215
- var :app, "myapp"
216
- var :version, -> { File.read("VERSION").strip }
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
- sh "bundle exec rake test"
221
- end
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 "quality", "Run tests then check complexity"
225
- def quality
226
- sh "flog lib/"
227
- end
373
+ desc "deploy", "Deploy to production"
374
+ def deploy = sh "cap production deploy"
375
+ end
376
+ ```
228
377
 
229
- desc "build", "Build the gem"
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
- depends_on :quality, :build
235
- desc "release", "Release #{version} to RubyGems"
236
- def release
237
- sh "bundle exec rake release"
238
- end
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 with coverage
249
- bundle exec bin/asgard help # try the CLI against this gem's own .loki file
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 "simple_flow"
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 SimpleFlow::DependencyGraph.
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
- SimpleFlow::DependencyGraph.new(full_graph).order
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 = SimpleFlow::DependencyGraph.new(graph).parallel_order
135
+ groups = Dagwood::DependencyGraph.new(graph).parallel_order
136
136
 
137
137
  groups.each do |group|
138
138
  if group.size > 1
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Asgard
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
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 files.
12
- # Returns an array of paths, or nil if nothing is found.
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
- dot_loki = File.join(dir, ".loki")
21
- return [dot_loki] if File.exist?(dot_loki)
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.0
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: simple_flow
27
+ name: dagwood
42
28
  requirement: !ruby/object:Gem::Requirement
43
29
  requirements:
44
30
  - - "~>"
45
31
  - !ruby/object:Gem::Version
46
- version: '0.4'
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.4'
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 SimpleFlow::DependencyGraph, var declarations, dotenv, sh/shebang helpers, and
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 SimpleFlow.
103
+ summary: A just-like task runner built on Thor, with recipe dependencies via Dagwood.
117
104
  test_files: []