Metadata-Version: 2.1
Name: wexample-prompt
Version: 15.0.0
Summary: Renders typed terminal output and captures interactive input through a nestable IoManager with indentation and verbosity
Author-Email: weeger <contact@wexample.com>
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Project-URL: homepage, https://github.com/wexample/python-prompt
Requires-Python: >=3.10
Requires-Dist: attrs>=23.1.0
Requires-Dist: cattrs>=23.1.0
Requires-Dist: colorama
Requires-Dist: readchar
Requires-Dist: wcwidth
Requires-Dist: wexample-helpers>=19.1.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-benchmark>=5.2.3; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Description-Content-Type: text/markdown

# prompt

Version: 15.0.0

`wexample-prompt` renders structured terminal output and captures interactive input for Python CLI applications. It centres on an `IoManager` that any class can own or share, producing typed responses — log, info, success, title, table, list, tree, choice, confirm, and more — each honouring the current indentation level and verbosity. Python developers building applications that need consistent, nestable terminal I/O across a hierarchy of objects are its intended audience.

## Table of Contents

- [Quickstart](#quickstart)
- [Using the mixin inside a class](#using-the-mixin-inside-a-class)
- [Tests](#tests)
- [Architecture](#architecture)
- [Integration in the Suite](#integration-in-the-suite)
- [Dependencies](#dependencies)
- [Versioning & Compatibility Policy](#versioning--compatibility-policy)
- [License](#license)
- [About us](#about-us)
- [Known Limitations & Roadmap](#known-limitations--roadmap)
- [Status & Compatibility](#status--compatibility)
- [Useful Links](#useful-links)
- [Migration Notes](#migration-notes)

## Quickstart

Install from PyPI:

```bash
pip install wexample-prompt
```

The entry point is `IoManager`, defined in src/wexample_prompt/common/io_manager.py. Instantiate it once and call response methods directly:

```python
from wexample_prompt.common.io_manager import IoManager

io = IoManager()
io.log("Starting")
io.info("Something happened")
io.success("All done")
```

Each call prints a styled line to stdout immediately and returns the response object.

## Using the mixin inside a class

When output is spread across several classes, inherit `WithIoMethods` (src/wexample_prompt/mixin/with_io_methods.py) instead of holding an `IoManager` by hand. The mixin exposes every `IoManager` method directly on `self` and injects the correct indentation context automatically:

```python
from wexample_helpers.decorator.base_class import base_class
from wexample_prompt.mixin.with_io_methods import WithIoMethods

@base_class
class Worker(WithIoMethods):
    def run(self):
        self.log("Starting")
        self.success("All done")

Worker().run()
```

`ensure_io_manager()` is called lazily on the first method access, so no explicit setup is needed. To share one manager across a parent–child hierarchy, pass the parent when constructing the child:

```python
@base_class
class Child(WithIoMethods):
    def run(self):
        self.log("nested message")   # printed one indentation level deeper

worker = Worker()
child = Child(parent_io_handler=worker)
child.run()
```

## Tests

Run the test suite:

```bash
pytest tests/
```

With coverage:

```bash
pytest --cov=prompt tests/
```

## Architecture

`wexample-prompt` is organised as a layered pipeline: callers talk to `IoManager`, which builds a typed response object, passes it through a `PromptContext` to a render chain, then hands the result to an output handler. Every layer is independently replaceable.

---

### IoManager

src/wexample_prompt/common/io_manager.py is the single public surface for callers. It is assembled at class definition time through multiple inheritance: each response category contributes one `*ManagerMixin` that declares one method (`info`, `title`, `choice`, …). `IoManager` itself owns:

- `output` — the active `AbstractPromptOutputHandler` (stdout by default)
- `default_context_verbosity` / `default_response_verbosity` — fall-through values when callers omit verbosity
- `_recorder_stack` — a LIFO list of capture buffers; `push_recorder()` / `pop_recorder()` let callers collect every response emitted inside a block
- `_terminal_width` — lazily populated via `shutil.get_terminal_size()`, refreshed on SIGWINCH when `enable_resize_listening()` has been called

`print_response()` is the single gate every response passes through:
1. Stamps `response.created_at` (if absent).
2. Appends to the top capture buffer when the recorder stack is active.
3. Returns early without rendering when `response.verbosity == VerbosityLevel.QUIET`.
4. Calls `self.output.print(response, self.create_context(context))`.

---

### Mixin layer

src/wexample_prompt/mixin/response/messages/info_prompt_response_manager_mixin.py is a representative example. Every `*ManagerMixin` follows the same three-step body:

```python
response = XxxPromptResponse.create_xxx(message=message, verbosity=..., ...)
return self.print_response(
    response=response,
    context=XxxPromptResponse.rebuild_context_for_kwargs(context=context, parent_kwargs=kwargs),
    frame=frame,
)
```

The mixins live under src/wexample_prompt/mixin/response/, grouped by category (messages, titles, data, interactive). They carry no state and no rendering logic — all they do is construct the response and call `print_response`.

---

### Response classes

src/wexample_prompt/responses/abstract_prompt_response.py is the base for every response. It holds `lines: list[PromptResponseLine]`, an optional `verbosity`, and a `created_at` timestamp. Its `render(context)` method joins `line.render(context)` across all lines and returns the final string (or `None` when verbosity filtering suppresses it).

Concrete classes are grouped in subdirectories of src/wexample_prompt/responses/:

| Directory | Examples |
|---|---|
| `messages/` | `InfoPromptResponse`, `SuccessPromptResponse`, `ErrorPromptResponse`, … |
| `titles/` | `TitlePromptResponse`, `SubtitlePromptResponse`, `SeparatorPromptResponse` |
| `data/` | `ListPromptResponse`, `TablePromptResponse`, `TreePromptResponse`, … |
| `interactive/` | `ChoicePromptResponse`, `InputPromptResponse`, `SpinnerPromptResponse`, … |
| *(root)* | `EchoPromptResponse`, `LogPromptResponse`, `FramePromptResponse`, `CodePromptResponse` |

`AbstractMessageResponse` (src/wexample_prompt/responses/messages/abstract_message_response.py) adds a class-level `SYMBOL` prepended to the first segment and implements `apply_prefix_to_kwargs` so that `WithIoMethods` subclasses can inject a class prefix without double-prefixing the symbol.

`AbstractInteractivePromptResponse` (src/wexample_prompt/responses/interactive/abstract_interactive_prompt_response.py) wraps the render loop with terminal-height tracking to support cursor rewinding (`_partial_clear`), and stores the user's answer in `_answer`.

`FramePromptResponse` (src/wexample_prompt/responses/frame_prompt_response.py) overrides `render()` entirely: it renders its child `responses` list into an inner context, then wraps every resulting line in `╭─…─╮` / `│…│` / `╰─…─╯` borders. `IoManager.print_response()` also accepts a `frame=` shortcut that wraps any response in a `FramePromptResponse` before printing.

---

### Rendering pipeline

A call to `response.render(context)` descends through three levels:

```
AbstractPromptResponse.render(context)
  └─ for line in self.lines:
       PromptResponseLine.render(context)   ← applies indentation, wraps to context.width
         └─ for seg in self.segments:
              PromptResponseSegment.render(context, line_remaining_width)
                └─ ColorManager.colorize(text, color, styles)   ← emits ANSI codes
```

**`PromptResponseLine`** (src/wexample_prompt/common/prompt_response_line.py) prepends the indentation string produced by `context.render_indentation()`. When `context.width` is set and `context.formatting` is not `False`, it wraps segments to fit the available visible width, starting a new `indentation + content` line whenever the remaining width is exhausted.

**`PromptResponseSegment`** (src/wexample_prompt/common/prompt_response_segment.py) holds a single span of raw text with an optional `TerminalColor` and a list of `TextStyle` values. Its `render()` splits the text at the remaining width boundary and returns `(rendered_fit, remainder_segment | None)` so the line can continue wrapping.

**`ColorManager`** (src/wexample_prompt/common/color_manager.py) translates `TerminalColor`, `TerminalBgColor`, and `TextStyle` enums into ANSI prefix strings (via `colorama`), concatenates them before the text, and appends `\033[0m`.

---

### Style markup parser

src/wexample_prompt/common/style_markup_parser.py converts inline markup such as `@blue+bold{word}` or `@color:red{text}` embedded in a string into lists of `PromptResponseSegment`s, one list per logical line. It is called by `PromptResponseLine.create_from_string()` so callers can pass a plain string to any response factory without manually constructing segments. Special directive types `@path{…}` and `@time{…}` emit clickable paths and formatted timestamps rather than color segments.

---

### Output handlers

src/wexample_prompt/output/abstract_prompt_output_handler.py declares two abstract methods: `print(response, context)` and `erase(response)`.

**`PromptStdoutOutputHandler`** (src/wexample_prompt/output/prompt_stdout_output_handler.py) calls `response.render(context)`, writes the result to `sys.stdout`, and implements `erase()` with ANSI cursor-up sequences computed from `response.rendered_content`.

**`PromptBufferOutputHandler`** (src/wexample_prompt/output/prompt_buffer_output_handler.py) stores both the original response objects (`responses`) and their rendered strings (`rendered`). `flush()` clears both lists and returns the rendered strings. Used in tests and anywhere callers want to capture output without touching the terminal.

---

### PromptContext

src/wexample_prompt/common/prompt_context.py is a plain value object (not mutated during rendering). It carries:

- `width` — effective terminal width at the point of print (in visible characters)
- `indentation` / `indentation_length` / `indentation_character` / `indentation_style` — how the indentation prefix is built
- `colorized` — whether ANSI codes are emitted
- `verbosity` — upper bound; responses with a higher verbosity level are suppressed
- `bordered` — passed to nested renders (e.g. `FramePromptResponse` sets this to `False` for its inner context to prevent double-bordering)
- `parent_context` — linked list used by `WithIoManager` to chain contexts across object hierarchies

`IoManager.create_context(context)` merges caller-provided context with the manager's own indentation level and verbosity defaults. A child class that received `parent_io_handler` gets one extra indentation level automatically.

---

### Application integration

**`WithIoManager`** (src/wexample_prompt/mixin/with_io_manager.py) gives any class the ability to own or share an `IoManager`. `ensure_io_manager()` lazily creates one (or inherits it from `parent_io_handler`). `create_io_context()` builds a `PromptContext` from the class's current indentation, color, and verbosity settings, chained to the parent's context when present.

**`WithIoMethods`** (src/wexample_prompt/mixin/with_io_methods.py) extends `WithIoManager` with `__getattr__`: any unknown attribute that matches a method name on `IoManager` is returned as a wrapper function that injects `context=self.create_io_context()`. This lets subclasses call `self.info(…)`, `self.title(…)`, etc., without holding a reference to `self.io`.

**`WithIndentation`** (src/wexample_prompt/mixin/with_indentation.py) is mixed into `IoManager` to maintain a manager-level indentation counter (`indentation_up()` / `indentation_down()`). `create_context()` adds this to any per-call context before passing it to `print_response`.

---

### Complete call path

For `io.info("Something happened")`:

1. `InfoPromptResponseManagerMixin.info()` calls `InfoPromptResponse.create_info(message, …)`, which calls `AbstractMessageResponse._create_symbol_message()`, which calls `PromptResponseLine.create_from_string()` → `parse_style_markup()` → builds `PromptResponseSegment` objects.
2. The constructed `InfoPromptResponse` is passed to `IoManager.print_response(response, context)`.
3. `print_response` stamps `created_at`, appends to the recorder stack (if active), checks `VerbosityLevel.QUIET`, then calls `self.output.print(response, self.create_context(context))`.
4. `PromptStdoutOutputHandler.print()` calls `response.render(context)`, which iterates `PromptResponseLine.render(context)` → `PromptResponseSegment.render(context, remaining_width)` → `ColorManager.colorize()` → ANSI string.
5. The joined string is written to `sys.stdout`.

For `self.info("…")` on a `WithIoMethods` subclass:

1. `__getattr__("info")` resolves to a wrapper around `self.io.info`.
2. The wrapper injects `context=self.create_io_context()` (which includes the class's indentation level), then calls `self.io.info(…, context=…)`.
3. From step 1 of the path above.

## Integration in the Suite

This package is part of the Wexample Suite — a collection of high-quality, modular tools designed to work seamlessly together across multiple languages and environments.

### Related Packages

The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.

Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.

## Dependencies

- attrs: >=23.1.0
- cattrs: >=23.1.0
- colorama: 
- readchar: 
- wcwidth: 
- wexample-helpers: >=19.1.0

## Versioning & Compatibility Policy

Wexample packages follow **Semantic Versioning** (SemVer):

- **MAJOR**: Breaking changes
- **MINOR**: New features, backward compatible
- **PATCH**: Bug fixes, backward compatible

We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

Free to use in both personal and commercial projects.

## About us

[Wexample](https://wexample.com) stands as a cornerstone of the digital ecosystem — a collective of seasoned engineers, researchers, and creators driven by a relentless pursuit of technological excellence. More than a media platform, it has grown into a vibrant community where innovation meets craftsmanship, and where every line of code reflects a commitment to clarity, durability, and shared intelligence.

This packages suite embodies this spirit. Trusted by professionals and enthusiasts alike, it delivers a consistent, high-quality foundation for modern development — open, elegant, and battle-tested. Its reputation is built on years of collaboration, refinement, and rigorous attention to detail, making it a natural choice for those who demand both robustness and beauty in their tools.

Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.

## Known Limitations & Roadmap

Current limitations and planned features are tracked in the GitHub issues.

See the [project roadmap](https://github.com/wexample/python-prompt/issues) for upcoming features and improvements.

## Status & Compatibility

**Maturity**: Production-ready

**Python Support**: >=3.10

**OS Support**: Linux, macOS, Windows

**Status**: Actively maintained

## Useful Links

- **Homepage**: https://github.com/wexample/python-prompt
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
- **Issue Tracker**: https://github.com/wexample/python-prompt/issues
- **Discussions**: https://github.com/wexample/python-prompt/discussions
- **PyPI**: [pypi.org/project/wexample-prompt](https://pypi.org/project/wexample-prompt/)

## Migration Notes

When upgrading between major versions, refer to the migration guides in the documentation.

Breaking changes are clearly documented with upgrade paths and examples.
