1"""This module defines the remove_indentation macro.""" 2 3def remove_indentation(string): 4 """Removes indentation from a multiline string. 5 6 This utility function allows us to write multiline templates in a context that requires 7 indentation, for example inside a macro. It discards the first and last lines if they only 8 contain spaces or tabs. Then, it computes an indentation prefix based on the first remaining 9 line and removes that prefix from all lines. 10 11 Example: 12 13 ``` 14 def greeter_script(): 15 return remove_indentation(''' 16 #!/bin/bash 17 echo "Hello, {name}!" 18 ''').format(name = "world") 19 ``` 20 21 This is equivalent to: 22 23 ``` 24 TEMPLATE = '''#!/bin/bash 25 echo "Hello, {name}!" 26 ''' 27 28 def greeter_script(): 29 return TEMPLATE.format(name = "world") 30 ``` 31 32 This macro is similar to 33 https://github.com/bazelbuild/rules_rust/blob/937e63399b111a6d7ee53b187e4d113300b089e9/rust/private/utils.bzl#L386. 34 35 Args: 36 string: A multiline string. 37 Returns: 38 The input string minus any indentation. 39 """ 40 41 def get_indentation(line): 42 indentation = "" 43 for char in line.elems(): 44 if char in [" ", "\t"]: 45 indentation += char 46 else: 47 break 48 49 # For some reason Buildifier thinks the below variable is uninitialized. 50 # buildifier: disable=uninitialized 51 return indentation 52 53 lines = string.split("\n") 54 55 # Skip first line if empty. 56 if get_indentation(lines[0]) == lines[0]: 57 lines = lines[1:] 58 59 # Compute indentation based on the first remaining line, and remove indentation from all lines. 60 indentation = get_indentation(lines[0]) 61 lines = [line.removeprefix(indentation) for line in lines] 62 63 # Skip last line if empty. 64 if get_indentation(lines[len(lines) - 1]) == lines[len(lines) - 1]: 65 lines = lines[:-1] 66 67 result = "\n".join(lines) 68 if result[:-1] != "\n": 69 # Ensure we always end with a newline. 70 result += "\n" 71 72 return result 73