Skip to content

Commit 4c31fee

Browse files
feat: add support for carapace macros (#6)
* feat: add support for carapace macros --------- Co-authored-by: Sebastian Bresin <sebastian.bresin@gmail.com>
1 parent 553ec52 commit 4c31fee

File tree

2 files changed

+84
-8
lines changed

2 files changed

+84
-8
lines changed

README.md

+48-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ https://carapace-sh.github.io/carapace-spec/carapace-spec/usage.html
1616
* Options
1717
* Exclusive relationships
1818
* Re-generate spec automatically on plugin install/uninstall
19+
* Completion overrides (see `Macros` section below)
1920

2021
## Requirements
2122
* `carapace-spec`: [dowload the binary for your OS]([url](https://github.com/carapace-sh/carapace-spec/releases)) and put it in your `PATH`
@@ -29,11 +30,53 @@ plugins install @cristiand391/oclif-carapace-spec-plugin
2930

3031
Then run the `carapace-gen` command and follow the instructions to source the spec in your shell.
3132

33+
## Macros
34+
`carapace-spec` allows to use macros for customizing completion style and values:
35+
36+
https://carapace-sh.github.io/carapace-spec/carapace-spec/macros.html
37+
38+
You can use macros to define custom flag completion logic for dynamic flag values that can't be hard-coded in CLIs like usernames, IDs, etc.
39+
40+
Macros file example for the Salesforce CLI:
41+
```yaml
42+
# this node applies to all commands, define here completion logic for flags that repeat themselves in multiple commands.
43+
persistentFlagsCompletion:
44+
target-org: ["$(sf org list auth --json | jq -r '.result[].username')"]
45+
target-dev-hub: ["$(sf org list auth --json | jq -r '.result[] | select(.isDevHub) | .username')"]
46+
definition-file: ["$files([.json])"]
47+
manifest: ["$files([.xml])"]
48+
file: ["$files"]
49+
50+
# override flag completion for specific commands.
51+
# important: command ids need to separated by colons.
52+
commandOverrides:
53+
flags:
54+
'project:deploy:start':
55+
pre-destructive-changes: ["$files([.xml])"]
56+
post-destructive-changes: ["$files([.xml])"]
57+
'org:delete:scratch':
58+
target-org: ["$(sf org list auth --json | jq -r '.result[] | select(.isScratchOrg) | .username')"]
59+
'org:delete:sandbox':
60+
target-org: ["$(sf org list auth --json | jq -r '.result[] | select(.isSandbox) | .username')"]
61+
62+
```
63+
64+
It uses the `exec` macro for `--target-org` and `--target-dev-hub` flags to get completion values from a command, and the `files` macro for XML/JSON file completion on specific flags.
65+
66+
### Usage
67+
68+
1. Define a YAML file with completion definitions like in the example above.
69+
2. Set the `<BIN>_CARAPACE_SPEC_MACROS_FILE` env var to the path to the YAML file.
70+
3. Run `sf carapace-gen --refresh-cache`.
71+
72+
> [!NOTE]
73+
The `<BIN>` part in the env var in step 2 refers to the name of your oclif CLI. For instance, for the Salesforce CLI (`sf`) the env var should be `SF_CARAPACE_SPEC_MACROS_FILE`.
74+
75+
> [!NOTE]
76+
This plugin re-generates the carapace spec everytime you install/uninstall plugins via `plugins install/uninstall`, make sure to set the `<BIN>_CARAPACE_SPEC_MACROS_FILE` env var in your shell RC file so that you don't miss the custom completions when the automatic re-generation happens under the hood.
77+
78+
3279
## Why should I use this instead of `@oclif/plugin-autocomplete`?
3380
`@oclif/plugin-autocomplete` only supports bash, zsh and powershell while `carapace-spec` supports those + 6 additional shells: https://carapace-sh.github.io/carapace-spec/carapace-spec/usage.html
34-
Except for flag exclusive relationships, the completion experience is pretty much the same so if you oclif/plugin-autocomplete works for you then you can ignore this.
35-
36-
In the future I plan to add support for injecting custom macros for specific command/flags,see:
37-
https://carapace-sh.github.io/carapace-spec/carapace-spec/macros/core.html
3881

39-
that would allow users to define dynamic completion logic for flag/arg values without having to touch any code.
82+
Except for flag exclusive relationships and completion overrides (macros), the completion experience is pretty much the same so if `oclif/plugin-autocomplete` works for you then you can ignore this.

src/commands/carapace-gen.ts

+36-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {Command, Flags, Interfaces} from '@oclif/core'
22
import {bold,cyan} from 'ansis'
33
import * as ejs from 'ejs'
4-
import {mkdir,writeFile} from 'node:fs/promises'
4+
import {mkdir,writeFile,readFile} from 'node:fs/promises'
55
import YAML, { YAMLMap } from 'yaml'
66

77
type YamlCommandNode = {
@@ -23,6 +23,19 @@ type TopicNode = {
2323

2424
type Node = CommandNode | TopicNode
2525

26+
type Macros = {
27+
persistentFlagsCompletion?: {
28+
[name: string]: string[];
29+
};
30+
commandOverrides?: {
31+
flags?: {
32+
[name: string]: {
33+
[name: string]: string[];
34+
};
35+
};
36+
};
37+
};
38+
2639
export default class CarapaceGen extends Command {
2740
static override description = `Generate a carapace spec file
2841
@@ -46,7 +59,13 @@ https://github.com/carapace-sh/carapace-spec`
4659

4760
const commandNodes = this.getCommandNodes()
4861

49-
await writeFile(specPath, YAML.stringify(this.buildCommandTree(commandNodes)))
62+
const macrosFilepath = this.config.scopedEnvVar('CARAPACE_SPEC_MACROS_FILE')
63+
const macros = macrosFilepath ? YAML.parse(await readFile(macrosFilepath, 'utf8')) : undefined
64+
65+
await writeFile(specPath, YAML.stringify(this.buildCommandTree(commandNodes, macros), {
66+
// avoid using YAML anchors in completion file, carapace doesn't like them.
67+
aliasDuplicateObjects: false
68+
}))
5069

5170
if (!flags['refresh-cache']) {
5271
this.log(`
@@ -65,7 +84,7 @@ https://github.com/carapace-sh/carapace-spec`
6584
}
6685
}
6786

68-
private buildCommandTree(nodes: Node[]) {
87+
private buildCommandTree(nodes: Node[], macros: Macros | undefined) {
6988
const commandTree: {
7089
name: string,
7190
description: string;
@@ -145,6 +164,20 @@ https://github.com/carapace-sh/carapace-spec`
145164
key: flagDef,
146165
value: node.flags[flagName].summary ?? node.flags[flagName].description ?? ''
147166
})
167+
168+
if (macros) {
169+
if (macros.persistentFlagsCompletion && Object.hasOwn(macros.persistentFlagsCompletion, flagName)) {
170+
completion.flag[flagName] = macros.persistentFlagsCompletion[flagName]
171+
hasFlagValueCompletion = true
172+
}
173+
if (macros.commandOverrides?.flags && Object.hasOwn(macros.commandOverrides.flags, node.id)) {
174+
if (Object.hasOwn(macros.commandOverrides.flags[node.id], flagName)) {
175+
completion.flag[flagName] = macros.commandOverrides.flags[node.id][flagName]
176+
hasFlagValueCompletion = true
177+
}
178+
}
179+
}
180+
148181
}
149182

150183
// add oclif's global help flag

0 commit comments

Comments
 (0)