33import os
44
55
6- def inspect_file_extensions (filename : str ):
6+ def inspect_file_extensions (filename : str , check_extension : bool = True ):
77 """Attempt to strip two levels of file extensions to determine the schema.
88
99 filename examples: fodo.pals.yaml, fodo.pals.json, ...
1010 """
1111 file_noext , extension = os .path .splitext (filename )
1212 file_noext_noext , extension_inner = os .path .splitext (file_noext )
1313
14- if extension_inner != ".pals" :
14+ if check_extension and extension_inner != ".pals" :
1515 raise RuntimeError (
1616 f"inspect_file_extensions: No support for file { filename } with extension { extension } . "
1717 f"PALS files must end in .pals.json or .pals.yaml or similar."
@@ -25,11 +25,94 @@ def inspect_file_extensions(filename: str):
2525 }
2626
2727
28- def load_file_to_dict (filename : str ) -> dict :
28+ def process_includes (data , base_dir : str ):
29+ """Recursively process 'include' directives in the data structure."""
30+ if isinstance (data , dict ):
31+ # Handle 'include' key in dictionary
32+ if "include" in data :
33+ include_file = data ["include" ]
34+ # Check if include_file is a string (filename)
35+ if isinstance (include_file , str ):
36+ filepath = os .path .join (base_dir , include_file )
37+ # Load included file without strict extension check
38+ included_data = load_file_to_dict (filepath , check_extension = False )
39+
40+ # Remove 'include' key
41+ local_data = data .copy ()
42+ del local_data ["include" ]
43+
44+ # Recursively process local data
45+ local_data = {
46+ k : process_includes (v , base_dir ) for k , v in local_data .items ()
47+ }
48+
49+ # Merge logic
50+ # If included data is a list of single-key dicts (PALS special case), try to merge as dict
51+ if isinstance (included_data , list ):
52+ try :
53+ merged_included = {}
54+ all_dicts = True
55+ for item in included_data :
56+ if isinstance (item , dict ) and len (item ) == 1 :
57+ merged_included .update (item )
58+ else :
59+ all_dicts = False
60+ break
61+ if all_dicts :
62+ included_data = merged_included
63+ except Exception :
64+ pass
65+
66+ if isinstance (included_data , dict ):
67+ # Merge included data with local data (local overrides included?)
68+ # Spec: "Included file data will be included verbatim at the current level of nesting."
69+ # Usually specific (local) overrides generic (included).
70+ # So we take included, update with local.
71+ result = included_data .copy ()
72+ result .update (local_data )
73+ return result
74+ else :
75+ # If included data is not a dict, we can't merge it into a dict.
76+ # Unless the dict was JUST the include?
77+ if not local_data :
78+ return included_data
79+ # Fallback: return local data (ignore include) or error?
80+ # For now, let's return local_data but maybe warn?
81+ # Or maybe return included_data if local_data is empty?
82+ return local_data
83+
84+ # Recurse on values if no include or after processing
85+ return {k : process_includes (v , base_dir ) for k , v in data .items ()}
86+
87+ elif isinstance (data , list ):
88+ new_list = []
89+ for item in data :
90+ # Check if item is a dict with ONLY 'include' key
91+ if isinstance (item , dict ) and "include" in item and len (item ) == 1 :
92+ include_file = item ["include" ]
93+ if isinstance (include_file , str ):
94+ filepath = os .path .join (base_dir , include_file )
95+ included_data = load_file_to_dict (filepath , check_extension = False )
96+
97+ if isinstance (included_data , list ):
98+ new_list .extend (included_data )
99+ else :
100+ new_list .append (included_data )
101+ else :
102+ new_list .append (process_includes (item , base_dir ))
103+ else :
104+ new_list .append (process_includes (item , base_dir ))
105+ return new_list
106+
107+ else :
108+ return data
109+
110+
111+ def load_file_to_dict (filename : str , check_extension : bool = True ) -> dict :
29112 # Attempt to strip two levels of file extensions to determine the schema.
30113 # Examples: fodo.pals.yaml, fodo.pals.json, ...
31114 file_noext , extension , file_noext_noext , extension_inner = inspect_file_extensions (
32- filename
115+ filename , check_extension = check_extension
33116 ).values ()
34117
35118 # examples: fodo.pals.yaml, fodo.pals.json
@@ -51,6 +134,9 @@ def load_file_to_dict(filename: str) -> dict:
51134 f"load_file_to_dict: No support for PALS file { filename } with extension { extension } yet."
52135 )
53136
137+ # Process includes
138+ pals_data = process_includes (pals_data , base_dir = os .path .dirname (filename ))
139+
54140 return pals_data
55141
56142
0 commit comments