Skip to content

Commit c26d1f3

Browse files
committed
feat(init): add 'struct init' to scaffold a basic .struct.yaml with hooks, files, and folders\n\n- Creates .struct.yaml in target directory (non-destructive; skips if exists)\n- Includes pre_hooks/post_hooks with echo, README in files, and run-struct workflow in folders\n- Tests cover file creation and skip behavior\n\nRefs: #97
1 parent 77c4f43 commit c26d1f3

3 files changed

Lines changed: 104 additions & 0 deletions

File tree

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)