asgard 0.5.0 → 0.5.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: e81b8d0d4ce7904a7483a06348d488730f258171af1d0d6897a99fc058829d91
4
- data.tar.gz: a25c7c2dccc05df8ac533a17a118f93361aa8eabbb6ad8fde36af65088107cb2
3
+ metadata.gz: 299e2b451fc4acf80bf87970012a5fea712bf7f2efc4103465355d091d2b1282
4
+ data.tar.gz: e9dc3f90d5abaaac9b735e2b92a35296b5956bc0e261526244aff5d1ba500a8e
5
5
  SHA512:
6
- metadata.gz: ee9be83865e0fa5a50e5fac52e6d720e7d76b3da4f56790d8badb708a93d603d30628c5a960f1aca209f030f89b34c726f151caa3b6c337be4e69b1c00c15fb1
7
- data.tar.gz: 9741a4dcac57f091e00a5f6b84f1cd70d5c5e9c04b2059a8483edbb17166fca17762d38d6161eea6fd294e09fcc129394fce88c58c53a7d240d1eb664ca5d750
6
+ metadata.gz: 5279427ba559b0067881a8e828642222c3189b002c01dbeb409f69bfe39a018e7288a088125ea29b56d71f8a16370965ed4bea03a9328be9ef6c29b7a4c5d3af
7
+ data.tar.gz: cf33a3581b2fceeec5baf59d8fe42505c3d29e9d7f0717e5b81110208c1f9aa339883e06a1c5e9bc29487eba13e4f63ab37d509e48d03285a0ef90a20a64fcb1
data/CHANGELOG.md CHANGED
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.5.0] - Unreleased
8
+ ## [0.5.1] - 2026-10-19
9
9
 
10
10
  ### Added
11
11
 
@@ -31,6 +31,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
31
31
  `empty?`); every list is empty when the task is run by hand.
32
32
  - **`Asgard::Watch::Error`** (subclass of `Asgard::Error`) — raised when a
33
33
  `watch` names a task the project does not define.
34
+ - **`examples/do.loki`** — `asgard do lint test build` runs tasks in
35
+ sequence, with comma-separated steps for arguments and options
36
+ (`asgard do hello Alice, farewell Bob --formal`). A plain user task, not
37
+ a built-in; it stops at the first failure.
38
+ - **`examples/confirm.loki`** — a hidden `confirm` task to list first in a
39
+ dangerous task's `depends_on`; answering anything but y/yes aborts with
40
+ exit 1. Refuses without a terminal unless `ASGARD_YES` is set.
34
41
 
35
42
  ### Changed
36
43
 
@@ -43,6 +50,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
43
50
  - `Asgard.machine_wide?` is true for `watcher list` as well as
44
51
  `schedule list`, so both work from a directory with no `.loki` above it.
45
52
 
53
+ ### Fixed
54
+
55
+ - **Usage errors exit 1** — a missing argument, an unknown command, or a bad
56
+ subcommand (`asgard greet` with no NAME, `asgard nope`) printed Thor's
57
+ error but exited 0, so CI and scripts saw success. `Asgard::Base` now
58
+ defines `exit_on_failure?` as true.
59
+
46
60
  ## [0.4.1] - 2026-10-02
47
61
 
48
62
  ### Added
data/docs/examples.md CHANGED
@@ -130,6 +130,58 @@ This example is also useful as a test harness for verifying that parallel execut
130
130
 
131
131
  ---
132
132
 
133
+ ## `do.loki`
134
+
135
+ **Path:** `examples/do.loki`
136
+
137
+ A plain user task that runs other tasks in sequence, the way `rake a b c` or `just a b c` does. No gem changes needed:
138
+
139
+ - Steps are comma-separated, so each step can carry its own arguments and options; with no commas, every word is a task of its own
140
+ - Each step is parsed by Thor as if it were typed on the command line
141
+ - The sequence stops at the first failure (a failing `sh`, or a step returning `:fail`)
142
+
143
+ ```bash
144
+ asgard do greet finish
145
+ asgard do hello Alice, farewell Bob --formal
146
+ # Hello, Alice!
147
+ # Goodbye, Bob.
148
+ ```
149
+
150
+ !!! note
151
+ Every task runs at most once per `asgard` invocation, so `asgard do hello Alice, hello Bob` greets only Alice. The same rule makes a dependency shared by several steps run once.
152
+
153
+ ---
154
+
155
+ ## `confirm.loki`
156
+
157
+ **Path:** `examples/confirm.loki`
158
+
159
+ A guard task. List `confirm` first in a dangerous task's `depends_on`, and the task runs only after the user answers yes:
160
+
161
+ ```ruby
162
+ depends_on :confirm
163
+ desc "Drop and recreate the database"
164
+ def reset_database = sh "bin/rails db:reset"
165
+ ```
166
+
167
+ ```bash
168
+ asgard reset_database
169
+ # Really run `asgard reset_database`? [y/N] n
170
+ # asgard: aborted `asgard reset_database` (exit 1)
171
+
172
+ ASGARD_YES=1 asgard reset_database # no question, for scripts and CI
173
+ ```
174
+
175
+ - Answering no **raises** `Asgard::Error`, which stops the dependent task. Returning `:fail` would not: a dependency's return value never stops the task that depends on it
176
+ - A dependency doesn't know which task asked for it, so the question shows the whole command line
177
+ - With no terminal (a schedule, the watcher, a pipe) it refuses unless `ASGARD_YES` is set
178
+ - `confirm` is declared with `hide: true`, so it stays out of `asgard help`
179
+
180
+ !!! note
181
+ `confirm` runs at most once per `asgard` invocation, so `asgard do reset_database, drop_cache` asks once for the whole line. Keep it in its own first stage: in `depends_on [:confirm, :backup]`, `backup` runs while the question waits.
182
+
183
+ ---
184
+
133
185
  ## Summary
134
186
 
135
187
  | File | Primary Focus |
@@ -138,3 +190,5 @@ This example is also useful as a test harness for verifying that parallel execut
138
190
  | `server_subcommands.loki` | Subcommand groups, per-command options, depends_on in subcommands |
139
191
  | `db_subcommands.loki` | Multi-step depends_on chains, enum validation, long_desc in subcommands |
140
192
  | `concurrent.loki` | Parallel task execution, thread concurrency demonstration |
193
+ | `do.loki` | Running several tasks in sequence from one command line |
194
+ | `confirm.loki` | A guard dependency that asks before a dangerous task runs |
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+ # Demonstrates a guard task: list `confirm` first in a dangerous task's
3
+ # depends_on and the task only runs after the user answers yes.
4
+ #
5
+ # Run with:
6
+ # asgard reset_database # asks, runs only on y/yes
7
+ # ASGARD_YES=1 asgard reset_database # no question (scripts, CI)
8
+ #
9
+ # Behavior:
10
+ # - A dependency doesn't know which task asked for it, so the question
11
+ # shows the whole command line: "Really run `asgard reset_database`?"
12
+ # - Answering no raises Asgard::Error, which stops everything that
13
+ # hasn't run yet; asgard prints "asgard: aborted ..." and exits 1.
14
+ # Returning :fail would NOT work: a dependency's return value never
15
+ # stops the task that depends on it.
16
+ # - With no terminal to ask (a schedule, the watcher, a pipe) it refuses
17
+ # unless ASGARD_YES is set, rather than hanging or guessing.
18
+ # - It runs at most once per asgard invocation, so
19
+ # `asgard do reset_database, drop_cache` asks once for the whole line.
20
+ # - Put confirm in its own first stage. In a parallel group,
21
+ # `depends_on [:confirm, :backup]`, backup runs while the question waits.
22
+
23
+ class Tasks
24
+ # Not a Thor command (a class method), so it can be tested on its own:
25
+ # Tasks.confirmed?("y", yes_env: nil) #=> true
26
+ def self.confirmed?(answer, yes_env:)
27
+ return true if yes_env && !yes_env.empty?
28
+
29
+ %w[y yes].include?(answer.to_s.strip.downcase)
30
+ end
31
+
32
+ desc "Ask before running the task that depends on this", hide: true
33
+ def confirm
34
+ command = "asgard #{ARGV.join(' ')}"
35
+ yes_env = env(:asgard_yes, "")
36
+ return if self.class.confirmed?(nil, yes_env:)
37
+ raise Asgard::Error, "refusing `#{command}`: no terminal to confirm on (set ASGARD_YES=1)" unless $stdin.tty?
38
+
39
+ answer = ask("Really run `#{command}`? [y/N]")
40
+ raise Asgard::Error, "aborted `#{command}`" unless self.class.confirmed?(answer, yes_env:)
41
+ end
42
+
43
+ depends_on :confirm
44
+ desc "Pretend to drop and recreate the database (demo for confirm)"
45
+ def reset_database
46
+ puts "Database reset."
47
+ end
48
+ end
data/examples/do.loki ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ # Demonstrates a plain user task that runs other tasks in sequence — the
3
+ # `rake a b c` / `just a b c` style, which Thor's one-command dispatch
4
+ # doesn't offer on its own.
5
+ #
6
+ # Steps are comma-separated so a step can carry its own arguments and
7
+ # options; with no commas, every word is a task of its own.
8
+ #
9
+ # Run with:
10
+ # asgard do greet finish
11
+ # asgard do hello Alice, farewell Bob --formal, finish
12
+ #
13
+ # Behavior:
14
+ # - Each step is parsed by Thor exactly as if typed on the command line,
15
+ # so its arguments and options work as usual.
16
+ # - The sequence stops at the first failure: a failing `sh` exits the
17
+ # process, and a step returning :fail makes `do` return :fail (exit 1).
18
+ # - Every task runs at most once per asgard invocation, so
19
+ # `asgard do hello Alice, hello Bob` greets only Alice, and a dependency
20
+ # shared by several steps runs once.
21
+
22
+ require "shellwords"
23
+
24
+ class Tasks
25
+ # Not a Thor command (a class method), so it can be tested on its own:
26
+ # Tasks.do_steps("lint, hello Alice") #=> [["lint"], ["hello", "Alice"]]
27
+ def self.do_steps(line)
28
+ line.include?(",") ? line.split(",").map { |step| Shellwords.split(step) }.reject(&:empty?)
29
+ : Shellwords.split(line).map { |word| [word] }
30
+ end
31
+
32
+ desc "do STEPS...", "Run several tasks in sequence, e.g. `asgard do hello Alice, farewell Bob --formal`"
33
+ stop_on_unknown_option! :do
34
+ def do(*words)
35
+ steps = self.class.do_steps(words.join(" "))
36
+ raise Asgard::Error, "do: no tasks given" if steps.empty?
37
+
38
+ steps.each do |name, *args|
39
+ raise Asgard::Error, "do: unknown task '#{name}'" if name == "do" || !self.class.all_commands.key?(name)
40
+ # dispatch with nil options is what Tasks.start does: Thor parses this
41
+ # step's argv itself. (invoke would reuse the options parsed for `do`.)
42
+ return :fail if self.class.send(:dispatch, nil, [name, *args], nil, shell: shell) == :fail
43
+ end
44
+ end
45
+ end
data/lib/asgard/base.rb CHANGED
@@ -18,6 +18,10 @@ module Asgard
18
18
  include Dispatch
19
19
 
20
20
  class << self
21
+ # A usage error (missing argument, unknown command or option) exits 1,
22
+ # so CI and `asgard do` sequences see the failure. Thor's default is 0.
23
+ def exit_on_failure? = true
24
+
21
25
  def header(text = nil)
22
26
  return @_header if text.nil?
23
27
  (@_header ||= []) << text
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Asgard
4
- VERSION = "0.5.0"
4
+ VERSION = "0.5.1"
5
5
  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.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
@@ -129,9 +129,11 @@ files:
129
129
  - examples/.loki
130
130
  - examples/bad.loki
131
131
  - examples/concurrent.loki
132
+ - examples/confirm.loki
132
133
  - examples/db_subcommands.loki
133
134
  - examples/depends_on_block/bad/.loki
134
135
  - examples/depends_on_block/good/.loki
136
+ - examples/do.loki
135
137
  - examples/env_usage.loki
136
138
  - examples/kitchen_sink.loki
137
139
  - examples/server_subcommands.loki