[docs]defvalidate_selector(selector:list[str]):""" Validates that each pattern in the selector list is a substring of the previous pattern. This function ensures that patterns in a selector form a narrowing hierarchy where each subsequent pattern is more specific (a substring) of the previous pattern. :param selector: A list of string patterns to validate. Cannot be empty. :raises: ValueError, If the selector is empty or if any pattern is not a substring of the previous pattern in the sequence. """iflen(selector)==0:# pragma: no coverraiseValueError("Selector cannot be empty")previous=Noneforpatterninselector:ifpreviousisNone:previous=patterncontinueelifpatterninprevious:passelse:raiseValueError(f"{pattern} is NOT a sub string of {previous}")
[docs]defto_placeholder(name:str,selector:list[str],)->str:""" Convert a selector to a placeholder token that can be used in a template. Example: >>> to_placeholder( ... name="author", ... selector=['author = "Alice"', '= "Alice"', "Alice"] ... ) 'author = "{{ cookiecutter.author }}"' :param name: The name of the parameter. :param selector: A list of string patterns to validate. Cannot be empty. """previous=Nonetoken="{{ cookiecutter."+name+" }}"forpatterninselector[::-1]:ifpreviousisNone:previous=(pattern,token)else:token=pattern.replace(previous[0],previous[1])# print(f"{token = }, {pattern = }, {previous = }") # for debug onlyprevious=(pattern,token)returntoken
[docs]defreplace_with_placeholders(text:str,replacements:list[tuple[str,str]],):""" Replace multiple strings in text using temporary placeholders to avoid substring conflicts. Example: >>> replace_with_placeholders( ... text='author = "alice", author_email = "alice@email.com"', ... replacements=[ ... ("alice@email.com", "{{ cookiecutter.author_email }}"), ... ("alice", "{{ cookiecutter.author }}"), ... ] ... ) 'author = "{{ cookiecutter.author }}", author_email = "{{ cookiecutter.author_email }}"' :param text: The input text to perform replacements on :param replacements: List of tuples (search_string, replace_string) :returns: Text with all replacements applied """# Create a mapping of search strings to unique placeholders and final replacementsplaceholder_map={}# Generate unique placeholders for each search stringforsearch_string,replace_stringinreplacements:# Create a unique placeholder using UUID (guaranteed to not be a substring of other content)placeholder=f"__PLACEHOLDER_{uuid.uuid4()}__"placeholder_map[search_string]=(placeholder,replace_string)# First pass: replace all search strings with their placeholdersresult=textforsearch_string,(placeholder,_)inplaceholder_map.items():result=result.replace(search_string,placeholder)# Second pass: replace all placeholders with their final valuesfor_,(placeholder,replace_string)inplaceholder_map.items():result=result.replace(placeholder,replace_string)returnresult
double_curly_brackets_mapper={"{{":"{% raw %}{{{% endraw %}","}}":"{% raw %}}}{% endraw %}",}
[docs]defreplace_double_curly_brackets(text:str)->str:""" Replace double curly brackets with raw Jinja2 template syntax. When converting a seed project to a cookiecutter template, Jinja2 syntax in the original files needs special handling. This function replaces the standard Jinja2 delimiters ('{{' and '}}') with raw expressions that will be preserved as literal curly brackets in the generated template, rather than being interpreted as Jinja2/cookiecutter variables. This ensures that original Jinja2 syntax in the seed project is preserved as literal text in the generated cookiecutter template, preventing conflicts with cookiecutter's own variable substitution which also uses curly brackets. """forbefore,afterindouble_curly_brackets_mapper.items():text=text.replace(before,after)returntext