Skip to content

Commit a60e222

Browse files
authored
feat(init): add 'struct init' scaffolder (cherry-pick of 781d18d) (#108)
Docs updated for the new `struct init` command in: - docs/cli-reference.md (added `init` to overview and detailed section) - docs/usage.md (new section with examples and explanation) - docs/quickstart.md (bootstrap section showing `struct init`) Refs: #97
1 parent 77c4f43 commit a60e222

6 files changed

Lines changed: 152 additions & 1 deletion

File tree

docs/cli-reference.md

Lines changed: 18 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,mcp,completion} ...
12+
struct {info,validate,generate,list,generate-schema,mcp,completion,init} ...
1313
```
1414

1515
## Global Options
@@ -120,6 +120,23 @@ struct completion install [bash|zsh|fish]
120120
- 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.
121121
- This does not modify your shell configuration; it only prints the commands you can copy-paste.
122122

123+
### `init`
124+
125+
Initialize a basic .struct.yaml in the target directory.
126+
127+
Usage:
128+
129+
```sh
130+
struct init [path]
131+
```
132+
133+
- Creates a .struct.yaml if it does not exist.
134+
- Includes:
135+
- pre_hooks/post_hooks with echo commands
136+
- files with a README.md placeholder
137+
- folders referencing github/workflows/run-struct at ./
138+
- Non-destructive: if .struct.yaml already exists, it is not overwritten and a message is printed.
139+
123140
## Examples
124141

125142
### Basic Structure Generation

docs/quickstart.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ Or try a simple project structure:
7474
struct generate project/nodejs ./my-node-app
7575
```
7676

77+
## Bootstrap a new project
78+
79+
Start with a minimal .struct.yaml:
80+
81+
```sh
82+
struct init
83+
```
84+
85+
This writes a basic .struct.yaml with hooks, a README, and a reference to the run-struct workflow.
86+
7787
## Next Steps
7888

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

docs/usage.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,26 @@ The generated schema includes all available structures from both the built-in co
111111

112112
## Other Commands
113113

114+
### Initialize a project with .struct.yaml
115+
116+
Create a minimal .struct.yaml in the current directory:
117+
118+
```sh
119+
struct init
120+
```
121+
122+
Or specify a directory:
123+
124+
```sh
125+
struct init ./my-project
126+
```
127+
128+
The file includes:
129+
130+
- pre_hooks/post_hooks with echo commands
131+
- A README.md placeholder in files
132+
- A folders entry pointing to the github/workflows/run-struct workflow at ./
133+
114134
### Validate Configuration
115135

116136
```sh

struct_module/commands/init.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from struct_module.commands import Command
2+
import os
3+
import textwrap
4+
5+
BASIC_STRUCT_YAML = textwrap.dedent(
6+
"""
7+
# Generated by `struct init`
8+
# Edit as needed to customize your project bootstrap
9+
10+
pre_hooks:
11+
- echo "Starting struct generation"
12+
13+
post_hooks:
14+
- echo "Struct generation completed"
15+
16+
files:
17+
- README.md: |
18+
# Project
19+
20+
Initialized with struct.
21+
22+
folders:
23+
- ./:
24+
struct:
25+
- github/workflows/run-struct
26+
"""
27+
).lstrip()
28+
29+
class InitCommand(Command):
30+
def __init__(self, parser):
31+
super().__init__(parser)
32+
parser.description = "Initialize a basic .struct.yaml in the target directory"
33+
parser.add_argument('path', nargs='?', default='.', help='Directory to initialize (default: current directory)')
34+
parser.set_defaults(func=self.execute)
35+
36+
def execute(self, args):
37+
base_path = os.path.abspath(args.path or '.')
38+
target = os.path.join(base_path, '.struct.yaml')
39+
40+
os.makedirs(base_path, exist_ok=True)
41+
42+
# If file exists, do not overwrite without explicit confirmation behavior (keep simple: skip)
43+
if os.path.exists(target):
44+
self.logger.info(f".struct.yaml already exists at {target}, skipping creation")
45+
print(f"⚠️ .struct.yaml already exists at: {target}")
46+
return
47+
48+
with open(target, 'w') as f:
49+
f.write(BASIC_STRUCT_YAML)
50+
51+
print("✅ Created .struct.yaml")
52+
print(f" - {target}")

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+
# init to create a basic .struct.yaml
35+
from struct_module.commands.init import InitCommand
36+
InitCommand(subparsers.add_parser('init', help='Initialize a basic .struct.yaml in the target directory'))
37+
3438
# completion installer
3539
from struct_module.commands.completion import CompletionCommand
3640
CompletionCommand(subparsers.add_parser('completion', help='Manage shell completions'))

tests/test_init_command.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import argparse
2+
import os
3+
from unittest.mock import patch
4+
5+
from struct_module.commands.init import InitCommand, BASIC_STRUCT_YAML
6+
7+
8+
def test_init_creates_struct_yaml(tmp_path):
9+
parser = argparse.ArgumentParser()
10+
cmd = InitCommand(parser)
11+
12+
target_dir = tmp_path / "proj"
13+
args = parser.parse_args([str(target_dir)])
14+
15+
with patch('builtins.print') as mock_print:
16+
cmd.execute(args)
17+
18+
struct_file = target_dir / '.struct.yaml'
19+
assert struct_file.exists()
20+
21+
content = struct_file.read_text()
22+
# Basic checks for key sections
23+
assert 'pre_hooks:' in content
24+
assert 'post_hooks:' in content
25+
assert 'files:' in content
26+
assert 'README.md' in content
27+
assert 'folders:' in content
28+
assert 'github/workflows/run-struct' in content
29+
30+
31+
def test_init_skips_if_exists(tmp_path):
32+
parser = argparse.ArgumentParser()
33+
cmd = InitCommand(parser)
34+
35+
target_dir = tmp_path / "proj"
36+
target_dir.mkdir(parents=True)
37+
existing = target_dir / '.struct.yaml'
38+
existing.write_text('files: []\n')
39+
40+
args = parser.parse_args([str(target_dir)])
41+
42+
with patch('builtins.print') as mock_print:
43+
cmd.execute(args)
44+
# Should not overwrite existing file
45+
assert existing.read_text() == 'files: []\n'
46+
# Should print a message about skipping
47+
printed = "\n".join(c.args[0] for c in mock_print.call_args_list)
48+
assert '.struct.yaml already exists' in printed

0 commit comments

Comments
 (0)