Coverage for phml\helpers.py: 100%
41 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-05 15:06 -0500
« prev ^ index » next coverage.py v6.5.0, created at 2023-04-05 15:06 -0500
1import sys
2from pathlib import Path
3from traceback import print_tb
4from typing import Any, Iterator
6from phml.nodes import AST, Element, Node, Parent
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}
15 while parent is not None and not isinstance(parent, AST):
16 parents.append(parent)
17 parent = parent.parent
19 for parent in parents:
20 result.update(parent.context)
22 if isinstance(node, Element):
23 result.update(node.context)
24 return result
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)
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())
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")
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:])
50 return ""
53def normalize_indent(content: str, indent: int = 0) -> str:
54 """Normalize the indent between all lines.
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.
60 Returns:
61 str: The normalized string
62 """
64 lines = strip_blank_lines(content).split("\n")
65 offset = calc_offset(lines[0])
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)
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 """
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)
89 def __enter__(self):
90 pass
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()