Skip to content

Commit 1d0d036

Browse files
committed
feat: microgen - adds main execution and post-processing logic
1 parent a4276fe commit 1d0d036

File tree

1 file changed

+134
-0
lines changed

1 file changed

+134
-0
lines changed

scripts/microgenerator/generate.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import ast
2626
import os
27+
import argparse
2728
import glob
2829
import logging
2930
import re
@@ -497,6 +498,7 @@ def analyze_source_files(
497498
# Section 3: Code Generation
498499
# =============================================================================
499500

501+
500502
def _generate_import_statement(
501503
context: List[Dict[str, Any]], key: str, path: str
502504
) -> str:
@@ -610,3 +612,135 @@ def generate_code(config: Dict[str, Any], analysis_results: tuple) -> None:
610612
)
611613

612614
utils.write_code_to_file(output_path, final_code)
615+
616+
617+
# =============================================================================
618+
# Section 4: Main Execution
619+
# =============================================================================
620+
621+
622+
def setup_config_and_paths(config_path: str) -> Dict[str, Any]:
623+
"""Loads the configuration and sets up necessary paths.
624+
625+
Args:
626+
config_path: The path to the YAML configuration file.
627+
628+
Returns:
629+
A dictionary containing the loaded configuration and paths.
630+
"""
631+
632+
def find_project_root(start_path: str, markers: list[str]) -> str | None:
633+
"""Finds the project root by searching upwards for a marker."""
634+
current_path = os.path.abspath(start_path)
635+
while True:
636+
for marker in markers:
637+
if os.path.exists(os.path.join(current_path, marker)):
638+
return current_path
639+
parent_path = os.path.dirname(current_path)
640+
if parent_path == current_path: # Filesystem root
641+
return None
642+
current_path = parent_path
643+
644+
# Load configuration from the YAML file.
645+
config = utils.load_config(config_path)
646+
647+
# Determine the project root.
648+
script_dir = os.path.dirname(os.path.abspath(__file__))
649+
project_root = find_project_root(script_dir, ["setup.py"])
650+
if not project_root:
651+
project_root = os.getcwd() # Fallback to current directory
652+
653+
# Set paths in the config dictionary.
654+
config["project_root"] = project_root
655+
config["config_dir"] = os.path.dirname(os.path.abspath(config_path))
656+
657+
return config
658+
659+
660+
def _execute_post_processing(config: Dict[str, Any]):
661+
"""
662+
Executes post-processing steps, such as patching existing files.
663+
"""
664+
project_root = config["project_root"]
665+
post_processing_jobs = config.get("post_processing_templates", [])
666+
667+
for job in post_processing_jobs:
668+
template_path = os.path.join(config["config_dir"], job["template"])
669+
target_file_path = os.path.join(project_root, job["target_file"])
670+
671+
if not os.path.exists(target_file_path):
672+
logging.warning(
673+
f"Target file {target_file_path} not found, skipping post-processing job."
674+
)
675+
continue
676+
677+
# Read the target file
678+
with open(target_file_path, "r") as f:
679+
lines = f.readlines()
680+
681+
# --- Extract existing imports and __all__ members ---
682+
imports = []
683+
all_list = []
684+
all_start_index = -1
685+
all_end_index = -1
686+
687+
for i, line in enumerate(lines):
688+
if line.strip().startswith("from ."):
689+
imports.append(line.strip())
690+
if line.strip() == "__all__ = (":
691+
all_start_index = i
692+
if all_start_index != -1 and line.strip() == ")":
693+
all_end_index = i
694+
695+
if all_start_index != -1 and all_end_index != -1:
696+
for i in range(all_start_index + 1, all_end_index):
697+
member = lines[i].strip().replace('"', "").replace(",", "")
698+
if member:
699+
all_list.append(member)
700+
701+
# --- Add new items and sort ---
702+
for new_import in job.get("add_imports", []):
703+
if new_import not in imports:
704+
imports.append(new_import)
705+
imports.sort()
706+
imports = [f"{imp}\n" for imp in imports] # re-add newlines
707+
708+
for new_member in job.get("add_to_all", []):
709+
if new_member not in all_list:
710+
all_list.append(new_member)
711+
all_list.sort()
712+
713+
# --- Render the new file content ---
714+
template = utils.load_template(template_path)
715+
new_content = template.render(
716+
imports=imports,
717+
all_list=all_list,
718+
)
719+
720+
# --- Overwrite the target file ---
721+
with open(target_file_path, "w") as f:
722+
f.write(new_content)
723+
724+
logging.info(f"Successfully post-processed and overwrote {target_file_path}")
725+
726+
727+
if __name__ == "__main__":
728+
parser = argparse.ArgumentParser(
729+
description="A generic Python code generator for clients."
730+
)
731+
parser.add_argument("config", help="Path to the YAML configuration file.")
732+
args = parser.parse_args()
733+
734+
# Load config and set up paths.
735+
config = setup_config_and_paths(args.config)
736+
737+
# Analyze the source code.
738+
analysis_results = analyze_source_files(config)
739+
740+
# Generate the new client code.
741+
generate_code(config, analysis_results)
742+
743+
# Run post-processing steps.
744+
_execute_post_processing(config)
745+
746+
# TODO: Ensure blacken gets called on the generated source files as a final step.

0 commit comments

Comments
 (0)