import re def process_template(input_text): """ Process a text template with macro instructions and variable substitution. Supports multiple values for variables to generate multiple output versions. Each section between macro lines is treated as a separate template. Args: input_text (str): The input template text Returns: tuple: (output_text, error_message) - output_text: Processed output with variables substituted, or empty string if error - error_message: Error description and problematic line, or empty string if no error """ lines = input_text.strip().split('\n') current_variables = {} current_template_lines = [] all_output_lines = [] error_message = "" # Process the input line by line line_number = 0 while line_number < len(lines): orig_line = lines[line_number] line = orig_line.strip() line_number += 1 # Skip empty lines or comments if not line or line.startswith('#'): continue # Handle macro instructions if line.startswith('!'): # Process any accumulated template lines before starting a new macro if current_template_lines: # Process the current template with current variables template_output, err = process_current_template(current_template_lines, current_variables) if err: return "", err all_output_lines.extend(template_output) current_template_lines = [] # Reset template lines # Reset variables for the new macro current_variables = {} # Parse the macro line macro_line = line[1:].strip() # Check for unmatched braces in the whole line open_braces = macro_line.count('{') close_braces = macro_line.count('}') if open_braces != close_braces: error_message = f"Unmatched braces: {open_braces} opening '{{' and {close_braces} closing '}}' braces\nLine: '{orig_line}'" return "", error_message # Check for unclosed quotes if macro_line.count('"') % 2 != 0: error_message = f"Unclosed double quotes\nLine: '{orig_line}'" return "", error_message # Split by optional colon separator var_sections = re.split(r'\s*:\s*', macro_line) for section in var_sections: section = section.strip() if not section: continue # Extract variable name var_match = re.search(r'\{([^}]+)\}', section) if not var_match: if '{' in section or '}' in section: error_message = f"Malformed variable declaration\nLine: '{orig_line}'" return "", error_message continue var_name = var_match.group(1).strip() if not var_name: error_message = f"Empty variable name\nLine: '{orig_line}'" return "", error_message # Check variable value format value_part = section[section.find('}')+1:].strip() if not value_part.startswith('='): error_message = f"Missing '=' after variable '{{{var_name}}}'\nLine: '{orig_line}'" return "", error_message # Extract all quoted values var_values = re.findall(r'"([^"]*)"', value_part) # Check if there are values specified if not var_values: error_message = f"No quoted values found for variable '{{{var_name}}}'\nLine: '{orig_line}'" return "", error_message # Check for missing commas between values # Look for patterns like "value""value" (missing comma) if re.search(r'"[^,]*"[^,]*"', value_part): error_message = f"Missing comma between values for variable '{{{var_name}}}'\nLine: '{orig_line}'" return "", error_message # Store the variable values current_variables[var_name] = var_values # Handle template lines else: # Check for unknown variables in template line var_references = re.findall(r'\{([^}]+)\}', line) for var_ref in var_references: if var_ref not in current_variables: error_message = f"Unknown variable '{{{var_ref}}}' in template\nLine: '{orig_line}'" return "", error_message # Add to current template lines current_template_lines.append(line) # Process any remaining template lines if current_template_lines: template_output, err = process_current_template(current_template_lines, current_variables) if err: return "", err all_output_lines.extend(template_output) return '\n'.join(all_output_lines), "" def process_current_template(template_lines, variables): """ Process a set of template lines with the current variables. Args: template_lines (list): List of template lines to process variables (dict): Dictionary of variable names to lists of values Returns: tuple: (output_lines, error_message) """ if not variables or not template_lines: return template_lines, "" output_lines = [] # Find the maximum number of values for any variable max_values = max(len(values) for values in variables.values()) # Generate each combination for i in range(max_values): for template in template_lines: output_line = template for var_name, var_values in variables.items(): # Use modulo to cycle through values if needed value_index = i % len(var_values) var_value = var_values[value_index] output_line = output_line.replace(f"{{{var_name}}}", var_value) output_lines.append(output_line) return output_lines, "" def extract_variable_names(macro_line): """ Extract all variable names from a macro line. Args: macro_line (str): A macro line (with or without the leading '!') Returns: tuple: (variable_names, error_message) - variable_names: List of variable names found in the macro - error_message: Error description if any, empty string if no error """ # Remove leading '!' if present if macro_line.startswith('!'): macro_line = macro_line[1:].strip() variable_names = [] # Check for unmatched braces open_braces = macro_line.count('{') close_braces = macro_line.count('}') if open_braces != close_braces: return [], f"Unmatched braces: {open_braces} opening '{{' and {close_braces} closing '}}' braces" # Split by optional colon separator var_sections = re.split(r'\s*:\s*', macro_line) for section in var_sections: section = section.strip() if not section: continue # Extract variable name var_matches = re.findall(r'\{([^}]+)\}', section) for var_name in var_matches: new_var = var_name.strip() if not new_var in variable_names: variable_names.append(new_var) return variable_names, "" def extract_variable_values(macro_line): """ Extract all variable names and their values from a macro line. Args: macro_line (str): A macro line (with or without the leading '!') Returns: tuple: (variables_dict, error_message) - variables_dict: Dictionary mapping variable names to their values - error_message: Error description if any, empty string if no error """ # Remove leading '!' if present if macro_line.startswith('!'): macro_line = macro_line[1:].strip() variables = {} # Check for unmatched braces open_braces = macro_line.count('{') close_braces = macro_line.count('}') if open_braces != close_braces: return {}, f"Unmatched braces: {open_braces} opening '{{' and {close_braces} closing '}}' braces" # Check for unclosed quotes if macro_line.count('"') % 2 != 0: return {}, "Unclosed double quotes" # Split by optional colon separator var_sections = re.split(r'\s*:\s*', macro_line) for section in var_sections: section = section.strip() if not section: continue # Extract variable name var_match = re.search(r'\{([^}]+)\}', section) if not var_match: if '{' in section or '}' in section: return {}, "Malformed variable declaration" continue var_name = var_match.group(1).strip() if not var_name: return {}, "Empty variable name" # Check variable value format value_part = section[section.find('}')+1:].strip() if not value_part.startswith('='): return {}, f"Missing '=' after variable '{{{var_name}}}'" # Extract all quoted values var_values = re.findall(r'"([^"]*)"', value_part) # Check if there are values specified if not var_values: return {}, f"No quoted values found for variable '{{{var_name}}}'" # Check for missing commas between values if re.search(r'"[^,]*"[^,]*"', value_part): return {}, f"Missing comma between values for variable '{{{var_name}}}'" variables[var_name] = var_values return variables, "" def generate_macro_line(variables_dict): """ Generate a macro line from a dictionary of variable names and their values. Args: variables_dict (dict): Dictionary mapping variable names to lists of values Returns: str: A formatted macro line (including the leading '!') """ sections = [] for var_name, values in variables_dict.items(): # Format each value with quotes quoted_values = [f'"{value}"' for value in values] # Join values with commas values_str = ','.join(quoted_values) # Create the variable assignment section = f"{{{var_name}}}={values_str}" sections.append(section) # Join sections with a colon and space for readability return "! " + " : ".join(sections)