htty_core
1""" 2htty-core: A thin wrapper around a forked [ht](https://github.com/andyk/ht) binary for use with [htty](https://matrixmanatyrservice.github.io/htty/htty.html). 3""" 4 5from .core import Cols, Command, HtArgs, HtEvent, Rows, StyleMode, find_ht_binary, run 6 7__all__ = ["HtArgs", "HtEvent", "find_ht_binary", "run", "Command", "Rows", "Cols", "StyleMode", "__version__"] 8# [[[cog 9# import os 10# cog.out(f'__version__ = "{os.environ["HTTY_VERSION"]}"') 11# ]]] 12__version__ = "0.2.30" 13# [[[end]]]
147class HtArgs: 148 """ 149 The caller provides one of these when they want an `ht` process. 150 """ 151 152 def __init__( 153 self, 154 command: Command, 155 subscribes: Optional[list[HtEvent]] = None, 156 rows: Rows = None, 157 cols: Cols = None, 158 style_mode: Optional[StyleMode] = None, 159 ) -> None: 160 self.command = command 161 self.subscribes = subscribes or [] 162 self.rows = rows 163 self.cols = cols 164 self.style_mode = style_mode 165 166 def get_command(self, ht_binary: Optional[str] = None) -> list[str]: 167 """Build the command line arguments for running ht. 168 169 Args: 170 ht_binary: Optional path to ht binary. If not provided, find_ht_binary() will be called. 171 172 Returns: 173 List of command arguments that would be passed to subprocess.Popen 174 """ 175 if ht_binary is None: 176 ht_binary = find_ht_binary() 177 178 cmd_args = [ht_binary] 179 180 # Add subscription arguments 181 if self.subscribes: 182 subscribe_strings = [event.value for event in self.subscribes] 183 cmd_args.extend(["--subscribe", ",".join(subscribe_strings)]) 184 185 # Add size arguments if specified 186 if self.rows is not None and self.cols is not None: 187 cmd_args.extend(["--size", f"{self.cols}x{self.rows}"]) 188 189 # Add style mode if specified 190 if self.style_mode is not None: 191 cmd_args.extend(["--style-mode", self.style_mode]) 192 193 # Add separator and the command to run 194 cmd_args.append("--") 195 if isinstance(self.command, str): 196 cmd_args.extend(self.command.split()) 197 else: 198 cmd_args.extend(self.command) 199 200 return cmd_args
The caller provides one of these when they want an ht process.
152 def __init__( 153 self, 154 command: Command, 155 subscribes: Optional[list[HtEvent]] = None, 156 rows: Rows = None, 157 cols: Cols = None, 158 style_mode: Optional[StyleMode] = None, 159 ) -> None: 160 self.command = command 161 self.subscribes = subscribes or [] 162 self.rows = rows 163 self.cols = cols 164 self.style_mode = style_mode
166 def get_command(self, ht_binary: Optional[str] = None) -> list[str]: 167 """Build the command line arguments for running ht. 168 169 Args: 170 ht_binary: Optional path to ht binary. If not provided, find_ht_binary() will be called. 171 172 Returns: 173 List of command arguments that would be passed to subprocess.Popen 174 """ 175 if ht_binary is None: 176 ht_binary = find_ht_binary() 177 178 cmd_args = [ht_binary] 179 180 # Add subscription arguments 181 if self.subscribes: 182 subscribe_strings = [event.value for event in self.subscribes] 183 cmd_args.extend(["--subscribe", ",".join(subscribe_strings)]) 184 185 # Add size arguments if specified 186 if self.rows is not None and self.cols is not None: 187 cmd_args.extend(["--size", f"{self.cols}x{self.rows}"]) 188 189 # Add style mode if specified 190 if self.style_mode is not None: 191 cmd_args.extend(["--style-mode", self.style_mode]) 192 193 # Add separator and the command to run 194 cmd_args.append("--") 195 if isinstance(self.command, str): 196 cmd_args.extend(self.command.split()) 197 else: 198 cmd_args.extend(self.command) 199 200 return cmd_args
Build the command line arguments for running ht.
Args: ht_binary: Optional path to ht binary. If not provided, find_ht_binary() will be called.
Returns: List of command arguments that would be passed to subprocess.Popen
53class HtEvent(StrEnum): 54 """ 55 Event types that can be subscribed to from the ht process. 56 57 The original set of events is documented [in the ht repo](https://github.com/andyk/ht?tab=readme-ov-file#events). 58 59 Events added by `htty`: 60 61 - pid 62 - exitCode 63 - debug 64 - completed 65 """ 66 67 INIT = "init" 68 """ 69 Same as snapshot event (see below) but sent only once, as the first event after ht's start (when sent to 70 STDOUT) and upon establishing of WebSocket connection. 71 """ 72 73 SNAPSHOT = "snapshot" 74 """ 75 Terminal window snapshot. Sent when the terminal snapshot is taken with the takeSnapshot command. 76 77 Event data is an object with the following fields: 78 79 - cols - current terminal width, number of columns 80 - rows - current terminal height, number of rows 81 - text - plain text snapshot as multi-line string, where each line represents a terminal row 82 - seq - a raw sequence of characters, which when printed to a blank terminal puts it in the same state as 83 ht's virtual terminal 84 """ 85 86 OUTPUT = "output" 87 """ 88 Terminal output. Sent when an application (e.g. shell) running under ht prints something to the terminal. 89 90 Event data is an object with the following fields: 91 92 - seq - a raw sequence of characters written to a terminal, potentially including control sequences 93 (colors, cursor positioning, etc.) 94 """ 95 96 RESIZE = "resize" 97 """ 98 Terminal resize. Send when the terminal is resized with the resize command. 99 100 Event data is an object with the following fields: 101 102 - cols - current terminal width, number of columns 103 - rows - current terminal height, number of rows 104 """ 105 106 PID = "pid" 107 """ 108 ht runs the indicated command in `sh`. 109 This event provides the pid of that `sh` process 110 """ 111 112 EXIT_CODE = "exitCode" 113 """ 114 htty modified ht to stay open even after the command has completed. 115 This event indicates the exit code of the underlying command. 116 """ 117 118 COMMAND_COMPLETED = "commandCompleted" 119 """ 120 htty modified ht to run your command like so: 121 122 Previously, ht did the simple thing and ran your command like this: 123 ``` 124 sh -c '{command}'' 125 ``` 126 127 Sometimes, the PTY would shut down before all output was processed by ht, causing snapshots taken 128 after exit to be incomplete. 129 To fix this htty modified ht to run your like so: 130 131 ``` 132 sh -c '{command} ; exit_code=$? ; /path/to/ht wait-exit /path/to/a/temp/fifo ; exit $exit_code' 133 ``` 134 135 (The fifo is used to notify `ht wait-exit` that it's safe to exit) 136 137 Following this change, the command might complete at one time, and the exit code would be made available later. 138 This event indicates when the command completed, exitCode appears when the shell exits. 139 """ 140 141 DEBUG = "debug" 142 """ 143 These events contain messages that might be helpful for debugging `ht`. 144 """
Event types that can be subscribed to from the ht process.
The original set of events is documented in the ht repo.
Events added by htty:
- pid
- exitCode
- debug
- completed
Same as snapshot event (see below) but sent only once, as the first event after ht's start (when sent to STDOUT) and upon establishing of WebSocket connection.
Terminal window snapshot. Sent when the terminal snapshot is taken with the takeSnapshot command.
Event data is an object with the following fields:
- cols - current terminal width, number of columns
- rows - current terminal height, number of rows
- text - plain text snapshot as multi-line string, where each line represents a terminal row
- seq - a raw sequence of characters, which when printed to a blank terminal puts it in the same state as ht's virtual terminal
Terminal output. Sent when an application (e.g. shell) running under ht prints something to the terminal.
Event data is an object with the following fields:
- seq - a raw sequence of characters written to a terminal, potentially including control sequences (colors, cursor positioning, etc.)
Terminal resize. Send when the terminal is resized with the resize command.
Event data is an object with the following fields:
- cols - current terminal width, number of columns
- rows - current terminal height, number of rows
ht runs the indicated command in sh.
This event provides the pid of that sh process
htty modified ht to stay open even after the command has completed. This event indicates the exit code of the underlying command.
htty modified ht to run your command like so:
Previously, ht did the simple thing and ran your command like this:
sh -c '{command}''
Sometimes, the PTY would shut down before all output was processed by ht, causing snapshots taken after exit to be incomplete. To fix this htty modified ht to run your like so:
sh -c '{command} ; exit_code=$? ; /path/to/ht wait-exit /path/to/a/temp/fifo ; exit $exit_code'
(The fifo is used to notify ht wait-exit that it's safe to exit)
Following this change, the command might complete at one time, and the exit code would be made available later. This event indicates when the command completed, exitCode appears when the shell exits.
These events contain messages that might be helpful for debugging ht.
203def find_ht_binary() -> str: 204 """Find the bundled ht binary.""" 205 # Check HTTY_HT_BIN environment variable first 206 env_path = os.environ.get("HTTY_HT_BIN") 207 if env_path and os.path.isfile(env_path): 208 return env_path 209 210 ht_exe = "ht" + (sysconfig.get_config_var("EXE") or "") 211 212 # First, try to find the binary relative to this package installation 213 pkg_file = __file__ # This file: .../site-packages/htty_core/core.py 214 pkg_dir = os.path.dirname(pkg_file) # .../site-packages/htty_core/ 215 site_packages = os.path.dirname(pkg_dir) # .../site-packages/ 216 python_env = os.path.dirname(site_packages) # .../lib/python3.x/ 217 env_root = os.path.dirname(python_env) # .../lib/ 218 actual_env_root = os.path.dirname(env_root) # The actual environment root 219 220 # Look for binary in the environment's bin directory 221 env_bin_path = os.path.join(actual_env_root, "bin", ht_exe) 222 if os.path.isfile(env_bin_path): 223 return env_bin_path 224 225 # Only look for the bundled binary - no system fallbacks 226 raise FileNotFoundError( 227 f"Bundled ht binary not found at expected location: {env_bin_path}. " 228 f"This indicates a packaging issue with htty-core." 229 )
Find the bundled ht binary.
232def run(args: HtArgs) -> subprocess.Popen[str]: 233 """ 234 Given some `HtArgs` object, run its command via ht. 235 236 `ht` connect 237 238 239 Returns a subprocess.Popen object representing the running ht process. 240 The caller is responsible for managing the process lifecycle. 241 """ 242 cmd_args = args.get_command() 243 244 # Start the process 245 return subprocess.Popen( 246 cmd_args, 247 stdin=subprocess.PIPE, 248 stdout=subprocess.PIPE, 249 stderr=subprocess.PIPE, 250 text=True, 251 bufsize=1, 252 )
Given some HtArgs object, run its command via ht.
ht connect
Returns a subprocess.Popen object representing the running ht process. The caller is responsible for managing the process lifecycle.
23class StyleMode(StrEnum): 24 """Style mode for terminal output.""" 25 26 PLAIN = "plain" 27 STYLED = "styled"
Style mode for terminal output.