Metadata-Version: 2.4
Name: shtoobz
Version: 0.1.1
Summary: Lightweight interchangeable process and TCP byte streams for Windows and Linux.
Author: Jeremy Dunn
Author-email: Jeremy Dunn <jeremy.dunn315@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.11
Project-URL: Documentation, https://gitlab.com/stolenfootball-tools/shtoobz/-/blob/main/README.md
Project-URL: Issues, https://gitlab.com/stolenfootball-tools/shtoobz/-/issues
Project-URL: Repository, https://gitlab.com/stolenfootball-tools/shtoobz
Description-Content-Type: text/markdown

# shtoobz

Talk to a child process or a TCP socket through the same Python API. A **shtoob**
handles bytes, buffering, prompts, and timeouts so your client code can stay the
same when you switch transports.

Python **3.11+** · **Windows and Linux** · **No runtime dependencies**

## Install

In an existing uv project:

```sh
uv add shtoobz
```

Or install into your Python environment with `python -m pip install shtoobz`.
This is a Python library; there is no `shtoobz` command to launch.

## Try a process

Save this as `hello.py`. It launches a Python child, sends a line, and reads the
child's response. It runs on both Windows and Linux without an external server.

```python
import sys
from shtoobz import ProcessShtoob

with ProcessShtoob(
    [
        sys.executable, "-u", "-c",
        "print(input().upper(), flush=True)",
    ],
    timeout=5,
) as shtoob:
    shtoob.sendline(b"hello")
    reply = shtoob.recvline()
    print(reply.decode().rstrip("\r\n"))
```

Run `uv run python hello.py` from your project, or `python hello.py` if you used
pip. You should see:

```text
HELLO
```

The `with` block closes the shtoob and cleans up the child. Python's `-u` option
keeps the child's output unbuffered so responses arrive promptly.

## Switch transports

For an application that exposes the same byte protocol over stdin/stdout and
TCP, switch the connection by changing `LOCAL`. The example below expects your
application to print `Name: `, accept a name, and reply with a newline-terminated
message. Replace the executable path or host and port with your own target.

```python
from shtoobz import ProcessShtoob, SocketShtoob

LOCAL = True

shtoob = (
    ProcessShtoob(
        ["./example", "--stdio"], timeout=5,
    )
    if LOCAL
    else SocketShtoob("127.0.0.1", 9000, timeout=5)
)

with shtoob:
    shtoob.sendlineafter(
        b"Name: ", b"Alice", max_bytes=4096,
    )
    print(shtoob.recvline(max_bytes=4096))
```

On Windows, use your executable's path, for example `r".\example.exe"`.
Both endpoints must speak the same protocol; shtoobz does not translate between
a CLI menu and a different network protocol.

To keep connection setup out of your client, accept the shared `Shtoob` type:

```python
from shtoobz import Shtoob

class Client:
    def __init__(self, shtoob: Shtoob):
        self.shtoob = shtoob

    def request(self, payload: bytes) -> bytes:
        self.shtoob.sendline(payload)
        return self.shtoob.recvline(max_bytes=4096)
```

## Choose a read

| You need | Use |
| --- | --- |
| Up to a number of bytes | `recv(n)` |
| Exactly a number of bytes | `recvn(n)` |
| A prompt or delimiter | `recvuntil(b"> ")` |
| One line, including its newline | `recvline()` |
| Everything until the peer closes its output | `recvall()` |
| Output until an idle gap | `recv_available()` |
| A stream of lines or chunks | `iterlines()` or `iterchunks()` |

Payloads and delimiters are `bytes`. Encode text before sending and decode it
after reading. Reads retain bytes beyond a delimiter for the next call.

Set a finite `timeout` when you want a stalled read to fail. The default waits
indefinitely. A timeout preserves partial input so you can continue the same
read. Writes are blocking and have no timeout.

`recv(n)` may return fewer than `n` bytes; `recvn(n)` waits for the exact count.
`recvline()` raises `ShtoobEOF` if the peer closes before a newline arrives.
Use `iterlines()` when an unterminated final line should be returned.

The [API reference](https://gitlab.com/stolenfootball-tools/shtoobz/-/blob/main/docs/reference.md)
covers errors and recovery, response limits, prompt helpers, buffering, streaming,
traffic logging, and transport options.

## Supported use

shtoobz supports redirected process pipes and IPv4/IPv6 TCP connections. Use it
for programs that accept bytes on stdin and write responses to stdout, or expose
the same protocol through a socket. A child must flush its output promptly.

PTY/ConPTY terminal emulation, interactive terminal forwarding, async APIs, TLS,
server listeners, and process-tree management are outside the current scope.
Closing a process shtoob terminates and reaps its direct child, not descendants.

## More documentation

- [API reference](https://gitlab.com/stolenfootball-tools/shtoobz/-/blob/main/docs/reference.md): method behavior, timeout and EOF rules, examples, and lifecycle.
- [Development and releases](https://gitlab.com/stolenfootball-tools/shtoobz/-/blob/main/docs/development.md): work on the package, run checks, and publish with GitLab.
- [Issues](https://gitlab.com/stolenfootball-tools/shtoobz/-/issues): report a problem or request a feature.

Licensed under the [MIT License](https://gitlab.com/stolenfootball-tools/shtoobz/-/blob/main/LICENSE).
Copyright 2026 Jeremy Dunn.
