asgard 0.1.1 → 0.2.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.
@@ -0,0 +1,180 @@
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 "hello", "Say hello to the world"
54
+ def hello = sh 'echo "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
+ # echo "Hello, World!"
68
+ # Hello, World!
69
+ ```
70
+
71
+ See all available tasks:
72
+
73
+ ```bash
74
+ asgard help
75
+ ```
76
+
77
+ See help for a specific task:
78
+
79
+ ```bash
80
+ asgard help hello
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Add a Parameter
86
+
87
+ Positional parameters are declared directly in the method signature. Document them in the `desc` usage string:
88
+
89
+ ```ruby
90
+ class Tasks
91
+ desc "greet NAME", "Greet someone by name"
92
+ def greet(name = "World")
93
+ sh "echo 'Hello, #{name}!'"
94
+ end
95
+ end
96
+ ```
97
+
98
+ ```bash
99
+ asgard greet
100
+ # Hello, World!
101
+
102
+ asgard greet Alice
103
+ # Hello, Alice!
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Add an Option
109
+
110
+ Use `method_option` (alias: `option`) to declare named flags:
111
+
112
+ ```ruby
113
+ class Tasks
114
+ desc "greet NAME", "Greet someone by name"
115
+ option :shout, aliases: "-s", type: :boolean, desc: "Uppercase the greeting"
116
+ def greet(name = "World")
117
+ msg = options[:shout] ? "HELLO, #{name.upcase}!" : "Hello, #{name}!"
118
+ sh "echo '#{msg}'"
119
+ end
120
+ end
121
+ ```
122
+
123
+ ```bash
124
+ asgard greet Alice --shout
125
+ # HELLO, ALICE!
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Multi-Loki Structure
131
+
132
+ A large Asgard project might look like this:
133
+
134
+ ```
135
+ myproject/
136
+ .loki ← root marker and entry point (may be empty or contain tasks)
137
+ build.loki ← build-related and library dependency-related tasks
138
+ deploy.loki ← deployment tasks
139
+ qa.loki ← test and lint tasks
140
+ ```
141
+
142
+ Each `*.loki` file reopens `class Tasks`. To load them, pass `--auto-load` to the `asgard` command — they are loaded alphabetically before `.loki`. See [Task Files](task-files.md) for full details.
143
+
144
+ ---
145
+
146
+ ## Built-in Flags
147
+
148
+ Every task automatically has three flags available, defined as `class_option` on `Tasks`:
149
+
150
+ | Flag | Description |
151
+ |---|---|
152
+ | `--version` | Print the Asgard version and exit |
153
+ | `--debug` | Set `$DEBUG = true` before the task runs |
154
+ | `--verbose` | Set `$VERBOSE = true` before the task runs |
155
+
156
+ ```bash
157
+ asgard --version
158
+ asgard hello --debug
159
+ asgard hello --verbose
160
+ ```
161
+
162
+ Inside a task body, use the `debug?` and `verbose?` predicates:
163
+
164
+ ```ruby
165
+ def hello
166
+ sh "echo 'building...'"
167
+ sh "make --debug" if debug?
168
+ end
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Next Steps
174
+
175
+ - [Defining Tasks](tasks.md) — parameters, options, aliases, long_desc
176
+ - [Dependencies](dependencies.md) — sequential, parallel, and mixed dependency graphs
177
+ - [Variables](variables.md) — share values across tasks
178
+ - [Shell Helpers](shell.md) — `sh`, `shebang`, and polyglot scripts
179
+ - [Subcommands](subcommands.md) — group related tasks under a namespace
180
+ - [Examples](examples.md) — working `.loki` files for every feature
data/docs/helpers.md ADDED
@@ -0,0 +1,154 @@
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 "build", "Compile and package"
14
+ def build
15
+ compile("src")
16
+ package(app_version)
17
+ end
18
+
19
+ desc "release", "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 "build", "Compile the project"
53
+ def build
54
+ puts "Revision: #{current_sha}"
55
+ sh "rake build"
56
+ end
57
+
58
+ desc "deploy", "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
+ `var`-declared variables are also implemented using `no_commands` internally, which is why they appear as callable methods but not as CLI commands.
77
+
78
+ ---
79
+
80
+ ## Choosing Between `private` and `no_commands`
81
+
82
+ | | `private` | `no_commands` |
83
+ |---|---|---|
84
+ | Hidden from `--help` | Yes | Yes |
85
+ | Blocked from CLI | Yes | Yes |
86
+ | Accessible from subclass | No | Yes |
87
+ | Accessible from module include | No | Yes |
88
+ | Ruby idiom | Familiar | Thor-specific |
89
+
90
+ 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).
91
+
92
+ ---
93
+
94
+ ## Sharing Helpers Across Files
95
+
96
+ Extract shared helpers into a plain Ruby module and load it from `.loki` using `require_relative`:
97
+
98
+ ```ruby
99
+ # shared/helpers.rb
100
+ module BuildHelpers
101
+ private
102
+
103
+ def compile(dir)
104
+ sh "gcc -O2 -o bin/myapp #{dir}/*.c"
105
+ end
106
+
107
+ def dist_path(ver)
108
+ "pkg/myapp-#{ver}.tar.gz"
109
+ end
110
+ end
111
+ ```
112
+
113
+ ```ruby
114
+ # .loki
115
+ require_relative "shared/helpers"
116
+
117
+ class Tasks
118
+ include BuildHelpers
119
+
120
+ desc "build", "Compile the project"
121
+ def build = compile("src")
122
+
123
+ desc "package", "Create distribution archive"
124
+ def package = sh "tar czf #{dist_path(app_version)} bin/"
125
+ end
126
+ ```
127
+
128
+ 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.
129
+
130
+ !!! tip
131
+ 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.
132
+
133
+ ---
134
+
135
+ ## Helper Methods in Subcommands
136
+
137
+ 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:
138
+
139
+ ```ruby
140
+ class DeployCommands < Tasks
141
+ desc "staging", "Deploy to staging"
142
+ def staging = deploy_to("staging")
143
+
144
+ desc "production", "Deploy to production"
145
+ def production = deploy_to("production")
146
+
147
+ private
148
+
149
+ def deploy_to(env)
150
+ sh "cap #{env} deploy REV=#{current_sha}"
151
+ end
152
+ # current_sha is inherited from Tasks if defined there
153
+ end
154
+ ```
data/docs/index.md ADDED
@@ -0,0 +1,85 @@
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> — static values and lazy-evaluated lambdas via <code>var</code></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 on demand with <code>--auto-load</code></li>
21
+ <li><strong>Built-in Flags</strong> — <code>--version</code>, <code>--debug</code>, and <code>--verbose</code> available on every task</li>
22
+ </ul>
23
+ </td>
24
+ </tr>
25
+ </table>
26
+
27
+ 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.
28
+
29
+ ---
30
+
31
+ ## Quick Start
32
+
33
+ ```bash
34
+ # Install
35
+ gem install asgard
36
+
37
+ # Create your project root marker
38
+ touch .loki
39
+
40
+ # Add your first task
41
+ cat >> .loki << 'EOF'
42
+ class Tasks
43
+ desc "hello", "Say hello"
44
+ def hello = sh 'echo "Hello from Asgard!"'
45
+ end
46
+ EOF
47
+
48
+ # Run it
49
+ asgard hello
50
+ ```
51
+
52
+ ---
53
+
54
+ ## How It Works
55
+
56
+ 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 by passing `--auto-load` to the `asgard` command. All task files reopen `class Tasks`, which is pre-defined by the gem as a subclass of `Asgard::Base` (itself a Thor subclass).
57
+
58
+ 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`, `var`, `sh`, `shebang`, and `dotenv` layered on top.
59
+
60
+ ---
61
+
62
+ ## Documentation
63
+
64
+ | Section | Description |
65
+ |---|---|
66
+ | [Getting Started](getting-started.md) | Install, create your first `.loki`, run your first task |
67
+ | [Defining Tasks](tasks.md) | Parameters, options, long_desc, aliases, default_task |
68
+ | [Dependencies](dependencies.md) | Sequential, parallel, and mixed dependency graphs |
69
+ | [Variables](variables.md) | Static and lazy-evaluated task variables |
70
+ | [Helper Methods](helpers.md) | Private helpers and the `no_commands` block |
71
+ | [Options & Flags](options.md) | class_option, built-in flags, debug? and verbose? |
72
+ | [Subcommands](subcommands.md) | Grouping tasks under a namespace |
73
+ | [Shell Helpers](shell.md) | `sh`, `shebang`, and supported interpreters |
74
+ | [Environment](environment.md) | Loading `.env` files with `dotenv` |
75
+ | [Task Files](task-files.md) | `.loki` root marker, `--auto-load`, multi-file layout |
76
+ | [API Reference](api.md) | Module methods, DSL methods, error classes |
77
+ | [Examples](examples.md) | Working `.loki` files for every feature |
78
+ | [Changelog](changelog.md) | Release history |
79
+
80
+ ---
81
+
82
+ ## Requirements
83
+
84
+ - Ruby >= 3.2.0
85
+ - Dependencies: [thor](https://github.com/rails/thor) `~> 1.0`, [dagwood](https://rubygems.org/gems/dagwood) `~> 1.0`, [dotenv](https://github.com/bkeepers/dotenv) `~> 3.0`
data/docs/options.md ADDED
@@ -0,0 +1,180 @@
1
+ # Options & Flags
2
+
3
+ Asgard tasks use the full Thor option system. Options declared with `method_option` (alias: `option`) apply to a single task. Options declared with `class_option` apply to every task in the class. Asgard ships with three built-in `class_option` declarations on `Tasks`: `--debug`, `--verbose`, and `--version`.
4
+
5
+ ---
6
+
7
+ ## Per-Task Options
8
+
9
+ `method_option` (or its alias `option`) declares an option for the immediately following task:
10
+
11
+ ```ruby
12
+ class Tasks
13
+ desc "deploy ENV", "Deploy to ENV"
14
+ method_option :branch,
15
+ aliases: "-b",
16
+ type: :string,
17
+ default: "main",
18
+ desc: "Git branch to deploy"
19
+ method_option :dry_run,
20
+ aliases: "-n",
21
+ type: :boolean,
22
+ default: false,
23
+ desc: "Print commands without running"
24
+ def deploy(env = "staging")
25
+ if options[:dry_run]
26
+ puts "Would deploy #{options[:branch]} to #{env}"
27
+ else
28
+ sh "cap #{env} deploy BRANCH=#{options[:branch]}"
29
+ end
30
+ end
31
+ end
32
+ ```
33
+
34
+ Access option values inside the task body via `options[:name]` (a hash keyed by symbol).
35
+
36
+ ---
37
+
38
+ ## Class Options (Shared Across All Tasks)
39
+
40
+ `class_option` defines an option available on every task in the class. Add your own to complement the built-in ones:
41
+
42
+ ```ruby
43
+ class Tasks
44
+ class_option :dry_run,
45
+ aliases: "-n",
46
+ type: :boolean,
47
+ default: false,
48
+ desc: "Print commands without running"
49
+
50
+ class_option :env,
51
+ type: :string,
52
+ default: "development",
53
+ enum: %w[development staging production],
54
+ desc: "Target environment"
55
+
56
+ desc "deploy", "Deploy the application"
57
+ def deploy
58
+ if options[:dry_run]
59
+ puts "Would deploy to #{options[:env]}"
60
+ else
61
+ sh "cap #{options[:env]} deploy"
62
+ end
63
+ end
64
+
65
+ desc "migrate", "Run database migrations"
66
+ def migrate
67
+ sh "rails db:migrate RAILS_ENV=#{options[:env]}"
68
+ end
69
+ end
70
+ ```
71
+
72
+ Both `deploy` and `migrate` automatically accept `--dry-run` and `--env`.
73
+
74
+ ---
75
+
76
+ ## Built-in Flags
77
+
78
+ `Tasks` ships with three built-in class options and a version flag:
79
+
80
+ ### `--version`
81
+
82
+ Prints `Asgard::VERSION` and exits. Implemented as the `_version` method with the `_` prefix convention (gem-owned, blocked from direct CLI invocation):
83
+
84
+ ```bash
85
+ asgard --version
86
+ # 0.1.2
87
+ ```
88
+
89
+ ### `--debug`
90
+
91
+ A `class_option :debug` of type `:boolean`. When passed, sets `$DEBUG = true` before the task body runs (via the `invoke_command` hook in `Asgard::Base`):
92
+
93
+ ```bash
94
+ asgard build --debug
95
+ ```
96
+
97
+ Inside the task, use the `debug?` predicate:
98
+
99
+ ```ruby
100
+ def build
101
+ sh "rake build"
102
+ sh "rake build --trace" if debug?
103
+ end
104
+ ```
105
+
106
+ ### `--verbose`
107
+
108
+ A `class_option :verbose` of type `:boolean`. When passed, sets `$VERBOSE = true` before the task body runs:
109
+
110
+ ```bash
111
+ asgard test --verbose
112
+ ```
113
+
114
+ Inside the task, use the `verbose?` predicate:
115
+
116
+ ```ruby
117
+ def test
118
+ flags = verbose? ? "--verbose" : ""
119
+ sh "bundle exec rake test #{flags}"
120
+ end
121
+ ```
122
+
123
+ ---
124
+
125
+ ## `debug?` and `verbose?` Predicates
126
+
127
+ Both are private methods on `Tasks`, thin wrappers around the global variables:
128
+
129
+ ```ruby
130
+ private
131
+
132
+ def debug? = $DEBUG
133
+ def verbose? = $VERBOSE
134
+ ```
135
+
136
+ They are available in every task body and in subcommand classes that inherit from `Tasks`. Because `--debug` and `--verbose` are `class_option` declarations (not standalone commands), they work as modifiers alongside any task:
137
+
138
+ ```bash
139
+ asgard build --debug --verbose
140
+ asgard deploy production --verbose
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Option Types Reference
146
+
147
+ | Type | CLI Example | Ruby Value |
148
+ |---|---|---|
149
+ | `:string` | `--branch main` | `"main"` |
150
+ | `:boolean` | `--force` / `--no-force` | `true` / `false` |
151
+ | `:numeric` | `--count 3` | `3` |
152
+ | `:array` | `--tags foo bar baz` | `["foo", "bar", "baz"]` |
153
+ | `:hash` | `--vars KEY:val FOO:bar` | `{"KEY"=>"val", "FOO"=>"bar"}` |
154
+
155
+ ---
156
+
157
+ ## Option Keys Reference
158
+
159
+ | Key | Applies to | Description |
160
+ |---|---|---|
161
+ | `aliases` | `method_option`, `class_option` | Short-form flag string, e.g. `"-b"` |
162
+ | `type` | `method_option`, `class_option` | One of the five types above |
163
+ | `default` | `method_option`, `class_option` | Value used when the flag is omitted |
164
+ | `required` | `method_option` | Raises an error if the flag is missing |
165
+ | `desc` | `method_option`, `class_option` | One-line description shown in help |
166
+ | `enum` | `method_option`, `class_option` | Allowed values; validated by Thor |
167
+ | `banner` | `method_option` | Placeholder shown in help for the value slot |
168
+
169
+ ---
170
+
171
+ ## `_` Prefix Convention
172
+
173
+ Methods whose names start with `_` are considered gem-owned in Asgard's naming convention. `run!` guards against invoking them directly from the CLI:
174
+
175
+ ```bash
176
+ asgard _version
177
+ # asgard: unknown command '_version'
178
+ ```
179
+
180
+ If you define your own methods on `Tasks`, avoid the `_` prefix to prevent them from being silently blocked.