Skip to content

Commit 77c4f43

Browse files
authored
feat(cli): add completion installer and docs updates (#106)
This PR addresses part of #97 (CLI UX and discoverability) by adding a completion installer command and updating docs. Changes: - New subcommand: `struct completion install [bash|zsh|fish]` - Prints exact commands to enable argcomplete completions - Auto-detects shell if omitted - Docs updated: - README: added Shell Completion section - docs/cli-reference.md: documented `completion` command - docs/completion.md: added quick setup using `struct completion install` Why: - Improves first-run experience and discoverability by making it trivial to enable shell completions. Out of scope (will be tackled separately in #97): - `struct init` scaffolding - Output mode naming standardization Testing: - Verified `python -m struct_module.main completion install zsh` prints correct commands - Help output includes new command Refs: #97
1 parent d7c056a commit 77c4f43

6 files changed

Lines changed: 167 additions & 1 deletion

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@ struct validate my-config.yaml
4545
struct mcp --server
4646
```
4747

48+
### Shell Completion
49+
50+
Enable tab completion for struct commands and options:
51+
52+
```sh
53+
# Print exact setup commands for your shell (auto-detects if omitted)
54+
struct completion install
55+
56+
# Or specify explicitly
57+
struct completion install zsh
58+
struct completion install bash
59+
struct completion install fish
60+
```
61+
4862
### 🤖 MCP Integration Quick Start
4963

5064
Struct supports MCP (Model Context Protocol) for seamless AI tool integration:

docs/cli-reference.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ The `struct` CLI allows you to generate project structures from YAML configurati
99
**Basic Usage:**
1010

1111
```sh
12-
struct {info,validate,generate,list,generate-schema} ...
12+
struct {info,validate,generate,list,generate-schema,mcp,completion} ...
1313
```
1414

1515
## Global Options
@@ -107,6 +107,19 @@ struct generate-schema [-h] [-l LOG] [-c CONFIG_FILE] [-i LOG_FILE] [-s STRUCTUR
107107
- `-s STRUCTURES_PATH, --structures-path STRUCTURES_PATH`: Path to structure definitions.
108108
- `-o OUTPUT, --output OUTPUT`: Output file path for the schema (default: stdout).
109109

110+
### `completion`
111+
112+
Manage shell completions for struct.
113+
114+
Usage:
115+
116+
```sh
117+
struct completion install [bash|zsh|fish]
118+
```
119+
120+
- If no shell is provided, the command attempts to auto-detect your current shell and prints the exact commands to enable argcomplete-based completion for struct.
121+
- This does not modify your shell configuration; it only prints the commands you can copy-paste.
122+
110123
## Examples
111124

112125
### Basic Structure Generation

docs/completion.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ STRUCT provides intelligent auto-completion for commands, options, and **structu
77

88
## Quick Setup
99

10+
The easiest way is to ask struct to print the exact commands for your shell:
11+
12+
```sh
13+
# Auto-detect current shell and print install steps
14+
struct completion install
15+
16+
# Or specify explicitly
17+
struct completion install zsh
18+
struct completion install bash
19+
struct completion install fish
20+
```
21+
22+
You can still follow the manual steps below if you prefer.
23+
1024
For most users, this simple setup will enable full completion:
1125

1226
```sh
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from struct_module.commands import Command
2+
import os
3+
4+
SUPPORTED_SHELLS = ["bash", "zsh", "fish"]
5+
6+
class CompletionCommand(Command):
7+
def __init__(self, parser):
8+
super().__init__(parser)
9+
parser.description = "Manage CLI shell completions for struct (argcomplete)"
10+
sub = parser.add_subparsers(dest="action")
11+
12+
install = sub.add_parser("install", help="Print the commands to enable completion for your shell")
13+
install.add_argument("shell", nargs="?", choices=SUPPORTED_SHELLS, help="Shell type (auto-detected if omitted)")
14+
install.set_defaults(func=self._install)
15+
16+
def _detect_shell(self):
17+
shell = os.environ.get("SHELL", "")
18+
if shell:
19+
basename = os.path.basename(shell)
20+
if basename in SUPPORTED_SHELLS:
21+
return basename
22+
# Fallback to zsh if running zsh, else bash
23+
if os.environ.get("ZSH_NAME") or os.environ.get("ZDOTDIR"):
24+
return "zsh"
25+
return "bash"
26+
27+
def _install(self, args):
28+
shell = args.shell or self._detect_shell()
29+
print(f"Detected shell: {shell}")
30+
31+
if shell == "bash":
32+
print("\n# One-time dependency (if not installed):")
33+
print("python -m pip install argcomplete")
34+
print("\n# Enable completion for 'struct' in bash (append to ~/.bashrc):")
35+
print('echo "eval \"$(register-python-argcomplete struct)\"" >> ~/.bashrc')
36+
print("\n# Apply now:")
37+
print("source ~/.bashrc")
38+
39+
elif shell == "zsh":
40+
print("\n# One-time dependency (if not installed):")
41+
print("python -m pip install argcomplete")
42+
print("\n# Enable completion for 'struct' in zsh (append to ~/.zshrc):")
43+
print('echo "eval \"$(register-python-argcomplete --shell zsh struct)\"" >> ~/.zshrc')
44+
print("\n# Apply now:")
45+
print("source ~/.zshrc")
46+
47+
elif shell == "fish":
48+
print("\n# One-time dependency (if not installed):")
49+
print("python -m pip install argcomplete")
50+
print("\n# Install fish completion file for 'struct':")
51+
print('mkdir -p ~/.config/fish/completions')
52+
print('register-python-argcomplete --shell fish struct > ~/.config/fish/completions/struct.fish')
53+
print("\n# Apply now:")
54+
print("fish -c 'source ~/.config/fish/completions/struct.fish'")
55+
56+
else:
57+
self.logger.error(f"Unsupported shell: {shell}. Supported: {', '.join(SUPPORTED_SHELLS)}")
58+
return
59+
60+
print("\nTip: If 'register-python-argcomplete' is not found, try:\n python -m argcomplete.shellintegration <shell>")

struct_module/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ def main():
3131
GenerateSchemaCommand(subparsers.add_parser('generate-schema', help='Generate JSON schema for available structures'))
3232
MCPCommand(subparsers.add_parser('mcp', help='MCP (Model Context Protocol) support'))
3333

34+
# completion installer
35+
from struct_module.commands.completion import CompletionCommand
36+
CompletionCommand(subparsers.add_parser('completion', help='Manage shell completions'))
37+
3438
argcomplete.autocomplete(parser)
3539

3640
args = parser.parse_args()

tests/test_completion_command.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import argparse
2+
import os
3+
from unittest.mock import patch
4+
5+
from struct_module.commands.completion import CompletionCommand
6+
7+
8+
def make_parser():
9+
return argparse.ArgumentParser()
10+
11+
12+
def _gather_print_output(mock_print):
13+
return "\n".join(str(call.args[0]) for call in mock_print.call_args_list)
14+
15+
16+
def test_completion_install_bash_explicit():
17+
parser = make_parser()
18+
cmd = CompletionCommand(parser)
19+
with patch('builtins.print') as mock_print:
20+
args = parser.parse_args(['install', 'bash'])
21+
cmd._install(args)
22+
out = _gather_print_output(mock_print)
23+
assert "Detected shell: bash" in out
24+
assert "register-python-argcomplete struct" in out
25+
assert "~/.bashrc" in out
26+
27+
28+
def test_completion_install_zsh_explicit():
29+
parser = make_parser()
30+
cmd = CompletionCommand(parser)
31+
with patch('builtins.print') as mock_print:
32+
args = parser.parse_args(['install', 'zsh'])
33+
cmd._install(args)
34+
out = _gather_print_output(mock_print)
35+
assert "Detected shell: zsh" in out
36+
assert "register-python-argcomplete --shell zsh struct" in out
37+
assert "~/.zshrc" in out
38+
39+
40+
def test_completion_install_fish_explicit():
41+
parser = make_parser()
42+
cmd = CompletionCommand(parser)
43+
with patch('builtins.print') as mock_print:
44+
args = parser.parse_args(['install', 'fish'])
45+
cmd._install(args)
46+
out = _gather_print_output(mock_print)
47+
assert "Detected shell: fish" in out
48+
assert "register-python-argcomplete --shell fish struct" in out
49+
assert "~/.config/fish/completions/struct.fish" in out
50+
51+
52+
def test_completion_install_auto_detect_zsh():
53+
parser = make_parser()
54+
cmd = CompletionCommand(parser)
55+
with patch.dict(os.environ, {"SHELL": "/bin/zsh"}, clear=False):
56+
with patch('builtins.print') as mock_print:
57+
args = parser.parse_args(['install'])
58+
cmd._install(args)
59+
out = _gather_print_output(mock_print)
60+
assert "Detected shell: zsh" in out
61+
assert "register-python-argcomplete --shell zsh struct" in out

0 commit comments

Comments
 (0)