Recursively replace string placeholders with parameterized phrases I've written a python script to replace certain placeholders in a text with defined phrases. Some of the phrases might contain placeholders themselves, so the script needs to recursively replace them. Additionally the placeholders might allow parameters that need to be inserted at certain positions.

The current approach feels a little slow, because I'm iterating over the text much. It also fails (and crashes) if not enough parameters are provided for a parameterizable placeholder.

import re

parameters_regex = "(?:\[(.*)\])?"

def expand_message(message):
  """Recursivly replaces any occurencys of building blocks with its text."""
  for key, value in phrases.items():
    match = re.search(key + parameters_regex, message)
    if match:
      replacement = ""
      if (match.group(1)):
        parameters = match.group(1).split(",")
        replacement = value.format(*parameters)     
      else:
        replacement = value
      replacement = expand_message(replacement)
      message = message.replace(match.group(0), replacement)
  return message


def format_numerators(message):
  """Replaces the numerator placeholders with the actual numerator."""    
  numerator = 1
  while ('#.)' in message):
    message = message.replace('#.)', str(numerator) + ".)", 1)
    numerator+=1
  return message


def format_message(message):
  """Formats and returns the message."""      
  message = expand_message(message)
  message = format_numerators(message)
  return message


This is how I'm testing it at the moment:

phrases = { }
phrases["bb:WORKDAY"] = "bb:GET_UP\nbb:BRUSH_TEETH\nbb:GO_TO_WORK[train]\nbb:GO_HOME"
phrases["bb:FRIDAY"] = "bb:WORKDAY\nbb:GO_OUT[cinema,John]\nbb:GO_HOME"
phrases["bb:WEEKEND"] = "bb:GET_UP\nbb:GO_OUT[park,Mary]\nbb:GO_HOME"

phrases["bb:GET_UP"] = "#.) Get out of bed."
phrases["bb:BRUSH_TEETH"] = "#.) Brush your teeth."
phrases["bb:GO_TO_WORK"] = "#.) Take the {} and go to work."
phrases["bb:GO_HOME"] = "#.) Go home."
phrases["bb:GET_UP"] = "#.) Get out of bed."
phrases["bb:GO_OUT"] = "#.) Go to the {} with {}."
phrases["bb:GET_UP"] = "#.) Get out of bed." 

print(format_message("bb:WORKDAY"))
print("\n")
print(format_message("bb:FRIDAY"))  
print("\n")
print(format_message("bb:WEEKEND"))  



  1.) Get out of bed.
  2.) Brush your teeth.
  3.) Take the train and go to work.
  4.) Go home.
  
  1.) Get out of bed.
  2.) Brush your teeth.
  3.) Take the train and go to work.
  4.) Go home.
  5.) Go to the cinema with John.
  6.) Go home.
  
  1.) Get out of bed.
  2.) Go to the park with Mary.
  3.) Go home.
What if you would use re.sub() with a replacement function until there are no unfulfilled placeholders left. Something along these lines:

PHRASE_PATTERN = re.compile(r"(bb:\w+)(?:\[(.*)\])?")

def expand_message(message):
    """Recursively replaces any occurrences of building blocks with its text."""
    while "bb:" in message:
        message = PHRASE_PATTERN.sub(lambda group: phrases[group[1]].format(*group[2].split(","))
                                                   if group[2] else phrases[group[1]],
                                     message)
    return message


This way you don't need to iterate over all the available phrases and only handling the phrases used in a message. Should scale much better.