From e1be8ba7277c34e2661746c3b25259f650400265 Mon Sep 17 00:00:00 2001 From: Kenneth Belitzky Date: Mon, 22 Sep 2025 11:21:21 -0300 Subject: [PATCH 1/4] feat: display variable descriptions in interactive mode prompts - Add support for displaying variable descriptions in interactive prompts - Support both 'description' and 'help' fields for backward compatibility - Display descriptions only when available, maintaining clean prompts otherwise - Add comprehensive tests for the new functionality - Update documentation to explain the new feature Closes #116 --- docs/template-variables.md | 13 ++++- struct_module/template_renderer.py | 25 +++++++++- tests/test_template_renderer.py | 80 ++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 3 deletions(-) diff --git a/docs/template-variables.md b/docs/template-variables.md index 3ea9335..42ed29d 100644 --- a/docs/template-variables.md +++ b/docs/template-variables.md @@ -66,7 +66,7 @@ STRUCT provides these built-in variables: ## Interactive Variables -Define variables that prompt users for input: +Define variables that prompt users for input. When running in interactive mode, STRUCT will display the variable's description to help users understand what value is expected: ```yaml variables: @@ -84,6 +84,17 @@ variables: default: 8080 ``` +When prompted interactively, variables with descriptions will display like this: + +``` +❓ Enter value for project_name [MyProject]: + Description: The name of your project +❓ Enter value for author_name []: + Description: Your name +``` + +**Note**: The `description` field is displayed in interactive mode only. You can also use the legacy `help` field which works the same way. + ### Variable Types - `string`: Text values diff --git a/struct_module/template_renderer.py b/struct_module/template_renderer.py index 30a3c97..482525d 100644 --- a/struct_module/template_renderer.py +++ b/struct_module/template_renderer.py @@ -127,10 +127,23 @@ def prompt_for_missing_vars(self, content, vars): else: # Interactive prompt with enum support (choose by value or index) enum = conf.get('enum') + + # Get description if available (support both 'description' and 'help' fields) + description = conf.get('description') or conf.get('help') + if enum: # Build options list string like "(1) dev, (2) prod)" options = ", ".join([f"({i+1}) {val}" for i, val in enumerate(enum)]) - raw = input(f"❓ Enter value for {var} [{default}] {options}: ") + prompt = f"❓ Enter value for {var} [{default}] {options}: " + + # Display description on a new line if available + if description: + print(prompt.rstrip()) + print(f" Description: {description}") + raw = input(" ") or default + else: + raw = input(prompt) or default + raw = raw.strip() if raw == "": user_input = default @@ -142,7 +155,15 @@ def prompt_for_missing_vars(self, content, vars): # For invalid enum input, raise immediately instead of re-prompting raise ValueError(f"Variable '{var}' must be one of {enum}, got: {raw}") else: - user_input = input(f"❓ Enter value for {var} [{default}]: ") or default + prompt = f"❓ Enter value for {var} [{default}]: " + + # Display description on a new line if available + if description: + print(prompt.rstrip()) + print(f" Description: {description}") + user_input = input(" ") or default + else: + user_input = input(prompt) or default # Coerce and validate according to schema coerced = self._coerce_and_validate(var, user_input, conf) self.input_store.set_value(var, coerced) diff --git a/tests/test_template_renderer.py b/tests/test_template_renderer.py index 3b4850f..0ba3b0a 100644 --- a/tests/test_template_renderer.py +++ b/tests/test_template_renderer.py @@ -105,3 +105,83 @@ def test_render_template_with_mappings(): config_variables, input_store, non_interactive, mappings=mappings_dot) rendered_content_dot = renderer_dot.render_template(content_dot, {}) assert rendered_content_dot == "Account: 123456789" + + +def test_prompt_with_description_display(): + """Test that variable descriptions are displayed in interactive prompts""" + config_variables = [ + {"project_name": { + "type": "string", + "description": "The name of your project", + "default": "MyProject" + }}, + {"environment": { + "type": "string", + "description": "Target deployment environment", + "enum": ["dev", "staging", "prod"], + "default": "dev" + }}, + {"old_style_help": { + "type": "string", + "help": "This uses the old 'help' field", + "default": "test" + }}, + {"no_description": { + "type": "string", + "default": "test" + }} + ] + input_store = "/tmp/input.json" + non_interactive = False + renderer = TemplateRenderer(config_variables, input_store, non_interactive) + + # Test each variable type separately to ensure proper input handling + + # Test 1: Regular variable with description + content1 = "{{@ project_name @}}" + vars1 = {} + with patch('builtins.input', return_value="TestProject") as mock_input, \ + patch('builtins.print') as mock_print: + result_vars1 = renderer.prompt_for_missing_vars(content1, vars1) + assert result_vars1["project_name"] == "TestProject" + + # Check that description was printed + print_calls = [call.args[0] for call in mock_print.call_args_list] + assert any("Description: The name of your project" in call for call in print_calls) + + # Test 2: Enum variable with description + content2 = "{{@ environment @}}" + vars2 = {} + with patch('builtins.input', return_value="prod") as mock_input, \ + patch('builtins.print') as mock_print: + result_vars2 = renderer.prompt_for_missing_vars(content2, vars2) + assert result_vars2["environment"] == "prod" + + # Check that description was printed for enum + print_calls = [call.args[0] for call in mock_print.call_args_list] + assert any("Description: Target deployment environment" in call for call in print_calls) + + # Test 3: Variable with 'help' field (backward compatibility) + content3 = "{{@ old_style_help @}}" + vars3 = {} + with patch('builtins.input', return_value="help_test") as mock_input, \ + patch('builtins.print') as mock_print: + result_vars3 = renderer.prompt_for_missing_vars(content3, vars3) + assert result_vars3["old_style_help"] == "help_test" + + # Check that help was printed as description + print_calls = [call.args[0] for call in mock_print.call_args_list] + assert any("Description: This uses the old 'help' field" in call for call in print_calls) + + # Test 4: Variable without description (should not print description) + content4 = "{{@ no_description @}}" + vars4 = {} + with patch('builtins.input', return_value="no_desc_test") as mock_input, \ + patch('builtins.print') as mock_print: + result_vars4 = renderer.prompt_for_missing_vars(content4, vars4) + assert result_vars4["no_description"] == "no_desc_test" + + # Check that no description was printed + print_calls = [call.args[0] for call in mock_print.call_args_list] + # Should not contain any description text + assert not any("Description:" in call for call in print_calls) From b025069ac075b36bfe002c2171b5788939814577 Mon Sep 17 00:00:00 2001 From: Kenneth Belitzky Date: Mon, 22 Sep 2025 11:48:48 -0300 Subject: [PATCH 2/4] refactor: implement Option 4 formatting with contextual icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace basic description display with clean Option 4 format - Add contextual icons based on variable names and types - Implement cleaner two-line format for variables with descriptions - Maintain compact inline format for variables without descriptions - Add comprehensive test coverage for icon selection logic - Update documentation with new formatting examples Icons included: 🚀 Project/app names | 🌍 Environment vars | 🔌 Network/ports 🗄️ Database configs | ⚡ Boolean toggles | 🔐 Auth/secrets 🏷️ Versions/tags | 📁 Paths/directories | 🔧 General vars --- docs/template-variables.md | 30 +++++++++++++--- struct_module/template_renderer.py | 58 ++++++++++++++++++++++-------- tests/test_template_renderer.py | 45 ++++++++++++++++------- 3 files changed, 101 insertions(+), 32 deletions(-) diff --git a/docs/template-variables.md b/docs/template-variables.md index 42ed29d..aa60667 100644 --- a/docs/template-variables.md +++ b/docs/template-variables.md @@ -84,15 +84,35 @@ variables: default: 8080 ``` -When prompted interactively, variables with descriptions will display like this: +When prompted interactively, variables with descriptions will display with contextual icons and clean formatting: ``` -❓ Enter value for project_name [MyProject]: - Description: The name of your project -❓ Enter value for author_name []: - Description: Your name +🚀 project_name: The name of your project + Enter value [MyProject]: + +🌍 environment: Target deployment environment + Options: (1) dev, (2) staging, (3) prod + Enter value [dev]: +``` + +For variables without descriptions, a more compact format is used: + +``` +🔧 author_name []: +⚡ enable_logging [true]: ``` +**Contextual Icons**: STRUCT automatically selects appropriate icons based on variable names and types: +- 🚀 Project/app names +- 🌍 Environment/deployment variables +- 🔌 Ports/network settings +- 🗄️ Database configurations +- ⚡ Boolean/toggle options +- 🔐 Authentication/secrets +- 🏷️ Versions/tags +- 📁 Paths/directories +- 🔧 General variables + **Note**: The `description` field is displayed in interactive mode only. You can also use the legacy `help` field which works the same way. ### Variable Types diff --git a/struct_module/template_renderer.py b/struct_module/template_renderer.py index 482525d..635360a 100644 --- a/struct_module/template_renderer.py +++ b/struct_module/template_renderer.py @@ -99,6 +99,38 @@ def render_template(self, content, vars): template = self.env.from_string(content) return template.render(vars) + def _get_variable_icon(self, var_name, var_type): + """Get contextual icon for variable based on name and type""" + var_lower = var_name.lower() + + # Project/name related + if any(keyword in var_lower for keyword in ['project', 'name', 'app', 'title']): + return '🚀' + # Environment related + elif any(keyword in var_lower for keyword in ['env', 'environment', 'stage', 'deploy']): + return '🌍' + # Database related (check before URL to prioritize database_url) + elif any(keyword in var_lower for keyword in ['db', 'database', 'sql']): + return '🗄️' + # Port/network related + elif any(keyword in var_lower for keyword in ['port', 'url', 'host', 'endpoint']): + return '🔌' + # Boolean/toggle related + elif var_type == 'boolean' or any(keyword in var_lower for keyword in ['enable', 'disable', 'toggle', 'flag']): + return '⚡' + # Authentication/security + elif any(keyword in var_lower for keyword in ['token', 'key', 'secret', 'password', 'auth']): + return '🔐' + # Version/tag related + elif any(keyword in var_lower for keyword in ['version', 'tag', 'release']): + return '🏷️' + # Path/directory related + elif any(keyword in var_lower for keyword in ['path', 'dir', 'folder']): + return '📁' + # Default + else: + return '🔧' + def prompt_for_missing_vars(self, content, vars): parsed_content = self.env.parse(content) undeclared_variables = meta.find_undeclared_variables(parsed_content) @@ -127,22 +159,24 @@ def prompt_for_missing_vars(self, content, vars): else: # Interactive prompt with enum support (choose by value or index) enum = conf.get('enum') + var_type = conf.get('type', 'string') # Get description if available (support both 'description' and 'help' fields) description = conf.get('description') or conf.get('help') + # Get contextual icon + icon = self._get_variable_icon(var, var_type) + if enum: - # Build options list string like "(1) dev, (2) prod)" + # Build options list string like "(1) dev, (2) staging, (3) prod" options = ", ".join([f"({i+1}) {val}" for i, val in enumerate(enum)]) - prompt = f"❓ Enter value for {var} [{default}] {options}: " - # Display description on a new line if available if description: - print(prompt.rstrip()) - print(f" Description: {description}") - raw = input(" ") or default + print(f"{icon} {var}: {description}") + print(f" Options: {options}") + raw = input(f" Enter value [{default}]: ") or default else: - raw = input(prompt) or default + raw = input(f"{icon} {var} [{default}] {options}: ") or default raw = raw.strip() if raw == "": @@ -155,15 +189,11 @@ def prompt_for_missing_vars(self, content, vars): # For invalid enum input, raise immediately instead of re-prompting raise ValueError(f"Variable '{var}' must be one of {enum}, got: {raw}") else: - prompt = f"❓ Enter value for {var} [{default}]: " - - # Display description on a new line if available if description: - print(prompt.rstrip()) - print(f" Description: {description}") - user_input = input(" ") or default + print(f"{icon} {var}: {description}") + user_input = input(f" Enter value [{default}]: ") or default else: - user_input = input(prompt) or default + user_input = input(f"{icon} {var} [{default}]: ") or default # Coerce and validate according to schema coerced = self._coerce_and_validate(var, user_input, conf) self.input_store.set_value(var, coerced) diff --git a/tests/test_template_renderer.py b/tests/test_template_renderer.py index 0ba3b0a..70cde50 100644 --- a/tests/test_template_renderer.py +++ b/tests/test_template_renderer.py @@ -108,7 +108,7 @@ def test_render_template_with_mappings(): def test_prompt_with_description_display(): - """Test that variable descriptions are displayed in interactive prompts""" + """Test that variable descriptions are displayed in interactive prompts with Option 4 formatting""" config_variables = [ {"project_name": { "type": "string", @@ -137,7 +137,7 @@ def test_prompt_with_description_display(): # Test each variable type separately to ensure proper input handling - # Test 1: Regular variable with description + # Test 1: Regular variable with description (should show icon + description format) content1 = "{{@ project_name @}}" vars1 = {} with patch('builtins.input', return_value="TestProject") as mock_input, \ @@ -145,11 +145,11 @@ def test_prompt_with_description_display(): result_vars1 = renderer.prompt_for_missing_vars(content1, vars1) assert result_vars1["project_name"] == "TestProject" - # Check that description was printed + # Check that the new format was printed (icon + var: description) print_calls = [call.args[0] for call in mock_print.call_args_list] - assert any("Description: The name of your project" in call for call in print_calls) + assert any("🚀 project_name: The name of your project" in call for call in print_calls) - # Test 2: Enum variable with description + # Test 2: Enum variable with description (should show icon + description + options) content2 = "{{@ environment @}}" vars2 = {} with patch('builtins.input', return_value="prod") as mock_input, \ @@ -157,9 +157,10 @@ def test_prompt_with_description_display(): result_vars2 = renderer.prompt_for_missing_vars(content2, vars2) assert result_vars2["environment"] == "prod" - # Check that description was printed for enum + # Check that description and options were printed in new format print_calls = [call.args[0] for call in mock_print.call_args_list] - assert any("Description: Target deployment environment" in call for call in print_calls) + assert any("🌍 environment: Target deployment environment" in call for call in print_calls) + assert any("Options: (1) dev, (2) staging, (3) prod" in call for call in print_calls) # Test 3: Variable with 'help' field (backward compatibility) content3 = "{{@ old_style_help @}}" @@ -169,11 +170,11 @@ def test_prompt_with_description_display(): result_vars3 = renderer.prompt_for_missing_vars(content3, vars3) assert result_vars3["old_style_help"] == "help_test" - # Check that help was printed as description + # Check that help was printed in new format print_calls = [call.args[0] for call in mock_print.call_args_list] - assert any("Description: This uses the old 'help' field" in call for call in print_calls) + assert any("🔧 old_style_help: This uses the old 'help' field" in call for call in print_calls) - # Test 4: Variable without description (should not print description) + # Test 4: Variable without description (should use compact format with icon) content4 = "{{@ no_description @}}" vars4 = {} with patch('builtins.input', return_value="no_desc_test") as mock_input, \ @@ -181,7 +182,25 @@ def test_prompt_with_description_display(): result_vars4 = renderer.prompt_for_missing_vars(content4, vars4) assert result_vars4["no_description"] == "no_desc_test" - # Check that no description was printed + # Check that no description line was printed (should use inline format) print_calls = [call.args[0] for call in mock_print.call_args_list] - # Should not contain any description text - assert not any("Description:" in call for call in print_calls) + # Should not contain the two-line format with description + assert not any(": " in call and "no_description" in call for call in print_calls) + +def test_variable_icon_selection(): + """Test that appropriate icons are selected for different variable types""" + config_variables = [] + input_store = "/tmp/input.json" + non_interactive = True + renderer = TemplateRenderer(config_variables, input_store, non_interactive) + + # Test icon selection logic + assert renderer._get_variable_icon("project_name", "string") == "🚀" + assert renderer._get_variable_icon("environment", "string") == "🌍" + assert renderer._get_variable_icon("port", "integer") == "🔌" + assert renderer._get_variable_icon("enable_logging", "boolean") == "⚡" + assert renderer._get_variable_icon("api_token", "string") == "🔐" + assert renderer._get_variable_icon("database_url", "string") == "🗄️" + assert renderer._get_variable_icon("version", "string") == "🏷️" + assert renderer._get_variable_icon("config_path", "string") == "📁" + assert renderer._get_variable_icon("random_var", "string") == "🔧" From 4dce343f05ef2e7ad779ac720d0b342c699df422 Mon Sep 17 00:00:00 2001 From: Kenneth Belitzky Date: Mon, 22 Sep 2025 11:52:43 -0300 Subject: [PATCH 3/4] feat: add bold formatting for variable names in prompts - Use ANSI escape codes to make variable names bold in interactive prompts - Improves readability and visual hierarchy of prompts - Update test assertions to match new bold formatting - Update documentation to mention bold variable names - All tests passing with new formatting --- docs/template-variables.md | 4 +++- struct_module/template_renderer.py | 12 ++++++++---- tests/test_template_renderer.py | 12 ++++++------ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/template-variables.md b/docs/template-variables.md index aa60667..81e2620 100644 --- a/docs/template-variables.md +++ b/docs/template-variables.md @@ -84,7 +84,7 @@ variables: default: 8080 ``` -When prompted interactively, variables with descriptions will display with contextual icons and clean formatting: +When prompted interactively, variables with descriptions will display with contextual icons, **bold variable names**, and clean formatting: ``` 🚀 project_name: The name of your project @@ -102,6 +102,8 @@ For variables without descriptions, a more compact format is used: ⚡ enable_logging [true]: ``` +**Note**: Variable names appear in **bold** in actual terminal output for better readability. + **Contextual Icons**: STRUCT automatically selects appropriate icons based on variable names and types: - 🚀 Project/app names - 🌍 Environment/deployment variables diff --git a/struct_module/template_renderer.py b/struct_module/template_renderer.py index 635360a..9d634f4 100644 --- a/struct_module/template_renderer.py +++ b/struct_module/template_renderer.py @@ -167,16 +167,20 @@ def prompt_for_missing_vars(self, content, vars): # Get contextual icon icon = self._get_variable_icon(var, var_type) + # ANSI color codes for formatting + BOLD = '\033[1m' + RESET = '\033[0m' + if enum: # Build options list string like "(1) dev, (2) staging, (3) prod" options = ", ".join([f"({i+1}) {val}" for i, val in enumerate(enum)]) if description: - print(f"{icon} {var}: {description}") + print(f"{icon} {BOLD}{var}{RESET}: {description}") print(f" Options: {options}") raw = input(f" Enter value [{default}]: ") or default else: - raw = input(f"{icon} {var} [{default}] {options}: ") or default + raw = input(f"{icon} {BOLD}{var}{RESET} [{default}] {options}: ") or default raw = raw.strip() if raw == "": @@ -190,10 +194,10 @@ def prompt_for_missing_vars(self, content, vars): raise ValueError(f"Variable '{var}' must be one of {enum}, got: {raw}") else: if description: - print(f"{icon} {var}: {description}") + print(f"{icon} {BOLD}{var}{RESET}: {description}") user_input = input(f" Enter value [{default}]: ") or default else: - user_input = input(f"{icon} {var} [{default}]: ") or default + user_input = input(f"{icon} {BOLD}{var}{RESET} [{default}]: ") or default # Coerce and validate according to schema coerced = self._coerce_and_validate(var, user_input, conf) self.input_store.set_value(var, coerced) diff --git a/tests/test_template_renderer.py b/tests/test_template_renderer.py index 70cde50..3ea5008 100644 --- a/tests/test_template_renderer.py +++ b/tests/test_template_renderer.py @@ -145,9 +145,9 @@ def test_prompt_with_description_display(): result_vars1 = renderer.prompt_for_missing_vars(content1, vars1) assert result_vars1["project_name"] == "TestProject" - # Check that the new format was printed (icon + var: description) + # Check that the new format was printed (icon + bold var: description) print_calls = [call.args[0] for call in mock_print.call_args_list] - assert any("🚀 project_name: The name of your project" in call for call in print_calls) + assert any("🚀 \033[1mproject_name\033[0m: The name of your project" in call for call in print_calls) # Test 2: Enum variable with description (should show icon + description + options) content2 = "{{@ environment @}}" @@ -157,9 +157,9 @@ def test_prompt_with_description_display(): result_vars2 = renderer.prompt_for_missing_vars(content2, vars2) assert result_vars2["environment"] == "prod" - # Check that description and options were printed in new format + # Check that description and options were printed in new format with bold print_calls = [call.args[0] for call in mock_print.call_args_list] - assert any("🌍 environment: Target deployment environment" in call for call in print_calls) + assert any("🌍 \033[1menvironment\033[0m: Target deployment environment" in call for call in print_calls) assert any("Options: (1) dev, (2) staging, (3) prod" in call for call in print_calls) # Test 3: Variable with 'help' field (backward compatibility) @@ -170,9 +170,9 @@ def test_prompt_with_description_display(): result_vars3 = renderer.prompt_for_missing_vars(content3, vars3) assert result_vars3["old_style_help"] == "help_test" - # Check that help was printed in new format + # Check that help was printed in new format with bold print_calls = [call.args[0] for call in mock_print.call_args_list] - assert any("🔧 old_style_help: This uses the old 'help' field" in call for call in print_calls) + assert any("🔧 \033[1mold_style_help\033[0m: This uses the old 'help' field" in call for call in print_calls) # Test 4: Variable without description (should use compact format with icon) content4 = "{{@ no_description @}}" From 6cf306740d7502e52d3db741071c3d3f3256277d Mon Sep 17 00:00:00 2001 From: Kenneth Belitzky Date: Fri, 24 Oct 2025 11:13:26 -0300 Subject: [PATCH 4/4] Add WARP.md --- WARP.md | 492 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 492 insertions(+) create mode 100644 WARP.md diff --git a/WARP.md b/WARP.md new file mode 100644 index 0000000..4195703 --- /dev/null +++ b/WARP.md @@ -0,0 +1,492 @@ +# WARP.md - STRUCT Project Guide + +> This file serves as a comprehensive guide for developers working with the STRUCT project. It contains project-specific conventions, development workflows, and institutional knowledge. + +## 📋 Table of Contents + +- [Project Overview](#project-overview) +- [Development Environment](#development-environment) +- [Code Structure](#code-structure) +- [Development Workflow](#development-workflow) +- [Testing Guidelines](#testing-guidelines) +- [Code Style & Standards](#code-style--standards) +- [Release Process](#release-process) +- [Common Tasks](#common-tasks) +- [Troubleshooting](#troubleshooting) +- [Architecture Decisions](#architecture-decisions) +- [Performance Considerations](#performance-considerations) +- [Security Guidelines](#security-guidelines) +- [Documentation Standards](#documentation-standards) +- [Dependencies & Tools](#dependencies--tools) +- [Monitoring & Observability](#monitoring--observability) +- [Issue & Work Management](#issue--work-management) + +## 🎯 Project Overview + +### Mission +STRUCT simplifies project organization by creating consistent file and folder structures tailored to specific needs. It enhances productivity and maintains uniformity across projects through YAML-based configuration files. + +### Key Features +- **YAML-Based Configuration**: Simple, readable project structure definitions +- **Template Variables**: Dynamic content with Jinja2 templating and interactive prompts +- **Remote Content Fetching**: Support for GitHub, HTTP/HTTPS, S3, and Google Cloud Storage +- **Smart File Handling**: Multiple strategies for managing existing files +- **Automation Hooks**: Pre and post-generation shell commands +- **MCP Integration**: Model Context Protocol support for AI-assisted workflows + +### Technology Stack +- **Language**: Python 3.12+ +- **CLI Framework**: argparse with custom command pattern +- **Templating**: Jinja2 with custom delimiters +- **Testing**: pytest with coverage reporting +- **Documentation**: MkDocs with Material theme +- **CI/CD**: GitHub Actions +- **Package Management**: pip with requirements.txt + +## 🛠 Development Environment + +### Prerequisites +```bash +# Python 3.12 or higher +python --version + +# Virtual environment (recommended) +python -m venv .venv +source .venv/bin/activate # Linux/macOS +# or +.venv\Scripts\activate # Windows + +# Install dependencies +pip install -r requirements.txt +pip install -r requirements.dev.txt +``` + +### Environment Variables +```bash +# Optional: OpenAI API key for AI features +export OPENAI_API_KEY="your-api-key-here" + +# Optional: GitHub token for private repo access +export GITHUB_TOKEN="your-github-token" + +# Optional: Logging level +export STRUCT_LOG_LEVEL="DEBUG" +``` + +### IDE Configuration +- **VS Code**: Recommended extensions in `.vscode/extensions.json` +- **PyCharm**: Python interpreter should point to `.venv/bin/python` +- **Pre-commit hooks**: Run `pre-commit install` after setup + +## 🏗 Code Structure + +### Directory Layout +``` +struct_module/ +├── commands/ # CLI command implementations +│ ├── generate.py # Main generation command +│ ├── validate.py # YAML validation +│ ├── list.py # List available structures +│ └── ... +├── contribs/ # Built-in structure templates +├── filters.py # Jinja2 custom filters +├── template_renderer.py # Core templating logic +├── file_item.py # File handling and processing +├── input_store.py # User input persistence +├── utils.py # Utility functions +└── main.py # Entry point + +tests/ # Test suite +docs/ # Documentation source +examples/ # Example configurations +``` + +### Key Modules + +#### `template_renderer.py` +- Handles Jinja2 templating with custom delimiters +- Interactive variable prompting with descriptions +- Type coercion and validation +- Icon-based user interface + +#### `file_item.py` +- Represents files to be created/modified +- Handles different content sources (inline, remote, etc.) +- Implements file creation strategies (overwrite, skip, backup, etc.) + +#### `commands/` +- Each command is a separate class inheriting from `Command` +- Self-contained argument parsing and execution logic +- Consistent error handling and logging + +## 🔄 Development Workflow + +### Branch Strategy +- `main`: Production-ready code +- `feature/*`: New features (e.g., `feature/display-variable-descriptions-116`) +- `bugfix/*`: Bug fixes +- `hotfix/*`: Critical production fixes +- `docs/*`: Documentation updates + +### Commit Message Convention +``` +(): + + + +