phml.helpers
1import sys 2from pathlib import Path 3from traceback import print_tb 4from typing import Any, Iterator 5 6from phml.nodes import AST, Element, Node, Parent 7 8 9def build_recursive_context(node: Node, context: dict[str, Any]) -> dict[str, Any]: 10 """Build recursive context for the current node.""" 11 parent = node.parent 12 parents = [] 13 result = {**context} 14 15 while parent is not None and not isinstance(parent, AST): 16 parents.append(parent) 17 parent = parent.parent 18 19 for parent in parents: 20 result.update(parent.context) 21 22 if isinstance(node, Element): 23 result.update(node.context) 24 return result 25 26 27def iterate_nodes(node: Parent) -> Iterator[Node]: 28 """Recursively iterate over nodes and their children.""" 29 yield node 30 for child in node: 31 if isinstance(child, Parent): 32 yield from iterate_nodes(child) 33 34 35def calc_offset(content: str) -> int: 36 """Get the leading offset of the first line of the string.""" 37 return len(content) - len(content.lstrip()) 38 39 40def strip_blank_lines(data: str) -> str: 41 """Strip the blank lines at the start and end of a list.""" 42 data = data.rstrip().replace("\r\n", "\n") 43 data_lines = data.split("\n") 44 45 # remove leading blank lines 46 for idx in range(0, len(data_lines)): # pylint: disable=consider-using-enumerate 47 if data_lines[idx].strip() != "": 48 return "\n".join(data_lines[idx:]) 49 50 return "" 51 52 53def normalize_indent(content: str, indent: int = 0) -> str: 54 """Normalize the indent between all lines. 55 56 Args: 57 content (str): The content to normalize the indent for 58 indent (bool): The amount of offset to add to each line after normalization. 59 60 Returns: 61 str: The normalized string 62 """ 63 64 lines = strip_blank_lines(content).split("\n") 65 offset = calc_offset(lines[0]) 66 67 result = [] 68 for line in lines: 69 if len(line) > 0: 70 result.append( 71 " " * indent + line[min(calc_offset(line), offset) :], 72 ) 73 else: 74 result.append(line) 75 return "\n".join(result) 76 77 78class PHMLTryCatch: 79 """Context manager around core PHML actions. When an exception is raised 80 it is caught here and the current file that is being handled is prepended 81 to the exception message. 82 """ 83 84 def __init__(self, path: str | Path | None = None, fallback: str = "") -> None: 85 if path is None or str(path) == "": 86 path = fallback 87 self._path = str(path or fallback) 88 89 def __enter__(self): 90 pass 91 92 # (self, exc_type, exc_val, exc_tb) 93 def __exit__(self, _, exc_val, exc_tb): 94 if exc_val is not None and not isinstance(exc_val, SystemExit): 95 print_tb(exc_tb) 96 if self._path != "": 97 sys.stderr.write(f"[{self._path}]: {exc_val}") 98 else: 99 sys.stderr.write(str(exc_val)) 100 sys.stderr.flush() 101 exit()
def
build_recursive_context( node: phml.nodes.Node, context: dict[str, typing.Any]) -> dict[str, typing.Any]:
10def build_recursive_context(node: Node, context: dict[str, Any]) -> dict[str, Any]: 11 """Build recursive context for the current node.""" 12 parent = node.parent 13 parents = [] 14 result = {**context} 15 16 while parent is not None and not isinstance(parent, AST): 17 parents.append(parent) 18 parent = parent.parent 19 20 for parent in parents: 21 result.update(parent.context) 22 23 if isinstance(node, Element): 24 result.update(node.context) 25 return result
Build recursive context for the current node.
28def iterate_nodes(node: Parent) -> Iterator[Node]: 29 """Recursively iterate over nodes and their children.""" 30 yield node 31 for child in node: 32 if isinstance(child, Parent): 33 yield from iterate_nodes(child)
Recursively iterate over nodes and their children.
def
calc_offset(content: str) -> int:
36def calc_offset(content: str) -> int: 37 """Get the leading offset of the first line of the string.""" 38 return len(content) - len(content.lstrip())
Get the leading offset of the first line of the string.
def
strip_blank_lines(data: str) -> str:
41def strip_blank_lines(data: str) -> str: 42 """Strip the blank lines at the start and end of a list.""" 43 data = data.rstrip().replace("\r\n", "\n") 44 data_lines = data.split("\n") 45 46 # remove leading blank lines 47 for idx in range(0, len(data_lines)): # pylint: disable=consider-using-enumerate 48 if data_lines[idx].strip() != "": 49 return "\n".join(data_lines[idx:]) 50 51 return ""
Strip the blank lines at the start and end of a list.
def
normalize_indent(content: str, indent: int = 0) -> str:
54def normalize_indent(content: str, indent: int = 0) -> str: 55 """Normalize the indent between all lines. 56 57 Args: 58 content (str): The content to normalize the indent for 59 indent (bool): The amount of offset to add to each line after normalization. 60 61 Returns: 62 str: The normalized string 63 """ 64 65 lines = strip_blank_lines(content).split("\n") 66 offset = calc_offset(lines[0]) 67 68 result = [] 69 for line in lines: 70 if len(line) > 0: 71 result.append( 72 " " * indent + line[min(calc_offset(line), offset) :], 73 ) 74 else: 75 result.append(line) 76 return "\n".join(result)
Normalize the indent between all lines.
Args
- content (str): The content to normalize the indent for
- indent (bool): The amount of offset to add to each line after normalization.
Returns
str: The normalized string
class
PHMLTryCatch:
79class PHMLTryCatch: 80 """Context manager around core PHML actions. When an exception is raised 81 it is caught here and the current file that is being handled is prepended 82 to the exception message. 83 """ 84 85 def __init__(self, path: str | Path | None = None, fallback: str = "") -> None: 86 if path is None or str(path) == "": 87 path = fallback 88 self._path = str(path or fallback) 89 90 def __enter__(self): 91 pass 92 93 # (self, exc_type, exc_val, exc_tb) 94 def __exit__(self, _, exc_val, exc_tb): 95 if exc_val is not None and not isinstance(exc_val, SystemExit): 96 print_tb(exc_tb) 97 if self._path != "": 98 sys.stderr.write(f"[{self._path}]: {exc_val}") 99 else: 100 sys.stderr.write(str(exc_val)) 101 sys.stderr.flush() 102 exit()
Context manager around core PHML actions. When an exception is raised it is caught here and the current file that is being handled is prepended to the exception message.