asgard 0.5.1 → 0.6.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/CHANGELOG.md +179 -99
- data/README.md +141 -46
- data/bin/asgard +10 -0
- data/lib/asgard/base/dispatch.rb +257 -31
- data/lib/asgard/base/registry.rb +7 -3
- data/lib/asgard/base.rb +140 -9
- data/lib/asgard/declarations.rb +21 -0
- data/lib/asgard/doctor/task_sections.rb +11 -3
- data/lib/asgard/doctor.rb +12 -8
- data/lib/asgard/jobs/commands.rb +152 -0
- data/lib/asgard/jobs/declaration.rb +149 -0
- data/lib/asgard/jobs/launcher.rb +109 -0
- data/lib/asgard/jobs/state.rb +190 -0
- data/lib/asgard/jobs/stopper.rb +141 -0
- data/lib/asgard/jobs.rb +25 -0
- data/lib/asgard/kernel_methods.rb +19 -14
- data/lib/asgard/leading_flag.rb +10 -0
- data/lib/asgard/pid.rb +130 -0
- data/lib/asgard/schedule/commands.rb +39 -11
- data/lib/asgard/schedule/declaration.rb +139 -12
- data/lib/asgard/schedule/job_files.rb +24 -0
- data/lib/asgard/schedule/launchd.rb +55 -18
- data/lib/asgard/schedule/systemd.rb +69 -27
- data/lib/asgard/schedule/table.rb +34 -16
- data/lib/asgard/schedule.rb +49 -9
- data/lib/asgard/shell.rb +372 -44
- data/lib/asgard/state_dir.rb +84 -0
- data/lib/asgard/tasks.rb +8 -0
- data/lib/asgard/version.rb +1 -1
- data/lib/asgard/watch/changes.rb +26 -1
- data/lib/asgard/watch/commands.rb +52 -12
- data/lib/asgard/watch/daemon.rb +334 -43
- data/lib/asgard/watch/declaration.rb +135 -40
- data/lib/asgard/watch/dispatcher.rb +61 -10
- data/lib/asgard/watch/mailbox.rb +50 -0
- data/lib/asgard/watch/reporter.rb +50 -34
- data/lib/asgard/watch/spawn.rb +41 -16
- data/lib/asgard/watch/state.rb +93 -27
- data/lib/asgard/watch/worker.rb +44 -9
- data/lib/asgard/watch.rb +4 -11
- data/lib/asgard.rb +80 -14
- metadata +167 -186
- data/.envrc +0 -6
- data/.github/workflows/deploy-github-pages.yml +0 -52
- data/.loki +0 -39
- data/.reek.yml +0 -166
- data/.rubocop.yml +0 -163
- data/Archspec.rb +0 -24
- data/CLAUDE.md +0 -139
- data/COMMITS.md +0 -196
- data/bin/console +0 -11
- data/bin/setup +0 -8
- data/doc_tasks.loki +0 -21
- data/docs/api.md +0 -368
- data/docs/assets/css/custom.css +0 -93
- data/docs/assets/images/asgard.jpg +0 -0
- data/docs/changelog.md +0 -1
- data/docs/dependencies.md +0 -313
- data/docs/environment.md +0 -129
- data/docs/examples.md +0 -194
- data/docs/getting-started.md +0 -181
- data/docs/helpers.md +0 -262
- data/docs/index.md +0 -87
- data/docs/options.md +0 -224
- data/docs/schedule.md +0 -106
- data/docs/shell.md +0 -237
- data/docs/subcommands.md +0 -181
- data/docs/task-files.md +0 -407
- data/docs/tasks.md +0 -326
- data/docs/variables.md +0 -338
- data/docs/watch.md +0 -123
- data/examples/.env +0 -4
- data/examples/.envrc +0 -3
- data/examples/.loki +0 -24
- data/examples/bad.loki +0 -63
- data/examples/concurrent.loki +0 -58
- data/examples/confirm.loki +0 -48
- data/examples/db_subcommands.loki +0 -74
- data/examples/depends_on_block/bad/.loki +0 -51
- data/examples/depends_on_block/good/.loki +0 -83
- data/examples/do.loki +0 -45
- data/examples/env_usage.loki +0 -27
- data/examples/kitchen_sink.loki +0 -210
- data/examples/server_subcommands.loki +0 -65
- data/examples/subdir/.loki +0 -12
- data/examples/subdir/import_demo.loki +0 -14
- data/examples/subdir/import_up_demo.loki +0 -18
- data/examples/watcher_examples/.envrc +0 -3
- data/examples/watcher_examples/.loki +0 -54
- data/examples/watcher_examples/README.md +0 -112
- data/examples/watcher_examples/bin/hello +0 -3
- data/examples/watcher_examples/data/records.json +0 -1
- data/examples/watcher_examples/docs/drafts/ideas.md +0 -5
- data/examples/watcher_examples/docs/index.md +0 -4
- data/examples/watcher_examples/lib/greeter.rb +0 -7
- data/gem_tasks.loki +0 -51
- data/git.loki +0 -13
- data/mkdocs.yml +0 -166
- data/quality.loki +0 -266
- data/quality_rails.loki +0 -47
- data/xyzzy.loki +0 -12
data/docs/tasks.md
DELETED
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
# Defining Tasks
|
|
2
|
-
|
|
3
|
-
Every task is a public method inside `class Tasks`. Asgard pre-defines `Tasks` as a subclass of `Asgard::Base` (which is itself a Thor subclass), so your `.loki` files just reopen the class and add methods. The full Thor DSL is available everywhere.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Basic Task
|
|
8
|
-
|
|
9
|
-
A task with no parameters and no options:
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
class Tasks
|
|
13
|
-
desc "Say hello"
|
|
14
|
-
def hello = puts "Hello, World!"
|
|
15
|
-
end
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
`desc` accepts either one or two strings. With one argument, the description is shown in `asgard help` and the usage string defaults to the method name. Pass two arguments when the usage string needs to document parameters — `desc "greet NAME", "Greet NAME by name"`.
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
asgard hello
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Positional Parameter with Default
|
|
27
|
-
|
|
28
|
-
Positional parameters are declared directly in the method signature. Document them in the `desc` usage string (uppercase by convention):
|
|
29
|
-
|
|
30
|
-
```ruby
|
|
31
|
-
class Tasks
|
|
32
|
-
desc "greet NAME", "Greet NAME; omit NAME to greet the world"
|
|
33
|
-
def greet(name = "World")
|
|
34
|
-
puts "Hello, #{name}!"
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
asgard greet # Hello, World!
|
|
41
|
-
asgard greet Alice # Hello, Alice!
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## Named Options
|
|
47
|
-
|
|
48
|
-
Use `method_option` (alias: `option`) for named flags. Access them inside the method via `options[:name]`.
|
|
49
|
-
|
|
50
|
-
### All Five Option Types
|
|
51
|
-
|
|
52
|
-
```ruby
|
|
53
|
-
class Tasks
|
|
54
|
-
desc "Compile the project"
|
|
55
|
-
option :output, aliases: "-o", type: :string, default: "dist/", desc: "Output directory"
|
|
56
|
-
option :verbose, aliases: "-v", type: :boolean, default: false, desc: "Enable verbose output"
|
|
57
|
-
option :jobs, aliases: "-j", type: :numeric, default: 1, desc: "Number of parallel jobs"
|
|
58
|
-
option :tags, type: :array, desc: "Build tags to apply"
|
|
59
|
-
option :defines, type: :hash, desc: "Preprocessor defines (KEY:VALUE)"
|
|
60
|
-
def compile
|
|
61
|
-
puts "Compiling → #{options[:output]} with #{options[:jobs]} job(s)"
|
|
62
|
-
puts "Tags: #{options[:tags].join(', ')}" if options[:tags]
|
|
63
|
-
puts "Defines: #{options[:defines]}" if options[:defines]
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Option Types Reference
|
|
69
|
-
|
|
70
|
-
| Type | CLI Example | Ruby Value |
|
|
71
|
-
|---|---|---|
|
|
72
|
-
| `:string` | `--output dist/` | `"dist/"` |
|
|
73
|
-
| `:boolean` | `--verbose` / `--no-verbose` | `true` / `false` |
|
|
74
|
-
| `:numeric` | `--jobs 4` | `4` |
|
|
75
|
-
| `:array` | `--tags foo bar baz` | `["foo", "bar", "baz"]` |
|
|
76
|
-
| `:hash` | `--defines KEY:val FOO:bar` | `{"KEY"=>"val", "FOO"=>"bar"}` |
|
|
77
|
-
|
|
78
|
-
### Common Option Keys
|
|
79
|
-
|
|
80
|
-
| Key | Description |
|
|
81
|
-
|---|---|
|
|
82
|
-
| `aliases` | Short-form flag, e.g. `"-o"` |
|
|
83
|
-
| `type` | `:string`, `:boolean`, `:numeric`, `:array`, or `:hash` |
|
|
84
|
-
| `default` | Value used when the flag is omitted |
|
|
85
|
-
| `required` | If `true`, Thor raises an error when the flag is missing |
|
|
86
|
-
| `desc` | One-line description shown in help |
|
|
87
|
-
| `enum` | Array of allowed values; Thor validates automatically |
|
|
88
|
-
| `banner` | Placeholder shown in help for the value slot, e.g. `"SECONDS"` |
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## Required Option
|
|
93
|
-
|
|
94
|
-
```ruby
|
|
95
|
-
class Tasks
|
|
96
|
-
desc "deploy ENV", "Deploy to ENV"
|
|
97
|
-
option :strategy,
|
|
98
|
-
type: :string,
|
|
99
|
-
required: true,
|
|
100
|
-
enum: %w[blue-green rolling canary],
|
|
101
|
-
desc: "Deployment strategy"
|
|
102
|
-
def deploy(env = "staging")
|
|
103
|
-
sh "cap #{env} deploy --strategy #{options[:strategy]}"
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
```bash
|
|
109
|
-
asgard deploy # Error: required option '--strategy' is missing
|
|
110
|
-
asgard deploy --strategy rolling
|
|
111
|
-
asgard deploy production --strategy blue-green
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
## Enum Validation
|
|
117
|
-
|
|
118
|
-
```ruby
|
|
119
|
-
class Tasks
|
|
120
|
-
desc "Build the project"
|
|
121
|
-
option :env,
|
|
122
|
-
type: :string,
|
|
123
|
-
default: "development",
|
|
124
|
-
enum: %w[development staging production],
|
|
125
|
-
desc: "Target environment"
|
|
126
|
-
def build
|
|
127
|
-
sh "rake build ENV=#{options[:env]}"
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
Thor validates the value against the enum and shows a helpful error if it doesn't match.
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
## Banner
|
|
137
|
-
|
|
138
|
-
`banner` replaces the default `VALUE` placeholder in help output with a more descriptive name:
|
|
139
|
-
|
|
140
|
-
```ruby
|
|
141
|
-
class Tasks
|
|
142
|
-
desc "Wait for a service to become available"
|
|
143
|
-
option :timeout, type: :numeric, default: 30, banner: "SECONDS", desc: "Give up after SECONDS"
|
|
144
|
-
def wait
|
|
145
|
-
sh "wait-for-it --timeout #{options[:timeout]}"
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
Help output shows: `[--timeout=SECONDS]` instead of `[--timeout=VALUE]`.
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## Extended Description
|
|
155
|
-
|
|
156
|
-
`long_desc` provides detailed help shown by `asgard help <task>`. Use `\x5` at the start of a line to force a line break within the wrapped text (a Thor convention):
|
|
157
|
-
|
|
158
|
-
```ruby
|
|
159
|
-
class Tasks
|
|
160
|
-
long_desc <<~DESC
|
|
161
|
-
Generates a project report covering test coverage, lint results,
|
|
162
|
-
and a dependency audit.
|
|
163
|
-
|
|
164
|
-
Pass --format to control output style. Use --since to scope the
|
|
165
|
-
report to changes after a given date.
|
|
166
|
-
|
|
167
|
-
Examples:\x5
|
|
168
|
-
asgard report --format html --since 2024-01-01\x5
|
|
169
|
-
asgard report --format json --output report.json\x5
|
|
170
|
-
asgard report --format text
|
|
171
|
-
DESC
|
|
172
|
-
desc "Generate a project report"
|
|
173
|
-
option :format, type: :string, default: "text", enum: %w[text html json], desc: "Output format"
|
|
174
|
-
option :since, type: :string, banner: "DATE", desc: "Limit to changes after DATE"
|
|
175
|
-
def report
|
|
176
|
-
sh "generate-report --format #{options[:format]}"
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
!!! tip
|
|
182
|
-
`desc` and `depends_on` are independent of each other — either can come first, but both must appear before the `def`.
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
## Default Task
|
|
187
|
-
|
|
188
|
-
`default_task` declares which command runs when `asgard` is invoked with no arguments:
|
|
189
|
-
|
|
190
|
-
```ruby
|
|
191
|
-
class Tasks
|
|
192
|
-
default_task :greet
|
|
193
|
-
|
|
194
|
-
desc "Say hello (runs by default)"
|
|
195
|
-
def greet
|
|
196
|
-
puts "Hello from Asgard!"
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
```bash
|
|
202
|
-
asgard # same as: asgard greet
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Without `default_task`, running `asgard` with no arguments displays the help message.
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
## Abbreviated Command Matching
|
|
210
|
-
|
|
211
|
-
Every task is a Thor command, and Thor resolves any unambiguous prefix of a command name to that command automatically — no Asgard code involved, and nothing to declare. Given:
|
|
212
|
-
|
|
213
|
-
```ruby
|
|
214
|
-
class Tasks
|
|
215
|
-
desc "Check code style with RuboCop"
|
|
216
|
-
def rubocop_check = sh "bundle exec rubocop"
|
|
217
|
-
|
|
218
|
-
desc "Run the test suite"
|
|
219
|
-
def test_check = sh "bundle exec rake test"
|
|
220
|
-
|
|
221
|
-
desc "Run the test suite with verbose output"
|
|
222
|
-
def test_verbose = sh "bundle exec rake test -v"
|
|
223
|
-
|
|
224
|
-
desc "Deploy to production"
|
|
225
|
-
def deploy = sh "cap production deploy"
|
|
226
|
-
|
|
227
|
-
desc "Deploy to staging"
|
|
228
|
-
def deploy_staging = sh "cap staging deploy"
|
|
229
|
-
end
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
asgard r # same as: asgard rubocop_check — the only task starting with "r"
|
|
234
|
-
asgard test_c # same as: asgard test_check — enough of the name to be unique
|
|
235
|
-
asgard test # Ambiguous command test matches [test_check, test_verbose]
|
|
236
|
-
asgard deploy # runs deploy, not deploy_staging — see below
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
Thor matches on a plain prefix (`command_name.start_with?(typed_string)`), so the shortest string that is still unique for your task set works. Two details worth knowing:
|
|
240
|
-
|
|
241
|
-
- **An exact full name always wins**, even if it's also a prefix of another task. `asgard deploy` runs `deploy` itself, never the ambiguous-prefix error, because `deploy` is a defined command — not merely a prefix of `deploy_staging`.
|
|
242
|
-
- **An ambiguous prefix produces a clean error listing every candidate** (`Ambiguous command X matches [...]`) rather than guessing or running the alphabetically-first match. Type enough of the name to disambiguate.
|
|
243
|
-
|
|
244
|
-
!!! tip
|
|
245
|
-
Because matching depends on every task name currently defined, a short prefix that's unique today can become ambiguous tomorrow when a new task with the same stem is added — e.g. adding `rubocop_fix` alongside `rubocop_check` turns `asgard r` from a clean match into `Ambiguous command r matches [rubocop_check, rubocop_fix]`. Use [`map`](#command-aliases) below for a short name you want to guarantee stays stable regardless of what other tasks get added later.
|
|
246
|
-
|
|
247
|
-
---
|
|
248
|
-
|
|
249
|
-
## Command Aliases
|
|
250
|
-
|
|
251
|
-
`map` creates short aliases for existing tasks:
|
|
252
|
-
|
|
253
|
-
```ruby
|
|
254
|
-
class Tasks
|
|
255
|
-
map "-v" => "version"
|
|
256
|
-
map "--v" => "version"
|
|
257
|
-
map "t" => "test"
|
|
258
|
-
map "b" => "build"
|
|
259
|
-
|
|
260
|
-
desc "Print the version"
|
|
261
|
-
def version = puts Asgard::VERSION
|
|
262
|
-
|
|
263
|
-
desc "Run tests"
|
|
264
|
-
def test = sh "bundle exec rake test"
|
|
265
|
-
|
|
266
|
-
desc "Build the gem"
|
|
267
|
-
def build = sh "bundle exec rake build"
|
|
268
|
-
end
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
```bash
|
|
272
|
-
asgard t # same as: asgard test
|
|
273
|
-
asgard b # same as: asgard build
|
|
274
|
-
asgard -v # same as: asgard version (note: --version is the built-in flag)
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
---
|
|
278
|
-
|
|
279
|
-
## Formal Argument Declaration
|
|
280
|
-
|
|
281
|
-
`argument` provides rich positional-parameter metadata including type checking, enums, and help text.
|
|
282
|
-
|
|
283
|
-
!!! warning "Class-level scope"
|
|
284
|
-
`argument` is a **class-level declaration** that applies to **every task in the class**, not just the one that follows it. It is best suited for single-command CLIs or when every task in the file genuinely shares the same positional input. In multi-task files, prefer method signature parameters instead.
|
|
285
|
-
|
|
286
|
-
```ruby
|
|
287
|
-
class Tasks
|
|
288
|
-
argument :name,
|
|
289
|
-
type: :string,
|
|
290
|
-
default: "World",
|
|
291
|
-
desc: "Name to greet"
|
|
292
|
-
|
|
293
|
-
desc "hello NAME", "Say hello to NAME"
|
|
294
|
-
def hello = puts "Hello, #{name}!"
|
|
295
|
-
end
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
For most multi-task `.loki` files, the simpler positional default pattern is safer:
|
|
299
|
-
|
|
300
|
-
```ruby
|
|
301
|
-
def hello(name = "World") = puts "Hello, #{name}!"
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
---
|
|
305
|
-
|
|
306
|
-
## No Commands Block
|
|
307
|
-
|
|
308
|
-
`no_commands` marks a block of methods as public helpers that are excluded from the CLI and `--help` output. They are callable from any task in the same class:
|
|
309
|
-
|
|
310
|
-
```ruby
|
|
311
|
-
class Tasks
|
|
312
|
-
desc "Compile the project"
|
|
313
|
-
def build
|
|
314
|
-
puts "Revision: #{current_sha}"
|
|
315
|
-
sh "rake build"
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
no_commands do
|
|
319
|
-
def current_sha
|
|
320
|
-
`git rev-parse --short HEAD`.strip
|
|
321
|
-
end
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
See [Helper Methods](helpers.md) for the full guide on helpers, `private`, and cross-file sharing.
|
data/docs/variables.md
DELETED
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
# Variables
|
|
2
|
-
|
|
3
|
-
Asgard task files are plain Ruby. Shared configuration values are declared using Ruby class variables (`@@name`) at the top of the `Tasks` class body.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Ruby Variable Types
|
|
8
|
-
|
|
9
|
-
Ruby has four kinds of variables plus constants, each with a distinct prefix and scope. Understanding the differences matters because tasks are instance methods — the wrong variable type will simply not be visible where you expect it.
|
|
10
|
-
|
|
11
|
-
| Kind | Prefix | Example | Scope |
|
|
12
|
-
|------|--------|---------|-------|
|
|
13
|
-
| Local | none | `count = 0` | The method or block it is defined in only |
|
|
14
|
-
| Instance | `@` | `@name = "myapp"` | One specific object instance |
|
|
15
|
-
| Class | `@@` | `@@name = "myapp"` | The class and all its subclasses |
|
|
16
|
-
| Global | `$` | `$DEBUG = true` | Everywhere in the process |
|
|
17
|
-
| Constant | uppercase first letter | `APP = "myapp"` | Everywhere (namespaced to where defined) |
|
|
18
|
-
|
|
19
|
-
### Local variables
|
|
20
|
-
|
|
21
|
-
```ruby
|
|
22
|
-
def build
|
|
23
|
-
output_dir = "dist" # only visible inside this method
|
|
24
|
-
sh "rake build OUTDIR=#{output_dir}"
|
|
25
|
-
end
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
`output_dir` disappears when the method returns. It cannot be seen by any other task.
|
|
29
|
-
|
|
30
|
-
### Instance variables (`@`)
|
|
31
|
-
|
|
32
|
-
```ruby
|
|
33
|
-
class Tasks
|
|
34
|
-
@app = "myapp" # class instance variable — lives on the Tasks class object
|
|
35
|
-
|
|
36
|
-
def build
|
|
37
|
-
puts @app # nil — this @app is on the instance, not the class
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
`@` in the class body sets a variable on the class object itself, not on the instances that run tasks. Inside a task method body, `@name` refers to the instance, which is a different object. They do not share state.
|
|
43
|
-
|
|
44
|
-
`@` inside a method is useful for memoization within a single task invocation:
|
|
45
|
-
|
|
46
|
-
```ruby
|
|
47
|
-
def version
|
|
48
|
-
@version ||= `git describe --tags`.strip # computed once, cached for this run
|
|
49
|
-
end
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Class variables (`@@`)
|
|
53
|
-
|
|
54
|
-
```ruby
|
|
55
|
-
class Tasks
|
|
56
|
-
@@app = "myapp" # visible in every task method and every subclass
|
|
57
|
-
|
|
58
|
-
def build
|
|
59
|
-
puts @@app # "myapp"
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
`@@` is shared across the class body, all instance methods, and all subclasses (including Thor subcommand classes). This makes it the right choice for configuration values in Asgard task files.
|
|
65
|
-
|
|
66
|
-
### Global variables (`$`)
|
|
67
|
-
|
|
68
|
-
```ruby
|
|
69
|
-
$DEBUG = true # visible everywhere in the Ruby process
|
|
70
|
-
$VERBOSE = true
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Asgard uses `$DEBUG` and `$VERBOSE` internally — they are set by the `--debug` and `--verbose` CLI flags. Avoid declaring your own `$` variables in `.loki` files; they affect the entire Ruby process including all loaded gems.
|
|
74
|
-
|
|
75
|
-
Several important Ruby globals you may encounter in task files:
|
|
76
|
-
|
|
77
|
-
| Variable | Purpose |
|
|
78
|
-
|----------|---------|
|
|
79
|
-
| `$stdout` / `$STDOUT` | Standard output stream — `puts` writes here |
|
|
80
|
-
| `$stderr` / `$STDERR` | Standard error stream — `warn` writes here |
|
|
81
|
-
| `$DEBUG` | Enables debug mode when `true` |
|
|
82
|
-
| `$VERBOSE` | Enables verbose warnings when `true` |
|
|
83
|
-
| `$PROGRAM_NAME` / `$0` | The name of the running script |
|
|
84
|
-
|
|
85
|
-
The uppercase versions (`$STDOUT`, `$STDERR`) are the original stream objects. The lowercase versions (`$stdout`, `$stderr`) are reassignable aliases — libraries sometimes redirect them temporarily to capture output. In task files, use `$stdout.puts` or `$stderr.puts` when you need explicit stream control; use plain `puts` and `warn` for normal output.
|
|
86
|
-
|
|
87
|
-
### Constants
|
|
88
|
-
|
|
89
|
-
Any name that begins with an uppercase letter is a constant in Ruby. Constants are available everywhere — inside methods, across files, and across classes — without any prefix:
|
|
90
|
-
|
|
91
|
-
```ruby
|
|
92
|
-
APP_NAME = "myapp".freeze
|
|
93
|
-
MAX_RETRIES = 3
|
|
94
|
-
BASE_URL = "https://example.com".freeze
|
|
95
|
-
|
|
96
|
-
class Tasks
|
|
97
|
-
desc "Deploy the app"
|
|
98
|
-
def deploy
|
|
99
|
-
puts "Deploying #{APP_NAME} to #{BASE_URL}"
|
|
100
|
-
sh "cap deploy"
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
The convention is `ALL_CAPS_WITH_UNDERSCORES` for values that are truly fixed. Class and module names are also constants — `Tasks`, `Asgard`, `String`, `Integer` all start with an uppercase letter.
|
|
106
|
-
|
|
107
|
-
Ruby will issue a warning if you reassign a constant but will not prevent it. Use `.freeze` to make the value itself immutable:
|
|
108
|
-
|
|
109
|
-
```ruby
|
|
110
|
-
APP_NAME = "myapp".freeze # value cannot be mutated; reassignment still warns
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
**Constants vs `@@` class variables in task files:**
|
|
114
|
-
|
|
115
|
-
| | Constant | `@@` class variable |
|
|
116
|
-
|---|---|---|
|
|
117
|
-
| Accessible in task methods | Yes | Yes |
|
|
118
|
-
| Accessible in subcommand subclasses | Yes | Yes |
|
|
119
|
-
| Visible outside the class | Yes — anywhere | Only within the class hierarchy |
|
|
120
|
-
| Reassignment warning | Yes | No |
|
|
121
|
-
| Convention | `ALL_CAPS` | `snake_case` |
|
|
122
|
-
|
|
123
|
-
For fixed values that will never change — app names, version strings, URLs, port numbers — constants are often the clearest choice. For values that might reasonably vary across environments or be overridden in a different `.loki` file, `@@` with `||=` is more flexible.
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## Strings and Interpolation
|
|
128
|
-
|
|
129
|
-
### Always use double quotes
|
|
130
|
-
|
|
131
|
-
Ruby supports both single and double quoted strings. In Asgard task files, always use double quotes:
|
|
132
|
-
|
|
133
|
-
```ruby
|
|
134
|
-
@@app ||= "myapp".freeze # correct
|
|
135
|
-
sh "bundle exec rake test" # correct
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
Single-quoted strings look similar but behave differently — they do not support interpolation or escape sequences. Mixing the two styles adds confusion for no benefit. Double quotes work everywhere single quotes do, and more.
|
|
139
|
-
|
|
140
|
-
### String interpolation
|
|
141
|
-
|
|
142
|
-
Embedding a variable's value inside a string uses the `#{}` syntax. Everything inside the braces is Ruby code — the result is converted to a string and inserted in place:
|
|
143
|
-
|
|
144
|
-
```ruby
|
|
145
|
-
class Tasks
|
|
146
|
-
@@app ||= "myapp".freeze
|
|
147
|
-
@@env ||= "production".freeze
|
|
148
|
-
|
|
149
|
-
desc "Deploy the app"
|
|
150
|
-
def deploy
|
|
151
|
-
sh "cap #{@@env} deploy APP=#{@@app}"
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
Any Ruby expression works inside `#{}`:
|
|
157
|
-
|
|
158
|
-
```ruby
|
|
159
|
-
puts "build started at #{Time.now}"
|
|
160
|
-
sh "puma -p #{env(:port, '3000').to_i + 1}"
|
|
161
|
-
sh "git tag #{@@app}-#{`git describe --tags`.strip}"
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
Interpolation only works inside double-quoted strings. This is the primary reason Asgard tasks use double quotes exclusively — shell commands almost always need to embed variable values.
|
|
165
|
-
|
|
166
|
-
### Multi-line strings
|
|
167
|
-
|
|
168
|
-
For shell scripts with multiple lines, use a heredoc. The `~` modifier strips leading indentation so the script aligns with your code:
|
|
169
|
-
|
|
170
|
-
```ruby
|
|
171
|
-
desc "Bootstrap the project"
|
|
172
|
-
def bootstrap
|
|
173
|
-
sh <<~SHELL
|
|
174
|
-
bundle install
|
|
175
|
-
rails db:create db:migrate
|
|
176
|
-
echo "#{@@app} ready on #{env(:port, '3000')}"
|
|
177
|
-
SHELL
|
|
178
|
-
end
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
Interpolation works inside heredocs the same as in double-quoted strings.
|
|
182
|
-
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
## System Environment Variables
|
|
186
|
-
|
|
187
|
-
System environment variables are set outside Ruby — in the shell, a CI environment, or a `.env` file — and are accessed inside tasks via the `env` Kernel method:
|
|
188
|
-
|
|
189
|
-
```ruby
|
|
190
|
-
class Tasks
|
|
191
|
-
desc "Start the server"
|
|
192
|
-
def start
|
|
193
|
-
sh "puma -p #{env(:port, '3000')} -e #{env(:rack_env, 'development')}"
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
desc "Deploy the app"
|
|
197
|
-
def deploy
|
|
198
|
-
sh "cap #{env(:deploy_target)} deploy" # raises KeyError if DEPLOY_TARGET is not set
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
`env` accepts a symbol or string and converts it to an uppercase `ENV` key automatically:
|
|
204
|
-
|
|
205
|
-
| Call | Equivalent | Behaviour |
|
|
206
|
-
|------|-----------|-----------|
|
|
207
|
-
| `env(:port, "3000")` | `ENV.fetch("PORT", "3000")` | Returns `"3000"` if `PORT` is unset |
|
|
208
|
-
| `env(:api_key)` | `ENV.fetch("API_KEY")` | Raises `KeyError` if `API_KEY` is unset |
|
|
209
|
-
| `env("DATABASE_URL")` | `ENV.fetch("DATABASE_URL")` | Raises `KeyError` if unset |
|
|
210
|
-
| `env("database_url")` | `ENV.fetch("DATABASE_URL")` | Same — name is always upcased |
|
|
211
|
-
|
|
212
|
-
!!! note
|
|
213
|
-
All environment variable values are strings. Convert to other types explicitly: `env(:port, "3000").to_i`, `env(:debug, "false") == "true"`.
|
|
214
|
-
|
|
215
|
-
Use `dotenv` to load a `.env` file before tasks run — see [Environment](environment.md).
|
|
216
|
-
|
|
217
|
-
---
|
|
218
|
-
|
|
219
|
-
## The Pattern
|
|
220
|
-
|
|
221
|
-
```ruby
|
|
222
|
-
class Tasks
|
|
223
|
-
@@app ||= "myapp".freeze
|
|
224
|
-
@@port ||= 3000
|
|
225
|
-
@@env ||= "production".freeze
|
|
226
|
-
|
|
227
|
-
desc "Print app info"
|
|
228
|
-
def info
|
|
229
|
-
puts "#{@@app} running on port #{@@port} in #{@@env}"
|
|
230
|
-
end
|
|
231
|
-
end
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
**Why `||=` instead of `=`?**
|
|
235
|
-
Because multiple `.loki` files reopen the same `class Tasks`. Using `||=` means the first file to declare a value wins, and subsequent files that reopen `Tasks` won't accidentally overwrite it.
|
|
236
|
-
|
|
237
|
-
**Why `.freeze`?**
|
|
238
|
-
It prevents mutation of the value (e.g. `@@app << "-extra"` raises a `FrozenError`). Numbers and symbols are already frozen. Use `.freeze` on strings, arrays, and hashes.
|
|
239
|
-
|
|
240
|
-
**Why `@@` instead of `@`?**
|
|
241
|
-
A single `@` in the class body sets a class instance variable — it lives on the `Tasks` class object and is **not** accessible inside task method bodies. `@@` is a class variable and is visible everywhere: in all instance methods, and in any subclass (including Thor subcommand classes).
|
|
242
|
-
|
|
243
|
-
---
|
|
244
|
-
|
|
245
|
-
## Sharing Values Across Subcommands
|
|
246
|
-
|
|
247
|
-
Class variables are visible in subclasses, which makes them the right choice when you have Thor subcommands defined in separate classes:
|
|
248
|
-
|
|
249
|
-
```ruby
|
|
250
|
-
# config.loki
|
|
251
|
-
class Tasks
|
|
252
|
-
@@app ||= "myapp".freeze
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
# deploy.loki
|
|
256
|
-
class DeployCommands < Tasks
|
|
257
|
-
desc "Deploy to production"
|
|
258
|
-
def production
|
|
259
|
-
sh "cap production deploy APP=#{@@app}" # @@app is visible here
|
|
260
|
-
end
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
class Tasks
|
|
264
|
-
desc "deploy SUBCOMMAND", "Deployment tasks"
|
|
265
|
-
subcommand "deploy", DeployCommands
|
|
266
|
-
end
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
---
|
|
270
|
-
|
|
271
|
-
## Computed Values
|
|
272
|
-
|
|
273
|
-
For values that require a shell call, file read, or any runtime computation, define a method instead:
|
|
274
|
-
|
|
275
|
-
```ruby
|
|
276
|
-
class Tasks
|
|
277
|
-
def version = `git describe --tags`.strip
|
|
278
|
-
def sha = `git rev-parse --short HEAD`.strip
|
|
279
|
-
|
|
280
|
-
desc "Show version info"
|
|
281
|
-
def info = puts "#{version} (#{sha})"
|
|
282
|
-
end
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
Methods defined without `desc` do not appear in `--help` output or as CLI commands. If you need memoization (the computation is expensive and called multiple times), use `||=` on an instance variable inside the method:
|
|
286
|
-
|
|
287
|
-
```ruby
|
|
288
|
-
class Tasks
|
|
289
|
-
def version
|
|
290
|
-
@version ||= `git describe --tags`.strip
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
---
|
|
296
|
-
|
|
297
|
-
## Sharing Values Across Files
|
|
298
|
-
|
|
299
|
-
Because all `.loki` files reopen the same `class Tasks`, a `@@` variable declared in one file is available in all other files loaded in the same session:
|
|
300
|
-
|
|
301
|
-
```ruby
|
|
302
|
-
# config.loki
|
|
303
|
-
class Tasks
|
|
304
|
-
@@app ||= "myapp".freeze
|
|
305
|
-
@@port ||= 8080
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
# deploy.loki
|
|
309
|
-
class Tasks
|
|
310
|
-
desc "Deploy the app"
|
|
311
|
-
def deploy = sh "cap deploy APP=#{@@app} PORT=#{@@port}"
|
|
312
|
-
end
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
---
|
|
316
|
-
|
|
317
|
-
## Naming Conventions
|
|
318
|
-
|
|
319
|
-
Class variable names use `snake_case` — the standard Ruby convention for variables and methods:
|
|
320
|
-
|
|
321
|
-
```ruby
|
|
322
|
-
class Tasks
|
|
323
|
-
@@app_name ||= "myapp".freeze
|
|
324
|
-
@@deploy_host ||= "production.example.com".freeze
|
|
325
|
-
@@max_workers ||= 4
|
|
326
|
-
end
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
Multi-word names are separated by underscores, not camelCase or hyphens.
|
|
330
|
-
|
|
331
|
-
---
|
|
332
|
-
|
|
333
|
-
## Naming Caution
|
|
334
|
-
|
|
335
|
-
!!! warning
|
|
336
|
-
Avoid `@@` names that conflict with built-in Ruby or Thor internals. Safe practice: use descriptive names that are unlikely to clash.
|
|
337
|
-
|
|
338
|
-
Names to avoid: `options`, `shell`, `invoke`, `command`, `args`.
|