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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +179 -99
  3. data/README.md +141 -46
  4. data/bin/asgard +10 -0
  5. data/lib/asgard/base/dispatch.rb +257 -31
  6. data/lib/asgard/base/registry.rb +7 -3
  7. data/lib/asgard/base.rb +140 -9
  8. data/lib/asgard/declarations.rb +21 -0
  9. data/lib/asgard/doctor/task_sections.rb +11 -3
  10. data/lib/asgard/doctor.rb +12 -8
  11. data/lib/asgard/jobs/commands.rb +152 -0
  12. data/lib/asgard/jobs/declaration.rb +149 -0
  13. data/lib/asgard/jobs/launcher.rb +109 -0
  14. data/lib/asgard/jobs/state.rb +190 -0
  15. data/lib/asgard/jobs/stopper.rb +141 -0
  16. data/lib/asgard/jobs.rb +25 -0
  17. data/lib/asgard/kernel_methods.rb +19 -14
  18. data/lib/asgard/leading_flag.rb +10 -0
  19. data/lib/asgard/pid.rb +130 -0
  20. data/lib/asgard/schedule/commands.rb +39 -11
  21. data/lib/asgard/schedule/declaration.rb +139 -12
  22. data/lib/asgard/schedule/job_files.rb +24 -0
  23. data/lib/asgard/schedule/launchd.rb +55 -18
  24. data/lib/asgard/schedule/systemd.rb +69 -27
  25. data/lib/asgard/schedule/table.rb +34 -16
  26. data/lib/asgard/schedule.rb +49 -9
  27. data/lib/asgard/shell.rb +372 -44
  28. data/lib/asgard/state_dir.rb +84 -0
  29. data/lib/asgard/tasks.rb +8 -0
  30. data/lib/asgard/version.rb +1 -1
  31. data/lib/asgard/watch/changes.rb +26 -1
  32. data/lib/asgard/watch/commands.rb +52 -12
  33. data/lib/asgard/watch/daemon.rb +334 -43
  34. data/lib/asgard/watch/declaration.rb +135 -40
  35. data/lib/asgard/watch/dispatcher.rb +61 -10
  36. data/lib/asgard/watch/mailbox.rb +50 -0
  37. data/lib/asgard/watch/reporter.rb +50 -34
  38. data/lib/asgard/watch/spawn.rb +41 -16
  39. data/lib/asgard/watch/state.rb +93 -27
  40. data/lib/asgard/watch/worker.rb +44 -9
  41. data/lib/asgard/watch.rb +4 -11
  42. data/lib/asgard.rb +80 -14
  43. metadata +167 -186
  44. data/.envrc +0 -6
  45. data/.github/workflows/deploy-github-pages.yml +0 -52
  46. data/.loki +0 -39
  47. data/.reek.yml +0 -166
  48. data/.rubocop.yml +0 -163
  49. data/Archspec.rb +0 -24
  50. data/CLAUDE.md +0 -139
  51. data/COMMITS.md +0 -196
  52. data/bin/console +0 -11
  53. data/bin/setup +0 -8
  54. data/doc_tasks.loki +0 -21
  55. data/docs/api.md +0 -368
  56. data/docs/assets/css/custom.css +0 -93
  57. data/docs/assets/images/asgard.jpg +0 -0
  58. data/docs/changelog.md +0 -1
  59. data/docs/dependencies.md +0 -313
  60. data/docs/environment.md +0 -129
  61. data/docs/examples.md +0 -194
  62. data/docs/getting-started.md +0 -181
  63. data/docs/helpers.md +0 -262
  64. data/docs/index.md +0 -87
  65. data/docs/options.md +0 -224
  66. data/docs/schedule.md +0 -106
  67. data/docs/shell.md +0 -237
  68. data/docs/subcommands.md +0 -181
  69. data/docs/task-files.md +0 -407
  70. data/docs/tasks.md +0 -326
  71. data/docs/variables.md +0 -338
  72. data/docs/watch.md +0 -123
  73. data/examples/.env +0 -4
  74. data/examples/.envrc +0 -3
  75. data/examples/.loki +0 -24
  76. data/examples/bad.loki +0 -63
  77. data/examples/concurrent.loki +0 -58
  78. data/examples/confirm.loki +0 -48
  79. data/examples/db_subcommands.loki +0 -74
  80. data/examples/depends_on_block/bad/.loki +0 -51
  81. data/examples/depends_on_block/good/.loki +0 -83
  82. data/examples/do.loki +0 -45
  83. data/examples/env_usage.loki +0 -27
  84. data/examples/kitchen_sink.loki +0 -210
  85. data/examples/server_subcommands.loki +0 -65
  86. data/examples/subdir/.loki +0 -12
  87. data/examples/subdir/import_demo.loki +0 -14
  88. data/examples/subdir/import_up_demo.loki +0 -18
  89. data/examples/watcher_examples/.envrc +0 -3
  90. data/examples/watcher_examples/.loki +0 -54
  91. data/examples/watcher_examples/README.md +0 -112
  92. data/examples/watcher_examples/bin/hello +0 -3
  93. data/examples/watcher_examples/data/records.json +0 -1
  94. data/examples/watcher_examples/docs/drafts/ideas.md +0 -5
  95. data/examples/watcher_examples/docs/index.md +0 -4
  96. data/examples/watcher_examples/lib/greeter.rb +0 -7
  97. data/gem_tasks.loki +0 -51
  98. data/git.loki +0 -13
  99. data/mkdocs.yml +0 -166
  100. data/quality.loki +0 -266
  101. data/quality_rails.loki +0 -47
  102. data/xyzzy.loki +0 -12
@@ -1,181 +0,0 @@
1
- # Getting Started
2
-
3
- This guide walks you through installing Asgard, creating your first `.loki` task file, and running tasks from the command line.
4
-
5
- ---
6
-
7
- ## Installation
8
-
9
- === "RubyGems"
10
-
11
- ```bash
12
- gem install asgard
13
- ```
14
-
15
- === "Bundler"
16
-
17
- ```bash
18
- bundle add asgard
19
- ```
20
-
21
- Or add it manually to your `Gemfile`:
22
-
23
- ```ruby
24
- gem "asgard", "~> 0.1"
25
- ```
26
-
27
- then run `bundle install`.
28
-
29
- ---
30
-
31
- ## Verify the Installation
32
-
33
- ```bash
34
- asgard --version
35
- # 0.1.2
36
- ```
37
-
38
- ---
39
-
40
- ## Create Your First Task File
41
-
42
- Every Asgard project needs a `.loki` file at its root. This hidden file is both the project root marker (Asgard searches upward from CWD to find it) and the entry point for your tasks.
43
-
44
- ```bash
45
- # Create the root marker in your project directory
46
- touch .loki
47
- ```
48
-
49
- Open `.loki` in your editor and add a task:
50
-
51
- ```ruby
52
- class Tasks
53
- desc "Say hello to the world"
54
- def hello = puts "Hello, World!"
55
- end
56
- ```
57
-
58
- !!! note
59
- The `Tasks` class is pre-defined by the gem as `class Tasks < Asgard::Base`. You just reopen it — no `require` or superclass declaration needed.
60
-
61
- ---
62
-
63
- ## Run Your Task
64
-
65
- ```bash
66
- asgard hello
67
- # Hello, World!
68
- ```
69
-
70
- See all available tasks:
71
-
72
- ```bash
73
- asgard help
74
- ```
75
-
76
- See help for a specific task:
77
-
78
- ```bash
79
- asgard help hello
80
- ```
81
-
82
- ---
83
-
84
- ## Add a Parameter
85
-
86
- Positional parameters are declared directly in the method signature. Document them in the `desc` usage string:
87
-
88
- ```ruby
89
- class Tasks
90
- desc "greet NAME", "Greet someone by name"
91
- def greet(name = "World")
92
- puts "Hello, #{name}!"
93
- end
94
- end
95
- ```
96
-
97
- ```bash
98
- asgard greet
99
- # Hello, World!
100
-
101
- asgard greet Alice
102
- # Hello, Alice!
103
- ```
104
-
105
- ---
106
-
107
- ## Add an Option
108
-
109
- Use `method_option` (alias: `option`) to declare named flags:
110
-
111
- ```ruby
112
- class Tasks
113
- desc "greet NAME", "Greet someone by name"
114
- option :shout, aliases: "-s", type: :boolean, desc: "Uppercase the greeting"
115
- def greet(name = "World")
116
- msg = options[:shout] ? "HELLO, #{name.upcase}!" : "Hello, #{name}!"
117
- puts msg
118
- end
119
- end
120
- ```
121
-
122
- ```bash
123
- asgard greet Alice --shout
124
- # HELLO, ALICE!
125
- ```
126
-
127
- ---
128
-
129
- ## Multi-Loki Structure
130
-
131
- A large Asgard project might look like this:
132
-
133
- ```
134
- myproject/
135
- .loki ← root marker and entry point (may be empty or contain tasks)
136
- build.loki ← build-related and library dependency-related tasks
137
- deploy.loki ← deployment tasks
138
- qa.loki ← test and lint tasks
139
- ```
140
-
141
- Each `*.loki` file reopens `class Tasks`. To load them, call `import "*.loki"` at the top of `.loki` — files are loaded in alphabetical order. See [Task Files](task-files.md) for full details.
142
-
143
- ---
144
-
145
- ## Built-in Flags
146
-
147
- Every task automatically has four flags available, defined as `class_option` on `Tasks`:
148
-
149
- | Flag | Description |
150
- |---|---|
151
- | `--version` | Print the Asgard version and exit |
152
- | `--debug` | Set `$DEBUG = true` before the task runs |
153
- | `--verbose` | Set `$VERBOSE = true` before the task runs |
154
- | `--doctor` | Diagnose `.loki` resolution, imports, and task definitions for the CWD, then exit |
155
-
156
- ```bash
157
- asgard --version
158
- asgard hello --debug
159
- asgard hello --verbose
160
- asgard --doctor
161
- ```
162
-
163
- Inside a task body, use the `debug?` and `verbose?` predicates:
164
-
165
- ```ruby
166
- def hello
167
- puts "building..."
168
- sh "make --debug" if debug?
169
- end
170
- ```
171
-
172
- ---
173
-
174
- ## Next Steps
175
-
176
- - [Defining Tasks](tasks.md) — parameters, options, aliases, long_desc
177
- - [Dependencies](dependencies.md) — sequential, parallel, and mixed dependency graphs
178
- - [Variables](variables.md) — share values across tasks
179
- - [Shell Helpers](shell.md) — `sh`, `shebang`, and polyglot scripts
180
- - [Subcommands](subcommands.md) — group related tasks under a namespace
181
- - [Examples](examples.md) — working `.loki` files for every feature
data/docs/helpers.md DELETED
@@ -1,262 +0,0 @@
1
- # Helper Methods
2
-
3
- Not every method needs to be a CLI command. Asgard (via Thor) provides two mechanisms to define callable helper methods that are excluded from `asgard help` and cannot be invoked directly from the command line.
4
-
5
- ---
6
-
7
- ## Private Methods
8
-
9
- Methods declared after `private` are callable from any task in the same class but are invisible to Thor's command dispatcher. They will not appear in `--help` output and cannot be called from the CLI:
10
-
11
- ```ruby
12
- class Tasks
13
- desc "Compile and package"
14
- def build
15
- compile("src")
16
- package(app_version)
17
- end
18
-
19
- desc "Build and publish to RubyGems"
20
- def release
21
- build
22
- sh "gem push pkg/myapp-#{app_version}.gem"
23
- end
24
-
25
- private
26
-
27
- def compile(dir)
28
- sh "gcc -O2 -o bin/myapp #{dir}/*.c"
29
- end
30
-
31
- def package(ver)
32
- sh "tar czf pkg/myapp-#{ver}.tar.gz bin/"
33
- end
34
-
35
- def app_version
36
- `git describe --tags`.strip
37
- end
38
- end
39
- ```
40
-
41
- !!! note
42
- In Ruby, `private` applies to all methods defined after it in the same class body. You can group all helpers at the bottom of the class after a single `private` declaration.
43
-
44
- ---
45
-
46
- ## The `no_commands` Block
47
-
48
- Thor's `no_commands` block marks public methods as excluded from CLI discovery. Unlike `private`, these methods are still publicly accessible from Ruby code (e.g., from a subclass or a module). They are useful for methods that must be public for technical reasons but should not appear as commands:
49
-
50
- ```ruby
51
- class Tasks
52
- desc "Compile the project"
53
- def build
54
- puts "Revision: #{current_sha}"
55
- sh "rake build"
56
- end
57
-
58
- desc "Deploy to production"
59
- def deploy
60
- puts "Deploying revision #{current_sha}..."
61
- sh "cap production deploy"
62
- end
63
-
64
- no_commands do
65
- def current_sha
66
- `git rev-parse --short HEAD`.strip
67
- end
68
-
69
- def timestamp
70
- Time.now.strftime("%Y%m%d-%H%M%S")
71
- end
72
- end
73
- end
74
- ```
75
-
76
- ---
77
-
78
- ## The `helper` DSL Method
79
-
80
- `helper` is an Asgard DSL method that defines a helper available in **both class context and instance context** with a single declaration. It is the right tool when a value needs to be used inside a `header` or `footer` call (which execute at class load time) and also inside task instance methods.
81
-
82
- ### The problem it solves
83
-
84
- Thor task methods run as instance methods. Class-level DSL calls like `header` and `footer` run as class methods. A plain `def` only creates an instance method, so it cannot be called inside `header`. Conversely, `def self.name` only creates a class method, so it cannot be called inside a task body without `self.class.name`.
85
-
86
- The manual workaround is verbose:
87
-
88
- ```ruby
89
- class Tasks
90
- @@project ||= "myapp".freeze
91
-
92
- # class method for header/footer
93
- def self.version
94
- @@version ||= File.read("lib/myapp/version.rb").match(/VERSION\s*=\s*"([^"]+)"/)[1].freeze
95
- end
96
-
97
- # private instance method delegating to the class method
98
- no_commands do
99
- private def version = self.class.version
100
- end
101
-
102
- header "#{@@project} v#{version}"
103
-
104
- desc "Show version"
105
- def show_version = puts version
106
- end
107
- ```
108
-
109
- `helper` replaces those six lines with one:
110
-
111
- ```ruby
112
- class Tasks
113
- @@project ||= "myapp".freeze
114
-
115
- helper(:version) {
116
- @@version ||= File.read("lib/myapp/version.rb").match(/VERSION\s*=\s*"([^"]+)"/)[1].freeze
117
- }
118
-
119
- header "#{@@project} v#{version}"
120
-
121
- desc "Show version"
122
- def show_version = puts version
123
- end
124
- ```
125
-
126
- ### Arguments
127
-
128
- `helper` supports any argument signature valid in a Ruby method definition: positional, keyword, default values, and blocks.
129
-
130
- ```ruby
131
- # No arguments
132
- helper(:project_root) { loki_up.parent.to_s }
133
-
134
- # Positional arguments
135
- helper(:gem_path) { |name| "lib/#{name}/version.rb" }
136
-
137
- # Positional with default
138
- helper(:tag_prefix) { |sep = "-"| "#{@@project}#{sep}" }
139
-
140
- # Positional and keyword arguments
141
- helper(:format_version) { |name, version, prefix: "v", separator: "-"|
142
- "#{prefix}#{name}#{separator}#{version}"
143
- }
144
- ```
145
-
146
- Wrong argument counts or unknown keyword names raise the same `ArgumentError` Ruby raises for any method call — no special error handling needed.
147
-
148
- ### Visibility
149
-
150
- `helper`-defined methods are:
151
-
152
- - **Excluded from `asgard help`** — they never appear as commands
153
- - **Blocked from CLI invocation** — cannot be called directly from the command line
154
- - **Private on the instance side** — not accessible from outside the class
155
-
156
- ### When to use `helper`
157
-
158
- Use `helper` when the value or computation must be available in both a class-level DSL call (`header`, `footer`, a `@@var` initializer) and inside task instance methods. For helpers that are only needed inside task bodies, a plain `private` method is simpler.
159
-
160
- ---
161
-
162
- ## Choosing Between `private` and `no_commands`
163
-
164
- | | `private` | `no_commands` |
165
- |---|---|---|
166
- | Hidden from `--help` | Yes | Yes |
167
- | Blocked from CLI | Yes | Yes |
168
- | Accessible from subclass | No | Yes |
169
- | Accessible from module include | No | Yes |
170
- | Ruby idiom | Familiar | Thor-specific |
171
-
172
- For most helpers, `private` is the right choice. Use `no_commands` when the helper must remain technically public (e.g., it will be inherited by a subcommand class).
173
-
174
- ---
175
-
176
- ## Sharing Helpers Across Files
177
-
178
- ### Within a project
179
-
180
- Extract shared helpers into a plain Ruby module and load it from `.loki` using `require_relative`:
181
-
182
- ```ruby
183
- # shared/helpers.rb
184
- module BuildHelpers
185
- private
186
-
187
- def compile(dir)
188
- sh "gcc -O2 -o bin/myapp #{dir}/*.c"
189
- end
190
-
191
- def dist_path(ver)
192
- "pkg/myapp-#{ver}.tar.gz"
193
- end
194
- end
195
- ```
196
-
197
- ```ruby
198
- # .loki
199
- require_relative "shared/helpers"
200
-
201
- class Tasks
202
- include BuildHelpers
203
-
204
- desc "Compile the project"
205
- def build = compile("src")
206
-
207
- desc "Create distribution archive"
208
- def package = sh "tar czf #{dist_path(app_version)} bin/"
209
- end
210
- ```
211
-
212
- Because `include` in the class body makes the module methods available as instance methods, and they are declared `private` inside the module, they remain invisible to Thor.
213
-
214
- !!! tip
215
- Helpers in a shared module can call `sh`, `shebang`, and other Asgard DSL methods because those are included in `Tasks` (via `Asgard::Base` and `Asgard::Shell`) and are available in `self` when the module method is invoked.
216
-
217
- ### Across projects with `import_up`
218
-
219
- If helpers are defined as tasks in a shared `.loki` file, use `import_up` to load them from any sub-project without knowing the absolute path:
220
-
221
- ```
222
- ~/sandbox/
223
- shared_helpers.loki ← defines helper tasks available to all sub-projects
224
- projectA/
225
- .loki
226
- projectB/
227
- .loki
228
- ```
229
-
230
- ```ruby
231
- # projectA/.loki (and identically in projectB/.loki)
232
- import_up "shared_helpers.loki"
233
-
234
- class Tasks
235
- # shared tasks are already defined in Tasks by here
236
- end
237
- ```
238
-
239
- `import_up` walks up from `Dir.pwd` until it finds `shared_helpers.loki`, then loads it. It returns `false` without raising if the file is not found, making it safe to use in projects where the shared file may not always be present.
240
-
241
- ---
242
-
243
- ## Helper Methods in Subcommands
244
-
245
- Subcommand classes that inherit from `Tasks` also inherit all private helpers and `no_commands` methods defined on `Tasks`. You can also define helpers local to the subcommand class:
246
-
247
- ```ruby
248
- class DeployCommands < Tasks
249
- desc "Deploy to staging"
250
- def staging = deploy_to("staging")
251
-
252
- desc "Deploy to production"
253
- def production = deploy_to("production")
254
-
255
- private
256
-
257
- def deploy_to(env)
258
- sh "cap #{env} deploy REV=#{current_sha}"
259
- end
260
- # current_sha is inherited from Tasks if defined there
261
- end
262
- ```
data/docs/index.md DELETED
@@ -1,87 +0,0 @@
1
- # Asgard
2
-
3
- <table>
4
- <tr>
5
- <td width="40%" align="center" valign="top">
6
- <img src="assets/images/asgard.jpg" alt="Asgard" width="300"><br>
7
- <em>"Loki collects the tricks.<br>Thor of Asgard runs them."</em>
8
- </td>
9
- <td width="60%" valign="top">
10
- <strong>Key Features</strong>
11
- <ul>
12
- <li><strong>Thor-Powered CLI</strong> — every Thor DSL feature available inside <code>.loki</code> task files</li>
13
- <li><strong>Task Dependencies</strong> — sequential, parallel, and mixed dependency graphs via <code>depends_on</code></li>
14
- <li><strong>Concurrent Execution</strong> — parallel task groups run in native Ruby threads</li>
15
- <li><strong>Subcommands</strong> — group related tasks under a named namespace</li>
16
- <li><strong>Variables</strong> — shared configuration via Ruby class variables (<code>@@name</code>), visible across all tasks and subcommands</li>
17
- <li><strong>Shell Helpers</strong> — <code>sh</code> for any shell command or heredoc; <code>shebang</code> for polyglot scripts</li>
18
- <li><strong>Dotenv Support</strong> — load <code>.env</code> files into the environment with <code>dotenv</code></li>
19
- <li><strong>Auto-Discovery</strong> — <code>.loki</code> root marker searched from CWD upward through parent directories</li>
20
- <li><strong>Multi-File Tasks</strong> — split tasks across <code>*.loki</code> files loaded via <code>import</code></li>
21
- <li><strong>Scheduled Tasks</strong> — run any task periodically under launchd (macOS) or systemd timers (Linux) with <code>schedule</code> and <code>asgard schedule install</code></li>
22
- <li><strong>Watched Files</strong> — run any task when files change with <code>watch</code> and <code>asgard watcher start</code>; the task reads what changed through <code>changes</code></li>
23
- <li><strong>Built-in Flags</strong> — <code>--version</code>, <code>--debug</code>, <code>--verbose</code>, and <code>--doctor</code> available on every task</li>
24
- </ul>
25
- </td>
26
- </tr>
27
- </table>
28
-
29
- Asgard is a [Thor](https://github.com/rails/thor)-based task runner for Ruby projects. Define tasks in `.loki` files, declare dependencies between them, and let Asgard handle ordering and concurrent execution. Anything Thor can do — subcommands, typed options, argument validation — is available inside a `.loki` file.
30
-
31
- ---
32
-
33
- ## Quick Start
34
-
35
- ```bash
36
- # Install
37
- gem install asgard
38
-
39
- # Create your project root marker
40
- touch .loki
41
-
42
- # Add your first task
43
- cat >> .loki << 'EOF'
44
- class Tasks
45
- desc "Say hello"
46
- def hello = puts "Hello from Asgard!"
47
- end
48
- EOF
49
-
50
- # Run it
51
- asgard hello
52
- ```
53
-
54
- ---
55
-
56
- ## How It Works
57
-
58
- Asgard searches upward from your current directory for a `.loki` file. That file marks the project root. Additional `*.loki` files in the same directory can be loaded via `import "*.loki"` at the top of `.loki`. All task files reopen `class Tasks`, which is pre-defined by the gem as a subclass of `Asgard::Base` (itself a Thor subclass).
59
-
60
- The full Thor DSL is available: `desc`, `method_option`, `class_option`, `long_desc`, `argument`, `default_task`, `map`, and `subcommand` all work exactly as documented in Thor — with Asgard's own `depends_on`, `sh`, `shebang`, and `dotenv` layered on top.
61
-
62
- ---
63
-
64
- ## Documentation
65
-
66
- | Section | Description |
67
- |---|---|
68
- | [Getting Started](getting-started.md) | Install, create your first `.loki`, run your first task |
69
- | [Defining Tasks](tasks.md) | Parameters, options, long_desc, aliases, default_task |
70
- | [Dependencies](dependencies.md) | Sequential, parallel, and mixed dependency graphs |
71
- | [Variables](variables.md) | Static and lazy-evaluated task variables |
72
- | [Helper Methods](helpers.md) | Private helpers and the `no_commands` block |
73
- | [Options & Flags](options.md) | class_option, built-in flags, debug? and verbose? |
74
- | [Subcommands](subcommands.md) | Grouping tasks under a namespace |
75
- | [Shell Helpers](shell.md) | `sh`, `shebang`, and supported interpreters |
76
- | [Environment](environment.md) | Loading `.env` files with `dotenv` |
77
- | [Task Files](task-files.md) | `.loki` root marker, `import`, multi-file layout |
78
- | [API Reference](api.md) | Module methods, DSL methods, error classes |
79
- | [Examples](examples.md) | Working `.loki` files for every feature |
80
- | [Changelog](changelog.md) | Release history |
81
-
82
- ---
83
-
84
- ## Requirements
85
-
86
- - Ruby >= 3.2.0
87
- - Dependencies: [thor](https://github.com/rails/thor) `~> 1.0`, [dotenv](https://github.com/bkeepers/dotenv) `~> 3.0`