Skip to content

Commit 36b8bf5

Browse files
committed
feat(completions): switch to shtab static completions\n\n- Expose get_parser() for shtab\n- Add --print-completion via shtab when available\n- Rework Detected shell: zsh
# Install shtab (once, in your environment): python -m pip install shtab # Generate static zsh completion for 'struct': mkdir -p ~/.zfunc python -m shtab struct_module.main:get_parser -s zsh -o ~/.zfunc/_struct # Ensure zsh loads user functions/completions (append to ~/.zshrc if needed): echo "fpath=(~/.zfunc $fpath)" >> ~/.zshrc echo "autoload -U compinit && compinit" >> ~/.zshrc # Apply now (or open a new shell): exec zsh Tip: You can also print completion directly via: struct --print-completion <shell> to generate static files for zsh/bash/fish\n- Replace argcomplete with shtab in requirements.txt
1 parent 315e448 commit 36b8bf5

3 files changed

Lines changed: 43 additions & 27 deletions

File tree

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ openai
44
python-dotenv
55
jinja2
66
PyGithub
7-
argcomplete
7+
shtab
88
colorlog
99
boto3
1010
google-cloud

struct_module/commands/completion.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
class CompletionCommand(Command):
77
def __init__(self, parser):
88
super().__init__(parser)
9-
parser.description = "Manage CLI shell completions for struct (argcomplete)"
9+
parser.description = "Manage CLI shell completions for struct (shtab-generated)"
1010
sub = parser.add_subparsers(dest="action")
1111

1212
install = sub.add_parser("install", help="Print the commands to enable completion for your shell")
@@ -29,32 +29,37 @@ def _install(self, args):
2929
print(f"Detected shell: {shell}")
3030

3131
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:")
32+
print("\n# Install shtab (once, in your environment):")
33+
print("python -m pip install shtab")
34+
print("\n# Generate static bash completion for 'struct':")
35+
print("mkdir -p ~/.local/share/bash-completion/completions")
36+
print("python -m shtab struct_module.main:get_parser -s bash -o ~/.local/share/bash-completion/completions/struct")
37+
print("\n# Apply now (or open a new shell):")
3738
print("source ~/.bashrc")
3839

3940
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")
41+
print("\n# Install shtab (once, in your environment):")
42+
print("python -m pip install shtab")
43+
print("\n# Generate static zsh completion for 'struct':")
44+
print("mkdir -p ~/.zfunc")
45+
print("python -m shtab struct_module.main:get_parser -s zsh -o ~/.zfunc/_struct")
46+
print("\n# Ensure zsh loads user functions/completions (append to ~/.zshrc if needed):")
47+
print('echo "fpath=(~/.zfunc $fpath)" >> ~/.zshrc')
48+
print('echo "autoload -U compinit && compinit" >> ~/.zshrc')
49+
print("\n# Apply now (or open a new shell):")
50+
print("exec zsh")
4651

4752
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':")
53+
print("\n# Install shtab (once, in your environment):")
54+
print("python -m pip install shtab")
55+
print("\n# Generate static fish completion for 'struct':")
5156
print('mkdir -p ~/.config/fish/completions')
52-
print('register-python-argcomplete --shell fish struct > ~/.config/fish/completions/struct.fish')
57+
print('python -m shtab struct_module.main:get_parser -s fish -o ~/.config/fish/completions/struct.fish')
5358
print("\n# Apply now:")
5459
print("fish -c 'source ~/.config/fish/completions/struct.fish'")
5560

5661
else:
5762
self.logger.error(f"Unsupported shell: {shell}. Supported: {', '.join(SUPPORTED_SHELLS)}")
5863
return
5964

60-
print("\nTip: If 'register-python-argcomplete' is not found, try:\n python -m argcomplete.shellintegration <shell>")
65+
print("\nTip: You can also print completion directly via: struct --print-completion <shell>")

struct_module/main.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import argparse, argcomplete
1+
import argparse
22
import logging
33
from dotenv import load_dotenv
44
from struct_module.utils import read_config_file, merge_configs
@@ -10,11 +10,15 @@
1010
from struct_module.commands.mcp import MCPCommand
1111
from struct_module.logging_config import configure_logging
1212

13-
13+
# Optional dependency: shtab for static shell completion generation
14+
try:
15+
import shtab # type: ignore
16+
except Exception: # pragma: no cover - optional at runtime
17+
shtab = None
1418

1519
load_dotenv()
1620

17-
def main():
21+
def get_parser():
1822
parser = argparse.ArgumentParser(
1923
description="Generate project structure from YAML configuration.",
2024
prog="struct",
@@ -35,11 +39,19 @@ def main():
3539
from struct_module.commands.init import InitCommand
3640
InitCommand(subparsers.add_parser('init', help='Initialize a basic .struct.yaml in the target directory'))
3741

38-
# completion installer
42+
# completion manager
3943
from struct_module.commands.completion import CompletionCommand
4044
CompletionCommand(subparsers.add_parser('completion', help='Manage shell completions'))
4145

42-
argcomplete.autocomplete(parser)
46+
# Add shtab completion printing flags if available
47+
if shtab is not None:
48+
# Adds --print-completion and --shell flags
49+
shtab.add_argument_to(parser)
50+
51+
return parser
52+
53+
def main():
54+
parser = get_parser()
4355

4456
args = parser.parse_args()
4557

@@ -49,14 +61,13 @@ def main():
4961
parser.exit()
5062

5163
# Read config file if provided
52-
if args.config_file:
64+
if getattr(args, 'config_file', None):
5365
file_config = read_config_file(args.config_file)
5466
args = argparse.Namespace(**merge_configs(file_config, args))
5567

56-
logging_level = getattr(logging, args.log.upper(), logging.INFO)
57-
58-
configure_logging(level=logging_level, log_file=args.log_file)
68+
logging_level = getattr(logging, getattr(args, 'log', 'INFO').upper(), logging.INFO)
5969

70+
configure_logging(level=logging_level, log_file=getattr(args, 'log_file', None))
6071

6172
args.func(args)
6273

0 commit comments

Comments
 (0)