Pre-process YAML content to resolve all !include directives. This will take the yaml content as a string and return the same string with all !include directives resolved. The reason we don't go with pyyaml-include or similar options is that they don't support merge keys, see https:/
(yaml_content: str, *, base_dir: Path = Path("."))
| 3 | |
| 4 | |
| 5 | def resolve_includes(yaml_content: str, *, base_dir: Path = Path(".")) -> str: |
| 6 | """Pre-process YAML content to resolve all !include directives. |
| 7 | This will take the yaml content as a string and return the same string with all |
| 8 | !include directives resolved. |
| 9 | |
| 10 | The reason we don't go with pyyaml-include or similar options is that they don't |
| 11 | support merge keys, see https://github.com/tanbro/pyyaml-include/issues/53 . |
| 12 | """ |
| 13 | |
| 14 | def include_replacer(match): |
| 15 | indentation = match.group(1) # Capture the indentation |
| 16 | prefix = match.group(2) # What comes before !include (like "<<: " or "key: ") |
| 17 | include_path = match.group(3) |
| 18 | full_path = base_dir / include_path |
| 19 | |
| 20 | included_content = full_path.read_text().strip() |
| 21 | |
| 22 | # Handle merge key specially |
| 23 | if prefix.strip() == "<<:": |
| 24 | # For merge keys, include the content with proper indentation |
| 25 | if indentation: |
| 26 | content_indent = indentation + " " |
| 27 | included_lines = included_content.splitlines() |
| 28 | indented_lines = [content_indent + line if line.strip() else line for line in included_lines] |
| 29 | return indentation + "<<:\n" + "\n".join(indented_lines) |
| 30 | else: |
| 31 | # Indent the included content by 2 spaces for proper merge format |
| 32 | included_lines = included_content.splitlines() |
| 33 | indented_lines = [" " + line if line.strip() else line for line in included_lines] |
| 34 | return "<<:\n" + "\n".join(indented_lines) |
| 35 | else: |
| 36 | # For regular includes, we need to handle indentation properly |
| 37 | # If the prefix ends with ': ', we need to add a newline and indent the content |
| 38 | if prefix.strip().endswith(":"): |
| 39 | # Calculate the indentation for the included content |
| 40 | content_indent = indentation + " " # Add 2 spaces for proper YAML nesting |
| 41 | included_lines = included_content.splitlines() |
| 42 | indented_lines = [content_indent + line if line.strip() else line for line in included_lines] |
| 43 | return indentation + prefix.strip() + "\n" + "\n".join(indented_lines) |
| 44 | else: |
| 45 | # For cases where it's not a key assignment, just replace with content |
| 46 | return indentation + prefix + included_content |
| 47 | |
| 48 | # Pattern to match !include directives with proper capture groups |
| 49 | # Group 1: indentation, Group 2: prefix (like "<<: " or "key: "), Group 3: file path |
| 50 | include_pattern = r"^(\s*)(.*?)!include\s+(.+)$" |
| 51 | |
| 52 | # Keep resolving includes until no more are found |
| 53 | while re.search(include_pattern, yaml_content, re.MULTILINE): |
| 54 | yaml_content = re.sub(include_pattern, include_replacer, yaml_content, flags=re.MULTILINE) |
| 55 | |
| 56 | return yaml_content |