Metadata-Version: 2.4
Name: nyxcore
Version: 0.7.0
Summary: nyx's utility library
Author: verticalsync
Author-email: verticalsync <nightly@riseup.net>
License-Expression: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
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: Programming Language :: Python :: 3.15
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
Requires-Dist: sphinx>=8.0.0 ; extra == 'docs'
Requires-Dist: sphinx-rtd-theme>=3.0.0 ; extra == 'docs'
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/verticalsync/nyxcore
Project-URL: Source, https://github.com/verticalsync/nyxcore
Provides-Extra: docs
Description-Content-Type: text/markdown

# nyxcore

[![PyPI version](https://img.shields.io/pypi/v/nyxcore)](https://pypi.org/project/nyxcore/)
[![Python versions](https://img.shields.io/pypi/pyversions/nyxcore)](https://pypi.org/project/nyxcore/)
[![License](https://img.shields.io/pypi/l/nyxcore)](https://github.com/verticalsync/nyxcore/blob/main/LICENSE)  
[![CI](https://github.com/verticalsync/nyxcore/actions/workflows/ci.yml/badge.svg)](https://github.com/verticalsync/nyxcore/actions/workflows/ci.yml)
[![codecov](https://codecov.io/github/verticalsync/nyxcore/graph/badge.svg?token=95GMGGSEOA)](https://codecov.io/github/verticalsync/nyxcore)

personal utility library

## modules

| module | description |
|---|---|
| `nyxcore.color` | ANSI color support — named colors, 256-color, true color, Windows compat, auto-strip |
| `nyxcore.logger` | Configurable logger with colored console output, file logging, custom formats |
| `nyxcore.prompt` | Interactive CLI prompts — confirm, ask, select, checkbox, password |
| `nyxcore.runtime` | Cross-platform OS detection — Windows, macOS, Linux version/edition/family checks |
| `nyxcore.config` | Typed config loader — env, JSON, JSONC, JSON5, TOML, INI, nested classes |

---

## `nyxcore.color`

ANSI escape code helpers. Zero dependencies.

```python
from nyxcore.color import fg, attr, fg256, fgrgb, strip, init
```

<details>
<summary><code>fg</code> / <code>bg</code> — color constants</summary>

| code | `fg` | `bg` |
|---|---|---|
| 30/40 | `fg.black` | `bg.black` |
| 31/41 | `fg.red` | `bg.red` |
| 32/42 | `fg.green` | `bg.green` |
| 33/43 | `fg.yellow` | `bg.yellow` |
| 34/44 | `fg.blue` | `bg.blue` |
| 35/45 | `fg.magenta` | `bg.magenta` |
| 36/46 | `fg.cyan` | `bg.cyan` |
| 37/47 | `fg.white` | `bg.white` |
| 90/100 | `fg.gray` | `bg.gray` |
| 91/101 | `fg.bright_red` | `bg.bright_red` |
| 92/102 | `fg.bright_green` | `bg.bright_green` |
| 93/103 | `fg.bright_yellow` | `bg.bright_yellow` |
| 94/104 | `fg.bright_blue` | `bg.bright_blue` |
| 95/105 | `fg.bright_magenta` | `bg.bright_magenta` |
| 96/106 | `fg.bright_cyan` | `bg.bright_cyan` |
| 97/107 | `fg.bright_white` | `bg.bright_white` |
| 39/49 | `fg.reset` | `bg.reset` |

```python
print(f"{fg.cyan}hello{fg.reset}")
```

</details>

<details>
<summary><code>attr</code> — text styles</summary>

`bold` (1), `dim` (2), `italic` (3), `underline` (4), `blink` (5), `reverse` (7), `hidden` (8), `strikethrough` (9), `reset` (0), `reset_bold` (22), `reset_italic` (23), `reset_underline` (24), `reset_blink` (25), `reset_reverse` (27).

```python
print(f"{attr.bold}{fg.red}bold red{attr.reset}")
```

</details>

<details>
<summary><code>fg256</code> / <code>bg256</code> / <code>fgrgb</code> / <code>bgrgb</code></summary>

Extended color functions.

| function | description |
|---|---|
| `fg256(code)` | 256-color foreground (0–255) |
| `bg256(code)` | 256-color background |
| `fgrgb(r, g, b)` | 24-bit true color foreground |
| `bgrgb(r, g, b)` | 24-bit true color background |

```python
print(f"{fg256(196)}bright red{fg.reset}")
print(f"{fgrgb(255, 100, 50)}orange{fg.reset}")
```

</details>

<details>
<summary><code>strip(text)</code></summary>

Remove all ANSI escape sequences.

```python
clean = strip("\x1b[31mhello\x1b[0m")  # "hello"
```

</details>

<details>
<summary><code>init(strip_auto=True)</code></summary>

Enable Windows console ANSI support. Call once at startup.

On Windows: enables virtual terminal processing via Win32 API.  
When `strip_auto=True`: non-TTY streams are wrapped to strip ANSI codes automatically.

```python
from nyxcore.color import init
init()
```

</details>

---

## `nyxcore.logger`

Configurable logger on top of stdlib `logging`. Supports colored console output, file output, custom formats.

```python
from nyxcore.logger import Logger

log = Logger("myapp")
log.info("hello")
log.warning("careful")
log.error("oops")
```

<details>
<summary><code>Logger(name, level, *, colorize, fmt, file)</code></summary>

| param | type | default | description |
|---|---|---|---|
| `name` | `str` | `"nyxcore"` | logger name |
| `level` | `str` or `int` | `"INFO"` | `DEBUG`/`INFO`/`WARNING`/`ERROR`/`CRITICAL` or a `logging` constant |
| `colorize` | `bool` or `None` | auto | `True` = colors on, `False` = plain, `None` = TTY-detect |
| `fmt` | `str` or `None` | default | see format below |
| `file` | `str` or `Path` | `None` | path to a log file (appends) |

</details>

<details>
<summary><code>set_level(level)</code></summary>

Change minimum log level at runtime.

```python
log.set_level("DEBUG")
log.debug("now visible")
```

</details>

<details>
<summary><code>add_file(path, level=None)</code></summary>

Add a file handler after construction. File output is never colorized.

```python
log.add_file("app.log")
log.add_file("errors.log", level="ERROR")
```

</details>

<details>
<summary><code>format</code></summary>

Python format specifiers work (`{level:>8}` right-aligns in 8 chars).

| placeholder | logging attr | example |
|---|---|---|
| `{time}` | `asctime` | `2026-07-21 18:42:29,755` |
| `{level}` | `levelname` | `INFO` |
| `{levelno}` | `levelno` | `20` |
| `{name}` | `name` | `myapp` |
| `{message}` | `message` | `hello` |
| `{path}` | `pathname` | `/app/src/main.py` |
| `{file}` | `filename` | `main.py` |
| `{module}` | `module` | `main` |
| `{func}` | `funcName` | `connect_db` |
| `{line}` | `lineno` | `42` |
| `{created}` | `created` | `1721590349.755` |
| `{msec}` | `msecs` | `755` |
| `{thread}` | `thread` | `140735248279360` |
| `{thread_name}` | `threadName` | `MainThread` |
| `{process}` | `process` | `12345` |

Default: `[{time}] {level:>8} | {name} | {message}`

```python
log = Logger("app", fmt="{level} | {message}")
```

**Color map:**

| level | color |
|---|---|
| `DEBUG` | gray (244) |
| `INFO` | blue (39) |
| `WARNING` | yellow (220) |
| `ERROR` | red (196) |
| `CRITICAL` | bold red |

</details>

<details>
<summary><code>examples</code></summary>

```python
# Log to file only
log = Logger("app", colorize=False, file="app.log")

# Log errors separately
log = Logger("app")
log.add_file("app.log", level="DEBUG")
log.add_file("errors.log", level="ERROR")
```

</details>

---

## `nyxcore.prompt`

Interactive CLI prompts with customizable formatting.

```python
from nyxcore.prompt import ask, confirm, select, checkbox, password

name = ask("Name", default="user")
ok = confirm("Continue", default=True)
opt = select("Pick color", ["red", "green", "blue"])
tags = checkbox("Select tags", ["urgent", "bug", "feature"], defaults=["bug"])
secret = password("Passphrase")
```

<details>
<summary><code>Prompt.__init__</code> — customization</summary>

| param | default | description |
|---|---|---|
| `prefix` | `"? "` | prompt prefix |
| `error_prefix` | `"✗ "` | error prefix |
| `yes_label` / `no_label` | `"y"` / `"n"` | confirm labels |
| `selected_marker` / `unselected_marker` | `"● "` / `"○ "` | checkbox markers |
| `mask` | `"•"` | password mask |
| `hint_style` / `error_style` | gray / red | ANSI styles |
| `default_fmt` | `"({value})"` | default value template |
| `suffix` | `" "` | prompt terminator |

```python
from nyxcore.color import fg
from nyxcore.prompt import Prompt

p = Prompt(prefix=f"{fg.green}?{fg.reset} ", suffix=" > ")
```

</details>

<details>
<summary><code>confirm(message, *, default)</code></summary>

Ask a yes/no question.

- **default**: `True` = `[Y/n]`, `False` = `[y/N]`, `None` = `[y/n]`.

```python
ok = p.confirm("Delete?", default=False)
```

</details>

<details>
<summary><code>ask(message, *, default, validate)</code></summary>

Free-text input.

- **default**: Value returned on empty input.
- **validate**: Callable that receives raw input.

```python
age = p.ask("Age", default=18, validate=int)
```

</details>

<details>
<summary><code>select(message, options, *, default)</code></summary>

Pick one from a list or dict.

- **options**: `list[str]` or `dict[str, str]` (keys → descriptions).

```python
p.select("Color", ["red", "green"])
p.select("Letter", {"a": "Alpha", "b": "Beta"})
```

</details>

<details>
<summary><code>checkbox(message, options, *, defaults, min_select, max_select)</code></summary>

Pick multiple.

- **min_select**: Minimum required.
- **max_select**: Maximum allowed.
- **returns**: `list[str]` or `None`.

```python
p.checkbox("Tags", ["urgent", "bug"], min_select=1, max_select=2)
```

</details>

<details>
<summary><code>password(message, *, mask)</code></summary>

Hidden input.

- **mask**: `False` hides all output.

```python
p.password("Passphrase", mask="*")
p.password("Pin", mask=False)
```

</details>

Standalone `confirm()`, `ask()`, `select()`, `checkbox()`, `password()` use a default `Prompt()`.

---

## `nyxcore.config`

Typed config loader. Supports `.env`, `.json`, `.jsonc`, `.json5`, `.toml`, `.ini` — zero deps.

```python
from nyxcore.config import load_config

class Database:
    HOST: str = "localhost"
    PORT: int = 5432

class AppConfig:
    NAME: str
    DB: Database

cfg = load_config(AppConfig, "config.toml")
```

<details>
<summary><code>load_config(config_cls, *paths, format, string, strict, prefix)</code></summary>

| param | type | default | description |
|---|---|---|---|
| `config_cls` | `type` | — | Class with annotated fields |
| `*paths` | `str` | — | File paths (format auto-detected) |
| `format` | `str` | `None` | Force format (`"json"`, `"toml"`, etc.) |
| `string` | `str` | `None` | Inline config string |
| `strict` | `bool` | `False` | Raise on missing required fields |
| `prefix` | `str` | `""` | Env var prefix (e.g. `"MYAPP_"`) |

**Supported formats:**

| ext | parser | notes |
|---|---|---|
| `.env` | `KEY=VALUE` | Quoted values, comments |
| `.json` | `json.loads` | Standard JSON |
| `.jsonc` | comment-stripped JSON | `//` and `/* */` |
| `.json5` | normalized JSON | Unquoted keys, trailing commas |
| `.toml` | `tomllib` | Python 3.11+ |
| `.ini` | `configparser` | Section headers, case-preserving |

</details>

<details>
<summary>Nested classes + <code>load_env()</code></summary>

Nested config classes with `__` separator:

```python
class Database:
    HOST: str = "localhost"

class Config:
    DB: Database

cfg = load_config(Config, string='DB={"HOST":"db.example.com"}')
cfg = load_config(Config, string="DB__HOST=db.example.com")
```

`load_env()` — backward-compat wrapper:

```python
from nyxcore.config import load_env
cfg = load_env(Config, file=".env", strict=True)
```

**Exceptions:**

| exception | meaning |
|---|---|
| `ConfigError` | Base error |
| `MissingVarError` | Required field missing |
| `ParseError` | Type coercion failure |

</details>

<details>
<summary>Examples by format</summary>

All examples use this config class:

```python
from nyxcore.config import load_config

class Logging:
    LEVEL: str = "info"
    FILE: str | None = None

class AppConfig:
    HOST: str
    PORT: int = 8080
    DEBUG: bool = False
    TAGS: list[str] = []
    LOG: Logging = Logging()
```

---

**`.env` file — flat keys, `__` for nesting:**

```env
HOST=localhost
PORT=5432
DEBUG=true
TAGS=api,web
LOG__LEVEL=debug
```

```python
cfg = load_config(AppConfig, "config.env")
# cfg.HOST == "localhost", cfg.PORT == 5432, cfg.DEBUG is True
# cfg.TAGS == ["api", "web"]
# cfg.LOG.LEVEL == "debug"
```

---

**`.json` file — standard JSON:**

```json
{
    "HOST": "localhost",
    "PORT": 5432,
    "DEBUG": true,
    "TAGS": ["api", "web"],
    "LOG": {
        "LEVEL": "debug",
        "FILE": "/var/log/app.log"
    }
}
```

```python
cfg = load_config(AppConfig, "config.json")
```

---

**`.jsonc` file — JSON with `//` and `/* */` comments:**

```jsonc
{
    // server address
    "HOST": "localhost",
    /* default port */
    "PORT": 5432,
    "TAGS": ["api", "web"],
    "LOG": {
        "LEVEL": "debug"
    }
}
```

```python
cfg = load_config(AppConfig, "config.jsonc")
```

---

**`.json5` file — relaxed JSON (unquoted keys, single quotes, trailing commas):**

```json5
{
    HOST: 'localhost',  // string
    PORT: 5432,
    DEBUG: true,
    TAGS: ['api', 'web'],
}
```

```python
cfg = load_config(AppConfig, "config.json5")
```

---

**`.toml` file — sections for nested classes:**

```toml
HOST = "localhost"
PORT = 5432
DEBUG = true
TAGS = ["api", "web"]

[LOG]
LEVEL = "debug"
FILE = "/var/log/app.log"
```

```python
cfg = load_config(AppConfig, "config.toml")
```

---

**`.ini` file — sections for nested classes (single-section flattened):**

```ini
[app]
HOST = localhost
PORT = 5432
DEBUG = true

[LOG]
LEVEL = debug
FILE = /var/log/app.log
```

```python
cfg = load_config(AppConfig, "config.ini")
# cfg.HOST == "localhost", cfg.LOG.LEVEL == "debug"
```

---

**Inline string (env-style, no file needed):**

```python
cfg = load_config(AppConfig, string="""
    HOST=localhost
    PORT=5432
    DEBUG=true
    TAGS=api,web
    LOG__LEVEL=debug
""")
```

**Multiple files (later values win):**

```python
cfg = load_config(AppConfig, "defaults.toml", "overrides.json")
```

**Strict mode (requires all fields without defaults):**

```python
cfg = load_config(AppConfig, "config.toml", strict=True)
```

</details>

---

## `nyxcore.runtime`

Cross-platform OS detection with strict validation.

```python
from nyxcore.runtime import is_windows, is_macos, is_linux, platform_info
```

<details>
<summary><code>platform_info()</code></summary>

Returns a `PlatformInfo` dataclass.

```python
info = platform_info()
print(info.name)    # "Windows 11", "macOS Sequoia", "Ubuntu 24.04"
```

</details>

<details>
<summary><code>is_windows(version, edition, server)</code></summary>

| param | type | description |
|---|---|---|
| `version` | `str` or `None` | `"11"`, `"10"`, `"7"`, `"server 2022"`, `"longhorn"`, etc. |
| `edition` | `str` or `None` | `"pro"`, `"enterprise"`, `"home"`, `"iot"`, `"datacenter"`, etc. |
| `server` | `bool` or `None` | `True` = server only, `False` = desktop only |

```python
is_windows("11")
is_windows("10", "pro")
is_windows(server=True)
```

</details>

<details>
<summary><code>is_macos(version)</code></summary>

- **version**: Numeric (`"15"`, `"10.15"`) or codename (`"sequoia"`, `"catalina"`, `"cheetah"`).

```python
is_macos("sequoia")
is_macos("15")
```

</details>

<details>
<summary><code>is_linux(distro, version, family)</code></summary>

| param | type | description |
|---|---|---|
| `distro` | `str` or `None` | `"ubuntu"`, `"arch"`, `"fedora"`, etc. |
| `version` | `str` or `None` | Version (`"24.04"`). Requires `distro`. |
| `family` | `str` or `None` | Package-manager family (`"debian"`, `"rpm"`, `"arch"`, `"alpine"`) |

```python
is_linux("ubuntu")
is_linux(family="debian")
is_linux(family="arch")
```

</details>

<details>
<summary>Exceptions</summary>

All inherit from `UnsupportedPlatformValueError` (subclass of `ValueError`):

| exception | raised by |
|---|---|
| `UnsupportedWindowsVersionError` | unknown Windows version |
| `UnsupportedWindowsEditionError` | unknown Windows edition |
| `UnsupportedMacOSVersionError` | unknown macOS version |
| `UnsupportedLinuxDistributionError` | unknown Linux distro |
| `UnsupportedLinuxVersionError` | version without distro |
| `UnsupportedLinuxFamilyError` | unknown Linux family |

</details>
