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.
@@ -0,0 +1,407 @@
1
+ # Task Files
2
+
3
+ Asgard uses a convention-based file discovery system. A hidden `.loki` file marks the project root. Everything else — loading sibling files, shared task libraries, monorepo-wide tasks — is controlled explicitly from inside your `.loki` file using the `import` and `import_up` Kernel methods.
4
+
5
+ ---
6
+
7
+ ## The `.loki` Root Marker
8
+
9
+ When you run `asgard`, it searches for a `.loki` file starting in the current working directory and walking upward through parent directories until it finds one or reaches the filesystem root. The first `.loki` file found marks the project root and is the only file Asgard loads automatically.
10
+
11
+ ```
12
+ myproject/
13
+ .loki ← found regardless of which subdirectory you're in
14
+ src/
15
+ app/
16
+ # asgard still works from here
17
+ ```
18
+
19
+ The `.loki` file can be completely empty — its presence alone marks the project root. It can also contain task definitions, `import` calls, or any valid Ruby.
20
+
21
+ ---
22
+
23
+ ## Loading Files with `import`
24
+
25
+ `import` is a Kernel method available everywhere in Ruby — at the top level of `.loki` files, inside class bodies, and inside task method bodies. It loads `.loki` files with `require`-like idempotency: a file is loaded at most once per process, no matter how many times `import` is called with the same path.
26
+
27
+ ### Single file by absolute path
28
+
29
+ ```ruby
30
+ import "/home/shared/gem_tasks.loki"
31
+ ```
32
+
33
+ ### Single file by relative path
34
+
35
+ Relative paths are resolved relative to the **caller's file location**, like `require_relative`:
36
+
37
+ ```ruby
38
+ # .loki — relative to this file's directory
39
+ import "build.loki"
40
+ import "../shared/gem_tasks.loki"
41
+ import "tasks/ci.loki"
42
+ ```
43
+
44
+ ### All files in the same directory (glob)
45
+
46
+ ```ruby
47
+ import "*.loki" # all *.loki files in the same directory as the calling file
48
+ ```
49
+
50
+ `*.loki` never matches `.loki` (the dotfile entry point) — Ruby's `Dir.glob` excludes dotfiles from `*` patterns by default.
51
+
52
+ ### All files recursively (recursive glob)
53
+
54
+ ```ruby
55
+ import "**/*.loki" # every .loki file in this directory and all subdirectories
56
+ ```
57
+
58
+ ### Specific named files
59
+
60
+ ```ruby
61
+ import "gem_tasks.loki"
62
+ import "ci_tasks.loki"
63
+ ```
64
+
65
+ ### Combining patterns
66
+
67
+ ```ruby
68
+ # .loki
69
+ import "*.loki" # load all siblings
70
+ import "../shared/*.loki" # load a parent-level shared library
71
+ ```
72
+
73
+ ### Typical `.loki` entry point
74
+
75
+ ```ruby
76
+ # .loki
77
+ import "*.loki" # load all sibling task files
78
+
79
+ class Tasks
80
+ # any top-level task definitions or overrides
81
+ end
82
+ ```
83
+
84
+ ### Return value
85
+
86
+ `import` returns `true` if at least one file was newly loaded, `false` if all files were already loaded or no glob pattern matched any file. If a specific (non-glob) file does not exist, `import` raises `LoadError`.
87
+
88
+ ```ruby
89
+ import("gem_tasks.loki") ? "loaded now" : "already loaded"
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Finding Files with `loki_up`
95
+
96
+ `loki_up(name = ".loki")` searches `Dir.pwd` and each ancestor directory for a file with the given name, returning its absolute path or `nil`. It does **not** load the file — it only finds it.
97
+
98
+ Despite the name, `loki_up` is not limited to `.loki` files — it will locate any file by name. This makes it useful for finding shared config files, `.env` files, or any other resource that lives somewhere up the directory tree:
99
+
100
+ ```ruby
101
+ loki_up # finds .loki (the project root marker)
102
+ loki_up("gem_tasks.loki") # finds gem_tasks.loki in CWD or any ancestor
103
+ loki_up(".env") # finds the nearest .env file up the tree
104
+ loki_up("VERSION") # finds a VERSION file in CWD or any ancestor
105
+ ```
106
+
107
+ Use `loki_up` when you need the path for other purposes, or to check whether a file exists before deciding to load it:
108
+
109
+ ```ruby
110
+ if (path = loki_up("gem_tasks.loki"))
111
+ import path
112
+ end
113
+
114
+ # Pass the located .env to dotenv — works from any subdirectory
115
+ dotenv loki_up(".env") || ".env"
116
+ ```
117
+
118
+ `loki_up` accepts exact filenames only. Glob patterns are not expanded by `loki_up` — use `import_up` for glob-aware ancestor search.
119
+
120
+ ---
121
+
122
+ ## Loading Files Found up the Tree with `import_up`
123
+
124
+ `import_up(name = ".loki")` combines `loki_up` and `import` into a single call. It finds the file (or files) up the ancestor chain and loads them.
125
+
126
+ ### Exact filename
127
+
128
+ ```ruby
129
+ import_up "gem_tasks.loki"
130
+ ```
131
+
132
+ Walks up from `Dir.pwd` until it finds `gem_tasks.loki`, then loads it. Returns `false` if not found anywhere.
133
+
134
+ ### Glob pattern
135
+
136
+ ```ruby
137
+ import_up "*.loki"
138
+ ```
139
+
140
+ Walks up from `Dir.pwd` and stops at the **first ancestor directory** that contains any `*.loki` files, loading all of them. It does not aggregate matches from multiple levels — it loads only the nearest match, then stops.
141
+
142
+ ```
143
+ ~/sandbox/
144
+ gem_tasks.loki ← loaded by import_up("*.loki") from ~/sandbox/myproject/sub/
145
+ ci_tasks.loki ← also loaded — same directory as the first match
146
+ myproject/
147
+ .loki
148
+ sub/
149
+ # Dir.pwd here; import_up("*.loki") finds ~/sandbox/*.loki files
150
+ ```
151
+
152
+ ### Return value
153
+
154
+ Returns `true` if any file was newly loaded, `false` if the file was not found or was already loaded.
155
+
156
+ ### Conditional load
157
+
158
+ Since `import_up` returns `false` when a file is not found (rather than raising), it composes naturally with `||`:
159
+
160
+ ```ruby
161
+ import_up("project_tasks.loki") || import_up("gem_tasks.loki")
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Idempotency
167
+
168
+ Both `import` and `import_up` track loaded files in Ruby's `$LOADED_FEATURES`. A second call with the same path is a no-op and returns `false`. This means:
169
+
170
+ - You can call `import "*.loki"` from both `.loki` and a shared task file without double-loading.
171
+ - `import_up("gem_tasks.loki")` from two different projects in the same process each load their nearest match once.
172
+ - Swapping `require` for `import` in a `.loki` file gives the same once-per-process guarantee.
173
+
174
+ ---
175
+
176
+ ## Verbose and Debug Feedback
177
+
178
+ `import` and `import_up` emit diagnostic messages to stderr when the `verbose?` or `debug?` flags are active (set via `--verbose` or `--debug` on the CLI, or by setting `$VERBOSE`/`$DEBUG` directly):
179
+
180
+ | Flag | `import` output | `import_up` output |
181
+ |---|---|---|
182
+ | `--verbose` | Prints each file path as it is loaded | Prints `name → /full/path` when found |
183
+ | `--debug` | Same as verbose, plus prints a skip message for already-loaded files | Same as verbose, plus prints `name not found` when the search comes up empty |
184
+
185
+ ```
186
+ $ asgard --verbose build
187
+ import: /home/user/myproject/build.loki
188
+ import: /home/user/myproject/test.loki
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Loading Patterns
194
+
195
+ ### Single-file project
196
+
197
+ All tasks in `.loki`, nothing else:
198
+
199
+ ```ruby
200
+ # .loki
201
+ class Tasks
202
+ @@app ||= "myapp".freeze
203
+
204
+ desc "Compile the project"
205
+ def build = sh "rake build"
206
+
207
+ desc "Run the test suite"
208
+ def test = sh "rake test"
209
+
210
+ desc "Build and push the gem"
211
+ def release = sh "gem push pkg/#{@@app}-*.gem"
212
+ end
213
+ ```
214
+
215
+ ### Multi-file project
216
+
217
+ Split tasks across files by concern. Load them all from `.loki` with a glob:
218
+
219
+ ```
220
+ myproject/
221
+ .loki ← entry point; imports siblings
222
+ build.loki ← build tasks
223
+ deploy.loki ← deploy tasks
224
+ test.loki ← test tasks
225
+ ```
226
+
227
+ ```ruby
228
+ # .loki
229
+ import "*.loki"
230
+ ```
231
+
232
+ ```ruby
233
+ # build.loki
234
+ class Tasks
235
+ desc "Compile the project"
236
+ def build = sh "rake build"
237
+ end
238
+ ```
239
+
240
+ ```ruby
241
+ # test.loki
242
+ class Tasks
243
+ depends_on :build
244
+ desc "Run the test suite"
245
+ def test = sh "bundle exec rake test"
246
+ end
247
+ ```
248
+
249
+ ```ruby
250
+ # deploy.loki
251
+ class Tasks
252
+ depends_on :test
253
+ desc "Deploy to production"
254
+ def deploy = sh "cap production deploy"
255
+ end
256
+ ```
257
+
258
+ Files loaded via glob are sorted alphabetically by `Dir.glob`, so `build.loki` loads before `test.loki`. Tasks defined in earlier files are available to later files via `depends_on`.
259
+
260
+ ### Controlled load order
261
+
262
+ When alphabetical order does not match your dependency order, import explicitly:
263
+
264
+ ```ruby
265
+ # .loki
266
+ import "infra.loki" # must be first
267
+ import "build.loki" # depends on infra
268
+ import "deploy.loki" # depends on build
269
+ ```
270
+
271
+ ### Shared task library in a monorepo
272
+
273
+ Place shared tasks in a parent directory and load them from any sub-project:
274
+
275
+ ```
276
+ ~/sandbox/
277
+ gem_tasks.loki ← shared: build, install, release tasks for any gem
278
+ myproject/
279
+ .loki ← loads gem_tasks.loki via import_up
280
+ other_project/
281
+ .loki ← also loads gem_tasks.loki via import_up
282
+ ```
283
+
284
+ ```ruby
285
+ # myproject/.loki
286
+ import_up "gem_tasks.loki" # finds ~/sandbox/gem_tasks.loki
287
+
288
+ class Tasks
289
+ # project-specific overrides here
290
+ end
291
+ ```
292
+
293
+ ### Conditional shared library
294
+
295
+ ```ruby
296
+ # .loki
297
+ import_up("ci_tasks.loki") || import_up("gem_tasks.loki")
298
+ ```
299
+
300
+ Loads `ci_tasks.loki` if found up the tree, otherwise falls back to `gem_tasks.loki`.
301
+
302
+ ### Subcommand classes across files
303
+
304
+ Define subcommand classes in separate files and register them in `.loki`:
305
+
306
+ ```
307
+ myproject/
308
+ .loki
309
+ db.loki
310
+ server.loki
311
+ ```
312
+
313
+ ```ruby
314
+ # db.loki
315
+ class DBCommands < Tasks
316
+ desc "Run migrations"
317
+ def migrate = sh "rails db:migrate"
318
+ end
319
+ ```
320
+
321
+ ```ruby
322
+ # server.loki
323
+ class ServerCommands < Tasks
324
+ desc "Start the server"
325
+ def start = sh "rails server"
326
+ end
327
+ ```
328
+
329
+ ```ruby
330
+ # .loki
331
+ import "*.loki" # db.loki and server.loki load first
332
+
333
+ class Tasks
334
+ desc "db SUBCOMMAND", "Manage the database"; subcommand "db", DBCommands
335
+ desc "server SUBCOMMAND", "Manage the server"; subcommand "server", ServerCommands
336
+ end
337
+ ```
338
+
339
+ Subcommand classes are available in `.loki` because siblings loaded via `import "*.loki"` execute before `.loki`'s own class body.
340
+
341
+ ---
342
+
343
+ ## Task Name Overloading
344
+
345
+ Because all `*.loki` files reopen the same `class Tasks`, two files can define a method with the same name. Ruby's class reopening semantics apply: the last definition loaded wins, silently replacing the earlier one.
346
+
347
+ Three things are overwritten when a task name is reused:
348
+
349
+ | What | Effect |
350
+ |---|---|
351
+ | `def method_name` | The Ruby method body — the earlier implementation is gone |
352
+ | `desc` metadata | Thor registers the new usage/description string, discarding the old one |
353
+ | `depends_on` stages | `method_added` captures the pending deps for the new definition; the earlier dep chain is replaced |
354
+
355
+ **Accidental overloading** is a silent bug. Keep task names unique across files.
356
+
357
+ !!! warning
358
+ There is no runtime error when a task is overloaded. If a task is not behaving as expected, check whether another `.loki` file defines the same method name and loads after it.
359
+
360
+ **Intentional overloading** lets you extend a task defined in an earlier file using `alias_method`:
361
+
362
+ ```ruby
363
+ # build.loki (loaded first)
364
+ class Tasks
365
+ desc "Compile the project"
366
+ def build = sh "rake build"
367
+ end
368
+
369
+ # postbuild.loki (loaded after build.loki, alphabetically)
370
+ class Tasks
371
+ no_commands { alias_method :_build_original, :build }
372
+
373
+ desc "Compile the project and copy assets"
374
+ def build
375
+ _build_original
376
+ sh "cp -r dist/ public/"
377
+ end
378
+ end
379
+ ```
380
+
381
+ !!! warning "Prefer `depends_on` over intentional overloading"
382
+ Using `alias_method` to bolt post-task behaviour onto an existing task is fragile and load-order dependent. The idiomatic alternative is `depends_on`:
383
+
384
+ ```ruby
385
+ class Tasks
386
+ desc "Compile the project"
387
+ def build = sh "rake build"
388
+
389
+ desc "Copy build output to public/"
390
+ def copy_assets = sh "cp -r dist/ public/"
391
+
392
+ depends_on :build, :copy_assets
393
+ desc "Compile and copy assets"
394
+ def build_all; end
395
+ end
396
+ ```
397
+
398
+ ---
399
+
400
+ ## Summary of Loading Rules
401
+
402
+ | Method | Finds? | Loads? | Glob? | Ancestor search? |
403
+ |---|---|---|---|---|
404
+ | `loki_up(name)` | Yes | No | No | Yes |
405
+ | `import(path)` | No | Yes | Yes | No |
406
+ | `import_up(name)` | Yes | Yes | Yes | Yes |
407
+ | Asgard's `run!` | Yes | `.loki` only | No | Yes |
data/docs/tasks.md ADDED
@@ -0,0 +1,286 @@
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
+ ## Command Aliases
210
+
211
+ `map` creates short aliases for existing tasks:
212
+
213
+ ```ruby
214
+ class Tasks
215
+ map "-v" => "version"
216
+ map "--v" => "version"
217
+ map "t" => "test"
218
+ map "b" => "build"
219
+
220
+ desc "Print the version"
221
+ def version = puts Asgard::VERSION
222
+
223
+ desc "Run tests"
224
+ def test = sh "bundle exec rake test"
225
+
226
+ desc "Build the gem"
227
+ def build = sh "bundle exec rake build"
228
+ end
229
+ ```
230
+
231
+ ```bash
232
+ asgard t # same as: asgard test
233
+ asgard b # same as: asgard build
234
+ asgard -v # same as: asgard version (note: --version is the built-in flag)
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Formal Argument Declaration
240
+
241
+ `argument` provides rich positional-parameter metadata including type checking, enums, and help text.
242
+
243
+ !!! warning "Class-level scope"
244
+ `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.
245
+
246
+ ```ruby
247
+ class Tasks
248
+ argument :name,
249
+ type: :string,
250
+ default: "World",
251
+ desc: "Name to greet"
252
+
253
+ desc "hello NAME", "Say hello to NAME"
254
+ def hello = puts "Hello, #{name}!"
255
+ end
256
+ ```
257
+
258
+ For most multi-task `.loki` files, the simpler positional default pattern is safer:
259
+
260
+ ```ruby
261
+ def hello(name = "World") = puts "Hello, #{name}!"
262
+ ```
263
+
264
+ ---
265
+
266
+ ## No Commands Block
267
+
268
+ `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:
269
+
270
+ ```ruby
271
+ class Tasks
272
+ desc "Compile the project"
273
+ def build
274
+ puts "Revision: #{current_sha}"
275
+ sh "rake build"
276
+ end
277
+
278
+ no_commands do
279
+ def current_sha
280
+ `git rev-parse --short HEAD`.strip
281
+ end
282
+ end
283
+ end
284
+ ```
285
+
286
+ See [Helper Methods](helpers.md) for the full guide on helpers, `private`, and cross-file sharing.