Skip to content

Commit ac4009a

Browse files
authored
feat: Add autocomplete for structure names (#83)
Add autocomplete support for structure names in generate command. - Added StructuresCompleter class - Integrated with generate command positional argument - Added tests and documentation - All 44 existing tests pass
1 parent af57889 commit ac4009a

7 files changed

Lines changed: 219 additions & 23 deletions

File tree

docs/completion.md

Lines changed: 94 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,36 @@
11
# Command-Line Auto-Completion
22

3-
This project uses [argcomplete](https://kislyuk.github.io/argcomplete/) to provide command-line auto-completion for the `struct` script. Follow these steps to enable auto-completion:
3+
STRUCT provides intelligent auto-completion for commands, options, and **structure names** using [argcomplete](https://kislyuk.github.io/argcomplete/). This makes discovering and using available structures much faster and more user-friendly.
44

5-
## Installation
5+
!!! tip "New Feature: Structure Name Completion"
6+
STRUCT now automatically completes structure names when using `struct generate`, showing all 47+ available structures from both built-in and custom paths!
7+
8+
## Quick Setup
9+
10+
For most users, this simple setup will enable full completion:
11+
12+
```sh
13+
# Install (if not already installed)
14+
pip install argcomplete
15+
16+
# Enable completion for current session
17+
eval "$(register-python-argcomplete struct)"
18+
19+
# Make permanent - add to your ~/.zshrc or ~/.bashrc
20+
echo 'eval "$(register-python-argcomplete struct)"' >> ~/.zshrc
21+
```
22+
23+
## Detailed Installation
624

725
### 1. Install argcomplete
826

927
```sh
1028
pip install argcomplete
1129
```
1230

13-
### 2. Enable Global Completion
31+
### 2. Enable Global Completion (Optional)
1432

15-
This step is usually done once per system:
33+
This step is optional but can be done once per system:
1634

1735
```sh
1836
activate-global-python-argcomplete
@@ -59,15 +77,41 @@ source ~/.config/fish/config.fish
5977

6078
After completing the setup, you can use auto-completion by typing part of a command and pressing `Tab`:
6179

80+
### Command Completion
6281
```sh
6382
struct <Tab>
6483
# Shows: generate, generate-schema, validate, info, list
84+
```
6585

86+
### Structure Name Completion ✨
87+
```sh
88+
# Complete structure names - shows all available structures!
6689
struct generate <Tab>
67-
# Shows available structure names and options
90+
# Shows: ansible-playbook, docker-files, github/workflows/codeql, project/nodejs, etc.
91+
92+
# Partial completion works too
93+
struct generate git<Tab>
94+
# Shows: git-hooks, github/workflows/codeql, github/templates, etc.
6895

96+
# Works with nested structures
97+
struct generate github/<Tab>
98+
# Shows: github/workflows/codeql, github/templates, github/prompts/generic, etc.
99+
```
100+
101+
### Custom Structure Paths
102+
```sh
103+
# Completion works with custom structure paths
104+
struct generate --structures-path /custom/path <Tab>
105+
# Shows structures from both custom path and built-in structures
106+
```
107+
108+
### Option Completion
109+
```sh
69110
struct generate --<Tab>
70-
# Shows: --log, --dry-run, --backup, --file-strategy, etc.
111+
# Shows: --log, --dry-run, --backup, --file-strategy, --structures-path, etc.
112+
113+
struct generate --log <Tab>
114+
# Shows: DEBUG, INFO, WARNING, ERROR, CRITICAL
71115
```
72116

73117
## Advanced Configuration
@@ -180,32 +224,64 @@ eval "$(register-python-argcomplete struct-wrapper.sh)"
180224

181225
## Supported Completions
182226

183-
STRUCT provides completion for:
227+
STRUCT provides intelligent completion for:
184228

185-
- **Commands**: `generate`, `validate`, `list`, etc.
186-
- **Options**: `--log`, `--dry-run`, `--backup`, etc.
187-
- **Structure names**: All available built-in and custom structures
229+
- **Commands**: `generate`, `validate`, `list`, `info`, `generate-schema`
230+
- **Options**: `--log`, `--dry-run`, `--backup`, `--file-strategy`, `--structures-path`, etc.
231+
- **Structure names**: All 47+ available built-in and custom structures
232+
- Built-in structures: `ansible-playbook`, `docker-files`, `helm-chart`, etc.
233+
- Nested structures: `github/workflows/codeql`, `project/nodejs`, `terraform/apps/generic`, etc.
234+
- Custom structures: From `--structures-path` directories
188235
- **File paths**: Local files and directories
189-
- **Enum values**: Log levels, file strategies, etc.
236+
- **Enum values**: Log levels (`DEBUG`, `INFO`, etc.), file strategies (`overwrite`, `skip`, etc.)
237+
238+
## How Structure Completion Works
239+
240+
The structure name completion feature:
241+
242+
1. **Dynamically discovers** all available structure files (`.yaml` files)
243+
2. **Scans multiple locations**:
244+
- Built-in structures in `struct_module/contribs/`
245+
- Custom structures from `--structures-path` if specified
246+
3. **Returns clean names** without `.yaml` extensions
247+
4. **Supports nested directories** like `github/workflows/codeql`
248+
5. **Updates automatically** when new structures are added
190249

191250
## Example Session
192251

193252
```sh
253+
# Command completion
194254
$ struct <Tab>
195255
generate generate-schema info list validate
196256

257+
# Structure name completion (NEW!)
197258
$ struct generate <Tab>
198-
configs/ docker-files project/ terraform/
199-
200-
$ struct generate terraform/<Tab>
201-
terraform/app terraform/module
202-
259+
ansible-playbook configs/codeowners github/workflows/codeql project/nodejs
260+
chef-cookbook docker-files helm-chart terraform/apps/generic
261+
ci-cd-pipelines git-hooks kubernetes-manifests vagrant-files
262+
263+
# Partial completion
264+
$ struct generate proj<Tab>
265+
project/custom-structures project/go project/nodejs project/ruby
266+
project/generic project/java project/python project/rust
267+
268+
# Nested structure completion
269+
$ struct generate github/<Tab>
270+
github/chatmodes/plan github/prompts/react-form github/workflows/codeql
271+
github/instructions/generic github/prompts/security-api github/workflows/labeler
272+
github/prompts/generic github/workflows/pre-commit github/workflows/stale
273+
274+
# Option completion
203275
$ struct generate --<Tab>
204276
--backup --dry-run --file-strategy --log
205-
--log-file --mappings-file --structures-path
277+
--log-file --mappings-file --structures-path --vars
206278

279+
# Enum value completion
207280
$ struct generate --log <Tab>
208-
DEBUG ERROR INFO WARNING
281+
DEBUG ERROR INFO WARNING CRITICAL
282+
283+
$ struct generate --file-strategy <Tab>
284+
append backup overwrite rename skip
209285
```
210286

211287
This makes working with STRUCT much more efficient and user-friendly!

docs/installation.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ Install STRUCT with pip:
88
pip install git+https://github.com/httpdss/struct.git
99
```
1010

11+
!!! tip "Enable Auto-Completion"
12+
After installation, enable command-line auto-completion for better productivity:
13+
```sh
14+
eval "$(register-python-argcomplete struct)"
15+
```
16+
For permanent setup, see the [Command-Line Completion](completion.md) guide.
17+
1118
## From Source
1219

1320
Clone the repository and install locally. See the [Development](development.md) page for details.

docs/quickstart.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,35 @@ struct generate structure.yaml .
4545

4646
> **Note**: The `file://` protocol is automatically added for `.yaml` files, so `structure.yaml` and `file://structure.yaml` work identically.
4747
48+
## Discovering Available Structures
49+
50+
Before generating, see what structures are available:
51+
52+
```sh
53+
struct list
54+
```
55+
56+
This shows all built-in structures you can use.
57+
58+
!!! tip "Auto-Completion"
59+
If you've enabled [auto-completion](completion.md), you can press `Tab` after `struct generate ` to see all available structures!
60+
4861
## First Example
4962

5063
After installing STRUCT, try this simple example:
5164

5265
```sh
53-
struct generate terraform-module ./my-terraform-module
66+
struct generate terraform/modules/generic ./my-terraform-module
5467
```
5568

5669
This will create a new terraform module structure in the `./my-terraform-module` directory.
5770

71+
Or try a simple project structure:
72+
73+
```sh
74+
struct generate project/nodejs ./my-node-app
75+
```
76+
5877
## Next Steps
5978

6079
- Learn about [YAML Configuration](configuration.md)

docs/usage.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,25 @@ struct -h
1818

1919
## Generate Command
2020

21+
### Finding Available Structures
22+
23+
Use the `list` command to see all available structures:
24+
25+
```sh
26+
struct list
27+
```
28+
29+
Or if you have [auto-completion](completion.md) enabled, use `Tab` to see all options:
30+
31+
```sh
32+
struct generate <Tab>
33+
# Shows all available structures
34+
```
35+
2136
### Simple Example
2237

2338
```sh
24-
struct generate terraform-module ./my-terraform-module
39+
struct generate terraform/modules/generic ./my-terraform-module
2540
```
2641

2742
### YAML File Usage

struct_module/commands/generate.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import yaml
44
import argparse
55
from struct_module.file_item import FileItem
6-
from struct_module.completers import file_strategy_completer
6+
from struct_module.completers import file_strategy_completer, structures_completer
77
from struct_module.template_renderer import TemplateRenderer
88

99
import subprocess
@@ -12,7 +12,8 @@
1212
class GenerateCommand(Command):
1313
def __init__(self, parser):
1414
super().__init__(parser)
15-
parser.add_argument('structure_definition', type=str, help='Path to the YAML configuration file')
15+
structure_arg = parser.add_argument('structure_definition', type=str, help='Path to the YAML configuration file')
16+
structure_arg.completer = structures_completer
1617
parser.add_argument('base_path', type=str, help='Base path where the structure will be created')
1718
parser.add_argument('-s', '--structures-path', type=str, help='Path to structure definitions')
1819
parser.add_argument('-n', '--input-store', type=str, help='Path to the input store', default='/tmp/struct/input.json')

struct_module/completers.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
13
class ChoicesCompleter(object):
24
def __init__(self, choices):
35
self.choices = choices
@@ -6,5 +8,47 @@ def __call__(self, **kwargs):
68
return self.choices
79

810

11+
class StructuresCompleter(object):
12+
"""Dynamic completer for available structure names."""
13+
14+
def __init__(self, structures_path=None):
15+
self.structures_path = structures_path
16+
17+
def __call__(self, prefix, parsed_args, **kwargs):
18+
"""Return list of available structure names for completion."""
19+
return self._get_available_structures(parsed_args)
20+
21+
def _get_available_structures(self, parsed_args):
22+
"""Get list of available structure names, similar to ListCommand logic."""
23+
# Get the directory where the commands are located
24+
this_file = os.path.dirname(os.path.realpath(__file__))
25+
contribs_path = os.path.join(this_file, "contribs")
26+
27+
# Check if custom structures path is provided via parsed_args
28+
structures_path = getattr(parsed_args, 'structures_path', None) or self.structures_path
29+
30+
if structures_path:
31+
paths_to_list = [structures_path, contribs_path]
32+
else:
33+
paths_to_list = [contribs_path]
34+
35+
all_structures = set()
36+
for path in paths_to_list:
37+
if not os.path.exists(path):
38+
continue
39+
40+
for root, _, files in os.walk(path):
41+
for file in files:
42+
if file.endswith(".yaml"):
43+
file_path = os.path.join(root, file)
44+
rel_path = os.path.relpath(file_path, path)
45+
# Remove .yaml extension
46+
structure_name = rel_path[:-5]
47+
all_structures.add(structure_name)
48+
49+
return sorted(list(all_structures))
50+
51+
952
log_level_completer = ChoicesCompleter(['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'])
1053
file_strategy_completer = ChoicesCompleter(['overwrite', 'skip', 'append', 'rename', 'backup'])
54+
structures_completer = StructuresCompleter()

tests/test_completers.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import pytest
2-
from struct_module.completers import log_level_completer, file_strategy_completer
2+
import os
3+
import argparse
4+
from struct_module.completers import log_level_completer, file_strategy_completer, structures_completer
35

46
def test_log_level_completer():
57
completer = log_level_completer()
@@ -16,3 +18,35 @@ def test_file_strategy_completer():
1618
assert 'append' in completer
1719
assert 'rename' in completer
1820
assert 'backup' in completer
21+
22+
def test_structures_completer():
23+
# Create a mock parsed_args
24+
parsed_args = argparse.Namespace(structures_path=None)
25+
26+
# Test the completer
27+
completions = structures_completer(prefix="", parsed_args=parsed_args)
28+
29+
# Should return a list
30+
assert isinstance(completions, list)
31+
32+
# Should contain some of the known structures from contribs
33+
# (these are based on what we saw in the directory listing)
34+
expected_structures = ['ansible-playbook', 'docker-files', 'helm-chart']
35+
36+
# Check if at least some expected structures are present
37+
for expected in expected_structures:
38+
assert expected in completions, f"Expected '{expected}' to be in completions: {completions}"
39+
40+
def test_structures_completer_with_custom_path(tmp_path):
41+
# Create a temporary structure file
42+
custom_structure_file = tmp_path / "custom-structure.yaml"
43+
custom_structure_file.write_text("files: []")
44+
45+
# Create a parsed_args with custom structures_path
46+
parsed_args = argparse.Namespace(structures_path=str(tmp_path))
47+
48+
# Test the completer
49+
completions = structures_completer(prefix="", parsed_args=parsed_args)
50+
51+
# Should contain the custom structure
52+
assert 'custom-structure' in completions

0 commit comments

Comments
 (0)