Skip to content

Commit eee0e8f

Browse files
authored
Add Folder Handling and Logging Enhancements (#12)
#### Overview This PR introduces folder structure support in the `generate` and `validate` commands, along with improved logging functionality using `colorlog` for better debugging and runtime insights. #### Changes - **Folder Structure Support**: - The `generate.py` script now supports creating folder structures from YAML definitions. - Added validation for the folder structure in `validate.py`, ensuring folders are correctly defined and contain a valid `struct` key. - **Logging Enhancements**: - New logging configuration file `logging_config.py` introduced, integrating `colorlog` for color-coded log output. - Refactored log levels in various parts of the code, changing some `info` logs to `debug` where appropriate for better verbosity control. - **Dependency Update**: - Added `colorlog` to `requirements.txt` to support colored logging. #### Justification The ability to define folder structures in YAML is critical for handling more complex project setups, especially for projects relying on modular directory layouts. Enhanced logging is essential for improving the traceability and debuggability of the tool's operations. #### Impact - Teams will now be able to define and validate folder structures more easily within the configuration files. - Logging improvements will make debugging easier and more informative, particularly during dry runs and when setting up folder hierarchies. - The addition of `colorlog` may require updating environments to include the new dependency.
1 parent 6ffc68f commit eee0e8f

6 files changed

Lines changed: 95 additions & 7 deletions

File tree

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ python-dotenv
55
jinja2
66
PyGithub
77
argcomplete
8+
colorlog

struct_module/commands/generate.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from struct_module.commands import Command
22
import os
33
import yaml
4+
import argparse
45
from struct_module.file_item import FileItem
56
from struct_module.completers import file_strategy_completer
67

@@ -32,6 +33,8 @@ def execute(self, args):
3233

3334

3435
def _create_structure(self, args):
36+
if isinstance(args, dict):
37+
args = argparse.Namespace(**args)
3538
if args.structure_definition.startswith("file://") and args.structure_definition.endswith(".yaml"):
3639
with open(args.structure_definition[7:], 'r') as f:
3740
config = yaml.safe_load(f)
@@ -46,6 +49,7 @@ def _create_structure(self, args):
4649

4750
template_vars = dict(item.split('=') for item in args.vars.split(',')) if args.vars else None
4851
config_structure = config.get('structure', [])
52+
config_folders = config.get('folders', [])
4953
config_variables = config.get('variables', [])
5054

5155
for item in config_structure:
@@ -75,3 +79,29 @@ def _create_structure(self, args):
7579
args.backup or None,
7680
args.file_strategy or 'overwrite'
7781
)
82+
83+
for item in config_folders:
84+
for folder, content in item.items():
85+
folder_path = os.path.join(args.base_path, folder)
86+
if args.dry_run:
87+
self.logger.info(f"[DRY RUN] Would create folder: {folder_path}")
88+
continue
89+
os.makedirs(folder_path, exist_ok=True)
90+
self.logger.info(f"Created folder: {folder_path}")
91+
92+
# check if content has struct value
93+
if 'struct' in content:
94+
self.logger.info(f"Generating structure in folder: {folder} with struct {content['struct']}")
95+
self._create_structure({
96+
'structure_definition': content['struct'],
97+
'base_path': folder_path,
98+
'structures_path': args.structures_path,
99+
'dry_run': args.dry_run,
100+
'vars': args.vars,
101+
'backup': args.backup,
102+
'file_strategy': args.file_strategy,
103+
'global_system_prompt': args.global_system_prompt,
104+
})
105+
self.logger.info(f"Generated structure in folder: {folder} with struct {content['struct']}")
106+
else:
107+
self.logger.warning(f"Unsupported content in folder: {folder}")

struct_module/commands/validate.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,37 @@ def execute(self, args):
1919
config = yaml.safe_load(f)
2020

2121
self._validate_structure_config(config.get('structure', []))
22+
self._validate_folders_config(config.get('folders', []))
2223
self._validate_variables_config(config.get('variables', []))
2324

2425

26+
# Validate the 'folders' key in the configuration file
27+
# folders should be defined as a list of dictionaries
28+
# each dictionary should have a 'struct' key
29+
#
30+
# Example:
31+
# folders:
32+
# - .devops/modules/my_module_one:
33+
# struct: terraform-module
34+
# - .devops/modules/my_module_two:
35+
# struct: terraform-module
36+
def _validate_folders_config(self, folders):
37+
if not isinstance(folders, list):
38+
raise ValueError("The 'folders' key must be a list.")
39+
for item in folders:
40+
if not isinstance(item, dict):
41+
raise ValueError("Each item in the 'folders' list must be a dictionary.")
42+
for name, content in item.items():
43+
if not isinstance(name, str):
44+
raise ValueError("Each name in the 'folders' item must be a string.")
45+
if not isinstance(content, dict):
46+
raise ValueError(f"The content of '{name}' must be a dictionary.")
47+
if 'struct' not in content:
48+
raise ValueError(f"Dictionary item '{name}' must contain a 'struct' key.")
49+
if not isinstance(content['struct'], str):
50+
raise ValueError(f"The 'struct' value for '{name}' must be a string.")
51+
52+
2553
# Validate the 'variables' key in the configuration file
2654
# variables should be defined as a list of dictionaries
2755
# each dictionary should have a 'name' key and optionall 'default' value

struct_module/file_item.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def __init__(self, properties):
3535
def _configure_openai(self):
3636
self.openai_client = OpenAI(api_key=openai_api_key)
3737
if not openai_model:
38-
self.logger.info("OpenAI model not found. Using default model.")
38+
self.logger.debug("OpenAI model not found. Using default model.")
3939
self.openai_model = "gpt-3.5-turbo"
4040
else:
4141
self.logger.debug(f"Using OpenAI model: {openai_model}")
@@ -144,7 +144,7 @@ def create(self, base_path, dry_run=False, backup_path=None, file_strategy='over
144144

145145
with open(file_path, 'w') as f:
146146
f.write(self.content)
147-
self.logger.info(f"Created file: {file_path} with content: \n\n{self.content}")
147+
self.logger.debug(f"Created file: {file_path} with content: \n\n{self.content}")
148148

149149
if self.permissions:
150150
os.chmod(file_path, int(self.permissions, 8))

struct_module/logging_config.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# FILE: struct_module/logging_config.py
2+
import logging
3+
import colorlog
4+
5+
def configure_logging(level=logging.INFO, log_file=None):
6+
"""Configure logging with colorlog."""
7+
handler = colorlog.StreamHandler()
8+
handler.setFormatter(colorlog.ColoredFormatter(
9+
"%(log_color)s[%(asctime)s][%(levelname)s][struct] >>> %(message)s",
10+
datefmt='%Y-%m-%d %H:%M:%S',
11+
log_colors={
12+
'DEBUG': 'cyan',
13+
'INFO': 'green',
14+
'WARNING': 'yellow',
15+
'ERROR': 'red',
16+
'CRITICAL': 'bold_red',
17+
}
18+
))
19+
20+
logging.basicConfig(
21+
level=level,
22+
handlers=[handler],
23+
)
24+
25+
if log_file:
26+
file_handler = logging.FileHandler(log_file)
27+
file_handler.setFormatter(logging.Formatter(
28+
"[%(asctime)s][%(levelname)s][struct] >>> %(message)s",
29+
datefmt='%Y-%m-%d %H:%M:%S'
30+
))
31+
logging.getLogger().addHandler(file_handler)

struct_module/main.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from struct_module.commands.info import InfoCommand
77
from struct_module.commands.validate import ValidateCommand
88
from struct_module.commands.list import ListCommand
9+
from struct_module.logging_config import configure_logging
910

1011

1112

@@ -42,11 +43,8 @@ def main():
4243

4344
logging_level = getattr(logging, args.log.upper(), logging.INFO)
4445

45-
logging.basicConfig(
46-
level=logging_level,
47-
filename=args.log_file,
48-
format='[%(asctime)s][%(levelname)s][struct] >>> %(message)s',
49-
)
46+
configure_logging(level=logging_level, log_file=args.log_file)
47+
5048

5149
args.func(args)
5250

0 commit comments

Comments
 (0)