asgard 0.1.2 → 0.3.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.
data/docs/variables.md ADDED
@@ -0,0 +1,338 @@
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`.
data/examples/.env ADDED
@@ -0,0 +1,4 @@
1
+ APP_NAME=my_app
2
+ PORT=4000
3
+ API_KEY=secret-key-abc123
4
+ DATABASE_URL=postgres://localhost/my_app_dev
data/examples/.loki CHANGED
@@ -1,2 +1,24 @@
1
- # The .loki file can be empty if there are *.loki files define the tasks
2
- # they will be auto loaded.
1
+ # examples/.loki root task file for the examples directory.
2
+ #
3
+ # Asgard loads only this file by default. Everything else must be
4
+ # explicitly pulled in with import.
5
+ #
6
+ # import(path)
7
+ # Load a specific file or a glob of files, idempotently. Absolute
8
+ # paths, relative paths, and globs are all accepted.
9
+ #
10
+ # import_up(name)
11
+ # Walk CWD and every ancestor directory looking for `name`, then
12
+ # import it. Useful inside a nested task file that needs to pull in
13
+ # a shared config from the project root without knowing how deep it is.
14
+ #
15
+ # loki_up(name)
16
+ # Same walk as import_up but just returns the absolute path (or nil)
17
+ # without loading it — handy when you need the path for another purpose.
18
+
19
+ # Load all *.loki files in this directory (kitchen_sink, concurrent, etc.)
20
+ import "*.loki"
21
+
22
+ # Load a task file from a subdirectory.
23
+ # import also accepts a direct path, not just a glob.
24
+ import "subdir/import_demo.loki"
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ # Demonstrates concurrent task execution via parallel depends_on groups.
3
+ #
4
+ # Each worker prints its letter repeatedly with random delays. Because
5
+ # worker_a, worker_b, and worker_c run in separate threads their output
6
+ # interleaves on stdout, proving real concurrency.
7
+ #
8
+ # Execution order:
9
+ # start → worker_a + worker_b + worker_c (all three concurrent) → finish
10
+ #
11
+ # Run with:
12
+ # asgard finish
13
+ #
14
+ # Sample output (character order varies every run):
15
+ # start
16
+ # ABCBACBACBABCBACBACB
17
+ # end
18
+
19
+ $stdout.sync = true # flush every print immediately across all threads
20
+
21
+ CONCURRENT_REPS = 10 # how many times each worker prints its character
22
+
23
+ class Tasks
24
+ desc "Print start marker"
25
+ def start
26
+ puts "starting demo of concurrent task execution ..."
27
+ end
28
+
29
+ desc "Print 'A' repeatedly with random delays"
30
+ def worker_a
31
+ CONCURRENT_REPS.times do
32
+ print "A"
33
+ sleep rand(0.05..0.3)
34
+ end
35
+ end
36
+
37
+ desc "Print 'B' repeatedly with random delays"
38
+ def worker_b
39
+ CONCURRENT_REPS.times do
40
+ print "B"
41
+ sleep rand(0.05..0.3)
42
+ end
43
+ end
44
+
45
+ desc "Print 'C' repeatedly with random delays"
46
+ def worker_c
47
+ CONCURRENT_REPS.times do
48
+ print "C"
49
+ sleep rand(0.05..0.3)
50
+ end
51
+ end
52
+
53
+ depends_on :start, [:worker_a, :worker_b, :worker_c]
54
+ desc "Print end marker after all workers complete"
55
+ def finish
56
+ puts "\nfini - the end of concurrent task demo"
57
+ end
58
+ end
@@ -39,7 +39,7 @@ class DBCommands < Tasks
39
39
  # depends_on chains within the subcommand group:
40
40
  # rollback → migrate → seed → reset
41
41
  depends_on :rollback, :migrate, :seed
42
- desc "reset", "Rollback all migrations, re-migrate, and reseed"
42
+ desc "Rollback all migrations, re-migrate, and reseed"
43
43
  def reset
44
44
  puts "Database reset complete."
45
45
  end
@@ -54,7 +54,7 @@ class DBCommands < Tasks
54
54
  asgard db console\x5
55
55
  asgard db console --env staging
56
56
  DESC
57
- desc "console", "Open an interactive database console"
57
+ desc "Open an interactive database console"
58
58
  option :env, type: :string, default: "development",
59
59
  enum: %w[development staging production],
60
60
  desc: "Environment to connect to"
@@ -62,7 +62,7 @@ class DBCommands < Tasks
62
62
  puts "Opening #{options[:env]} database console..."
63
63
  end
64
64
 
65
- desc "status", "Show applied and pending migrations"
65
+ desc "Show applied and pending migrations"
66
66
  def status
67
67
  puts "Checking migration status..."
68
68
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ # Demonstrates the env() Kernel helper for reading system environment variables.
3
+ #
4
+ # env() is cleaner than ENV[] — it accepts symbols or strings, upcases
5
+ # automatically, and raises KeyError when a required variable is missing
6
+ # rather than silently returning nil.
7
+ #
8
+ # Run from examples/ or any subdirectory — loki_up locates .env automatically:
9
+ # asgard env_demo
10
+
11
+ class Tasks
12
+ # loki_up walks CWD and every ancestor until it finds .env, returning
13
+ # its absolute path. This works regardless of which directory asgard
14
+ # is run from — no hardcoded relative path needed.
15
+ dotenv loki_up(".env") || ".env"
16
+
17
+ desc "Print environment variable values resolved via env()"
18
+ def env_demo
19
+ puts <<~OUT
20
+ APP_NAME : #{env(:app_name)}
21
+ PORT : #{env(:port)}
22
+ API_KEY : #{env(:api_key)}
23
+ DATABASE_URL : #{env(:database_url)}
24
+ LOG_LEVEL : #{env(:log_level, "info")}
25
+ OUT
26
+ end
27
+ end
@@ -5,9 +5,20 @@
5
5
  # install, release) so this file can be loaded alongside them without conflict.
6
6
 
7
7
  class Tasks
8
- # ── Asgard: varstatic value and lazy lambda ─────────────────────────────
9
- var :app_name, "my_app"
10
- var :build_dir, -> { "builds/#{app_name}" }
8
+ # ── Ruby class variables shared across all tasks and subcommands ──────────
9
+ @@app_name ||= "my_app".freeze
10
+ @@build_dir ||= "builds/#{@@app_name}".freeze
11
+
12
+ # ── Computed values — plain private methods replace the removed var DSL ──────
13
+ # Declare as private so Thor does not try to register them as commands.
14
+ # Use @ivar ||= inside the method body to memoize expensive calls.
15
+ private
16
+
17
+ def version = `git describe --tags --always`.strip
18
+ def sha = `git rev-parse --short HEAD`.strip
19
+ def branch = @branch ||= `git rev-parse --abbrev-ref HEAD`.strip
20
+
21
+ public
11
22
 
12
23
  # ── Asgard: dotenv — load environment variables ────────────────────────────
13
24
  # Uncomment to activate:
@@ -37,9 +48,9 @@ class Tasks
37
48
  map "pl" => :pipeline
38
49
 
39
50
  # ── Basic task — no parameters ─────────────────────────────────────────────
40
- desc "greet", "Say hello (default task when no command is given)"
51
+ desc "Say hello (default task when no command is given)"
41
52
  def greet
42
- puts "Hello from #{app_name} (#{options[:env]})!"
53
+ puts "Hello from #{@@app_name} #{version} (#{branch}) in #{options[:env]} mode!"
43
54
  end
44
55
 
45
56
  # ── Positional parameter with default ──────────────────────────────────────
@@ -72,14 +83,14 @@ class Tasks
72
83
  end
73
84
 
74
85
  # ── method_option — all five option types ──────────────────────────────────
75
- desc "compile", "Compile the project"
86
+ desc "Compile the project"
76
87
  option :output, aliases: "-o", type: :string, default: "dist/", desc: "Output directory"
77
88
  option :verbose, aliases: "-v", type: :boolean, default: false, desc: "Enable verbose output"
78
89
  option :jobs, aliases: "-j", type: :numeric, default: 1, desc: "Number of parallel jobs"
79
90
  option :tags, type: :array, desc: "Build tags to apply"
80
91
  option :defines, type: :hash, desc: "Preprocessor defines (KEY:VALUE)"
81
92
  def compile
82
- puts "Compiling #{app_name} → #{options[:output]}"
93
+ puts "Compiling #{@@app_name} → #{options[:output]}"
83
94
  end
84
95
 
85
96
  # ── required option + enum + banner ────────────────────────────────────────
@@ -99,7 +110,7 @@ class Tasks
99
110
  default: "main",
100
111
  desc: "Git branch to deploy"
101
112
  def deploy(env = "staging")
102
- puts "Deploying #{app_name}@#{options[:branch]} to #{env}..."
113
+ puts "Deploying #{@@app_name}@#{options[:branch]} to #{env}..."
103
114
  end
104
115
 
105
116
  # ── long_desc — extended help shown by `asgard help report` ────────────────
@@ -115,7 +126,7 @@ class Tasks
115
126
  asgard report --format json --output report.json\x5
116
127
  asgard rp --format text
117
128
  LONGDESC
118
- desc "report", "Generate a project report"
129
+ desc "Generate a project report"
119
130
  option :format, type: :string, default: "text", enum: %w[text html json], desc: "Output format"
120
131
  option :since, type: :string, banner: "DATE", desc: "Limit to changes after DATE"
121
132
  option :output, type: :string, banner: "FILE", desc: "Write output to FILE"
@@ -124,30 +135,52 @@ class Tasks
124
135
  end
125
136
 
126
137
  # ── Asgard depends_on: sequential — analyze runs before spec ───────────────
127
- desc "analyze", "Check code style and complexity"
138
+ desc "Check code style and complexity"
128
139
  def analyze = puts "Analyzing..."
129
140
 
130
141
  depends_on :analyze
131
- desc "spec", "Run the test suite (depends on: analyze)"
142
+ desc "Run the test suite (depends on: analyze)"
132
143
  def spec = puts "Running specs..."
133
144
 
134
145
  # ── Asgard depends_on: parallel — analyze and typecheck run concurrently ───
135
- desc "typecheck", "Run the type checker"
146
+ desc "Run the type checker"
136
147
  def typecheck = puts "Type checking..."
137
148
 
138
149
  depends_on [:analyze, :typecheck]
139
- desc "check", "Run analyze and typecheck in parallel"
150
+ desc "Run analyze and typecheck in parallel"
140
151
  def check = puts "All checks passed."
141
152
 
142
153
  # ── Asgard depends_on: mixed sequential + parallel ─────────────────────────
143
- desc "pack", "Create distribution archive"
154
+ desc "Create distribution archive"
144
155
  def pack = puts "Packing..."
145
156
 
146
157
  # check → compile+spec (parallel) → pack → pipeline
147
158
  depends_on :check, [:compile, :spec], :pack
148
- desc "pipeline", "Full pipeline: check → compile+spec → pack"
159
+ desc "Full pipeline: check → compile+spec → pack"
149
160
  def pipeline = puts "Pipeline complete."
150
161
 
162
+ # ── Asgard: debug? / verbose? — Kernel predicates for conditional output ──────
163
+ # debug? returns true when --debug is passed (sets $DEBUG)
164
+ # verbose? returns true when --verbose is passed (sets $VERBOSE)
165
+ # Both are set by Asgard before invoke_command runs, so they are safe
166
+ # to read inside any task body.
167
+ #
168
+ # Run with:
169
+ # asgard status --verbose
170
+ # asgard status --debug
171
+ # asgard status --verbose --debug
172
+ desc "Show application status"
173
+ def status
174
+ puts "#{@@app_name} is running in #{options[:env]} mode."
175
+ puts " build dir : #{@@build_dir}" if verbose?
176
+ puts " sha : #{current_sha}" if verbose?
177
+ if debug?
178
+ puts " $DEBUG : #{$DEBUG.inspect}"
179
+ puts " $VERBOSE : #{$VERBOSE.inspect}"
180
+ puts " options : #{options.inspect}"
181
+ end
182
+ end
183
+
151
184
  # ── Thor: no_commands — public helper excluded from CLI and --help ──────────
152
185
  no_commands do
153
186
  def current_sha
@@ -2,7 +2,7 @@
2
2
  # Demonstrates Thor subcommands registered on the top-level Tasks class.
3
3
  #
4
4
  # The subcommand class inherits from Tasks so it has access to sh, shebang,
5
- # var, depends_on, and the built-in --debug/--verbose class options.
5
+ # depends_on, and the built-in --debug/--verbose class options.
6
6
  #
7
7
  # Usage:
8
8
  # asgard server # shows subcommand help
@@ -26,7 +26,7 @@ class ServerCommands < Tasks
26
26
  ]
27
27
  end
28
28
 
29
- desc "stop", "Stop the running server"
29
+ desc "Stop the running server"
30
30
  option :force, aliases: "-f", type: :boolean, default: false, desc: "Force-kill without draining"
31
31
  option :wait, type: :numeric, default: 30, desc: "Seconds to wait for shutdown"
32
32
  def stop
@@ -37,7 +37,7 @@ class ServerCommands < Tasks
37
37
  end
38
38
  end
39
39
 
40
- desc "status", "Show server status and process info"
40
+ desc "Show server status and process info"
41
41
  def status
42
42
  puts "Checking server status..."
43
43
  end
@@ -0,0 +1,12 @@
1
+ # examples/subdir/.loki — root task file for this subdirectory.
2
+ #
3
+ # When asgard is run from examples/subdir/, it loads this file.
4
+ # From here, import pulls in sibling task files explicitly.
5
+ #
6
+ # import accepts a direct path or a glob:
7
+ # import "import_up_demo.loki" # a single file
8
+ # import "*.loki" # all loki files in this directory
9
+ # import "tasks/**/*.loki" # all loki files under a tasks/ tree
10
+ # import "../shared/*.loki" # files in a sibling directory
11
+
12
+ import "import_up_demo.loki"
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ # This file lives in a subdirectory and is explicitly imported by the
3
+ # parent examples/.loki via import "subdir/import_demo.loki".
4
+ #
5
+ # In a real project a subdirectory might hold task files scoped to a
6
+ # specific concern (deploy, database, CI) while the root .loki wires
7
+ # them all together with import.
8
+
9
+ class Tasks
10
+ desc "Confirm this task was loaded from a subdirectory via import"
11
+ def subdir_task
12
+ puts "Loaded from examples/subdir/import_demo.loki"
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ # Demonstrates import_up — find a file by walking up the directory tree
3
+ # and load it, without needing to know how deep the current file is.
4
+ #
5
+ # import_up(name) combines loki_up(name) + import(path) in one call.
6
+ # It searches CWD, then each ancestor in turn, and loads the first match.
7
+ #
8
+ # This is useful when a nested task file needs to pull in something from
9
+ # the project root — a shared config, a common helpers file — without
10
+ # hardcoding a relative path that would break if the file moves.
11
+ #
12
+ # import_up also accepts a glob:
13
+ # import_up "*.loki" # first directory (walking up) that contains any .loki file
14
+ # import_up "config/settings.loki" # first ancestor with config/settings.loki
15
+ # import_up ".env" # locate a .env file anywhere up the tree
16
+
17
+ # Load env_usage.loki from the nearest ancestor directory that contains it.
18
+ import_up "env_usage.loki"