from datetime import datetime import subprocess import xml.etree.ElementTree as ET from typing import Optional from .entry import Entry from .util import escape_text_for_xml class SingleShotEntry(Entry): """ Entry type for one-time script executions. Attributes: script: The script/command to execute stdout: Captured standard output from script execution stderr: Captured standard error from script execution exit_code: Process exit code after completion _executed: Whether the script has been executed """ def __init__(self, script: str, id: str, timestamp: datetime): """ Initialize a new single shot entry. Args: script: The script/command to execute id: Unique identifier for this entry timestamp: Creation timestamp for this entry """ super().__init__(id, timestamp) self.script = script self.stdout = "" self.stderr = "" self.exit_code: Optional[int] = None self._executed = False def update(self) -> None: """ Execute the script if not already executed. Captures stdout, stderr and exit code. """ if self._executed: return try: # Run script and capture output process = subprocess.run( self.script, shell=True, capture_output=True, text=True ) # Store results self.stdout = process.stdout self.stderr = process.stderr self.exit_code = process.returncode except subprocess.SubprocessError as e: # Handle subprocess errors by capturing the error message self.stdout = "" self.stderr = str(e) self.exit_code = -1 except Exception as e: # Handle any other errors self.stdout = "" self.stderr = f"Error executing script: {str(e)}" self.exit_code = -1 self._executed = True def generate_context(self) -> ET.Element: """ Generate an XML Element representing this single shot entry. Returns: ET.Element: XML element containing the entry's data """ # Create root element element = ET.Element("single_shot", { "id": self.id, }) # Add script as CDATA or escaped text element.text = escape_text_for_xml(self.script) # Only add output elements if script has executed if self._executed: # Add stdout element if there is output stdout_elem = ET.SubElement(element, "stdout") stdout_elem.text = escape_text_for_xml(self.stdout) # Add stderr element if there are errors stderr_elem = ET.SubElement(element, "stderr") stderr_elem.text = escape_text_for_xml(self.stderr) # Add exit code attribute after execution element.set("exit_code", str(self.exit_code)) return element