Source code for pudding.writer.writers.writer

"""Module defining base writer class."""

from pathlib import Path
from typing import Any

from ..node import Node


[docs] class Writer: """Base writer class. :var attrib_re: Regex for node attributes. :var node_re: Regex for a node path. """ def __init__( self, file_path: Path, *, encoding: str = "utf-8", root_name: str = "root" ) -> None: """Initialize writer. :param file_path: Path of the output file. :param encoding: Encoding of the output file. :param root_name: Name of the root element, if it exists. """ self.encoding = encoding self.file_path = file_path self.root_name = root_name
[docs] def add_attribute(self, path: str, name: str, value: str) -> None: """Add an attribute to an element. :param path: Path of the element. :param name: Name of the attribute. :param value: Value of the attribute. """ raise NotImplementedError
[docs] def create_element(self, path: str, value: str | None = None) -> Any: """Add an element to the current node. :param path: Path of the element. :param value: Value of the element or None if it has no value. :returns: The created element. """ raise NotImplementedError
[docs] def add_element(self, path: str, value: str | None = None) -> Any: """Add an element if it not already exists. Otherwise it appends the string to the already existing element. :param path: Path to the element. :param value: Value of the element or None if it has no value. :returns: The created element. """ raise NotImplementedError
[docs] def enter_path(self, path: str, value: str | None = None) -> None: """Enter a node and create elements in the path if they do not already exist. :param path: Path to the element. :param value: Value of the element or None if it has no value. """ raise NotImplementedError
[docs] def open_path(self, path: str, value: str | None = None) -> None: """Enter a node and create elements in the path if they do not already exist. Always creates the last node. :param path: Path to the element. :param value: Value of the element or None if it has no value. """ raise NotImplementedError
[docs] def leave_paths(self, amount: int = 1) -> None: """Leave the previously entered path. :param amount: Number of paths to leave. """ raise NotImplementedError
[docs] def delete_element(self, path: str) -> None: """Delete an element. :param path: Path of the element. """ raise NotImplementedError
[docs] def replace_element(self, path: str, value: str | None = None) -> None: """Replace an element. :param path: Path of the element. :param value: Value of the replaced element or None if it has no value. """ raise NotImplementedError
[docs] def generate_output(self) -> str: """Generate output in specified format.""" raise NotImplementedError
[docs] def write_output(self) -> None: """Write generated output to file. :param file_path: Path of the file to write to. """ with open(self.file_path, "w", encoding=self.encoding) as f: f.write(self.generate_output())
class BufferedWriter(Writer): """Base writer class for buffered output. :var attrib_re: Regex for node attributes. :var node_re: Regex for a node path. """ def __init__( self, file_path: Path, *, encoding: str = "utf-8", root_name: str = "root" ) -> None: """Initialize buffered writer. :param file_path: Path of the output file. :param encoding: Encoding of the output file. :param root_name: Name of the root element, if it exists. """ super().__init__(file_path, encoding=encoding, root_name=root_name) self.prev_roots: list[Node] = [] self.root = Node(root_name) def _get_element(self, path: str) -> Node: """Get first Node at given path. :param path: Path from root element. :returns: Node at the given path. :raises ValueError: If no element is found. """ elem = self.root.find(path) if elem is None: raise ValueError(f"Node at path {repr(path)} does not exist") return elem def _get_or_create_element(self, path: str, root: Node) -> Node: """Get first Node at given path or create it if it does not exist. :param xpath: Path from root element. :param root: Node to start from. :returns: Node at the given path. """ if path == ".": return root elem = root.find(path) if elem is not None: return elem target = root for node_path, _, _, _ in Node.split_path(path): elem = target.find(node_path) if elem is not None: target = elem else: target = target.add_child(node_path) return target def add_attribute(self, path: str, name: str, value: str) -> None: """Add an attribute to an element. :param path: Path of the element. :param name: Name of the attribute. :param value: Value of the attribute. """ self._get_element(path).set(name, value) def create_element(self, path: str, value: str | None = None) -> Node: """Add an element and always create the last element in the path. :param path: Path of the element. :param value: Value of the element or None if it has no value. :returns: The created SubElement. """ elem = self.root.find(path) if elem is None: new = self._get_or_create_element(path, self.root) new.text = value return new paths = Node.split_path(path) match len(paths): case 0: raise ValueError(f"Invalid path {repr(path)}.") case 1: parent = self.root child_node = paths[0][0] case _: *parent_paths, child_path = paths parent_path = "".join((path[0] for path in parent_paths)) parent = self._get_or_create_element(parent_path, self.root) child_node = child_path[0] return parent.add_child(child_node, value) def add_element(self, path: str, value: str | None = None) -> Node: """Add an element if it not already exists. Otherwise it appends the string to the already existing element. :param path: Path to the element. :param value: Value of the element or None if it has no value. :returns: The added/modified SubElement. """ elem = self._get_or_create_element(path, self.root) text = elem.text or "" if value is not None: elem.text = f"{text}{value}" return elem def enter_path(self, path: str, value: str | None = None) -> None: """Enter a node and create elements in the path if they do not already exist. :param path: Path to the element. :param value: Value of the element or None if it has no value. """ elem = self._get_or_create_element(path, self.root) elem.text = value self.prev_roots.append(self.root) self.root = elem def open_path(self, path: str, value: str | None = None) -> None: """Enter a node and create elements in the path if they do not already exist. Always creates the last node. :param path: Path to the element. :param value: Value of the element or None if it has no value. """ elem = self.create_element(path, value) self.prev_roots.append(self.root) self.root = elem def leave_paths(self, amount: int = 1) -> None: """Set the current root object to the previous one. :param amount: Amount of paths to leave. """ if amount > 0: self.root = self.prev_roots[-amount] del self.prev_roots[-amount:] def delete_element(self, path: str) -> None: """Delete an element. :param path: Path of the element. """ elem = self._get_element(path) parent = elem.parent if parent: parent.children.get(elem.node_path, []).remove(elem) del elem def replace_element(self, path: str, value: str | None = None) -> None: """Replace an element. :param path: Path of the element. :param value: Value of the replaced element or None if it has no value. """ elem = self._get_element(path) elem.text = value