1+ # -*- coding: utf-8 -*-
2+ # Copyright 2025 Google LLC
3+ #
4+ # Licensed under the Apache License, Version 2.0 (the "License");
5+ # you may not use this file except in compliance with the License.
6+ # You may obtain a copy of the License at
7+ #
8+ # http://www.apache.org/licenses/LICENSE-2.0
9+ #
10+ # Unless required by applicable law or agreed to in writing, software
11+ # distributed under the License is distributed on an "AS IS" BASIS,
12+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ # See the License for the specific language governing permissions and
14+ # limitations under the License.
15+ #
16+
17+ """Utility functions for the microgenerator."""
18+
19+ import os
20+ import sys
21+ import yaml
22+ import jinja2
23+ from typing import Dict , Any , Iterator , Callable
24+
25+
26+ def _load_resource (
27+ loader_func : Callable ,
28+ path : str ,
29+ not_found_exc : type ,
30+ parse_exc : type ,
31+ resource_type_name : str ,
32+ ) -> Any :
33+ """
34+ Generic resource loader with common error handling.
35+
36+ Args:
37+ loader_func: A callable that performs the loading and returns the resource.
38+ It should raise appropriate exceptions on failure.
39+ path: The path/name of the resource for use in error messages.
40+ not_found_exc: The exception type to catch for a missing resource.
41+ parse_exc: The exception type to catch for a malformed resource.
42+ resource_type_name: A human-readable name for the resource type.
43+ """
44+ try :
45+ return loader_func ()
46+ except not_found_exc :
47+ print (f"Error: { resource_type_name } '{ path } ' not found." , file = sys .stderr )
48+ sys .exit (1 )
49+ except parse_exc as e :
50+ print (
51+ f"Error: Could not load { resource_type_name .lower ()} from '{ path } ': { e } " ,
52+ file = sys .stderr ,
53+ )
54+ sys .exit (1 )
55+
56+
57+ def load_template (template_path : str ) -> jinja2 .Template :
58+ """
59+ Loads a Jinja2 template from a given file path.
60+ """
61+ template_dir = os .path .dirname (template_path )
62+ template_name = os .path .basename (template_path )
63+
64+ def _loader () -> jinja2 .Template :
65+ env = jinja2 .Environment (
66+ loader = jinja2 .FileSystemLoader (template_dir or "." ),
67+ trim_blocks = True ,
68+ lstrip_blocks = True ,
69+ )
70+ return env .get_template (template_name )
71+
72+ return _load_resource (
73+ loader_func = _loader ,
74+ path = template_path ,
75+ not_found_exc = jinja2 .exceptions .TemplateNotFound ,
76+ parse_exc = jinja2 .exceptions .TemplateError ,
77+ resource_type_name = "Template file" ,
78+ )
79+
80+
81+ def load_config (config_path : str ) -> Dict [str , Any ]:
82+ """Loads the generator's configuration from a YAML file."""
83+
84+ def _loader () -> Dict [str , Any ]:
85+ with open (config_path , "r" , encoding = "utf-8" ) as f :
86+ return yaml .safe_load (f )
87+
88+ return _load_resource (
89+ loader_func = _loader ,
90+ path = config_path ,
91+ not_found_exc = FileNotFoundError ,
92+ parse_exc = yaml .YAMLError ,
93+ resource_type_name = "Configuration file" ,
94+ )
95+
96+
97+ def walk_codebase (path : str ) -> Iterator [str ]:
98+ """Yields all .py file paths in a directory."""
99+ for root , _ , files in os .walk (path ):
100+ for file in files :
101+ if file .endswith (".py" ):
102+ yield os .path .join (root , file )
103+
104+
105+ def write_code_to_file (output_path : str , content : str ):
106+ """Ensures the output directory exists and writes content to the file."""
107+ output_dir = os .path .dirname (output_path )
108+
109+ # An empty output_dir means the file is in the current directory.
110+ if output_dir :
111+ print (f" Ensuring output directory exists: { os .path .abspath (output_dir )} " )
112+ os .makedirs (output_dir , exist_ok = True )
113+ if not os .path .isdir (output_dir ):
114+ print (f" Error: Output directory was not created." , file = sys .stderr )
115+ sys .exit (1 )
116+
117+ print (f" Writing generated code to: { os .path .abspath (output_path )} " )
118+ with open (output_path , "w" , encoding = "utf-8" ) as f :
119+ f .write (content )
120+ print (f"Successfully generated { output_path } " )
0 commit comments