# TextCompose
[](https://pypi.org/project/textcompose)
[](/LICENSE)
[](https://github.com/m-xim/textcompose/actions)
[](https://github.com/m-xim/textcompose/actions)
[](https://deepwiki.com/m-xim/textcompose)
**TextCompose** is a Python library for creating dynamic, structured text templates with an intuitive, component-based approach. Inspired by [aiogram-dialog](https://github.com/Tishka17/aiogram_dialog).
<br>
## ✨ Features
- 🧱 Flexible text composition from components
- 🔀 Conditional rendering support (`when`)
- 🔁 Grouping and repeating blocks
- 🎨 Formatting via f-string and Jinja2
- 🔌 Easily extensible with new components
## 🚀 Installation
```bash
uv add textcompose
# or
pip install textcompose
```
## 🧩 Components Overview
### General
- `Template` — main class for combining and rendering components
### Elements
`Element` — abstract base class for all element components
- `Text` — static text.
- `Format` — dynamic python f-string formatting
- `Jinja` — Jinja2 template rendering
- [`ProgressBar`](#progressbar) — show progress visually
### Containers
`Container` — abstract base class for all container components
- `Group` — group children with custom separators.
- `List` — repeat templates for each item in a collection.
### Logic Components
`Logic` — abstract base class for all container components
- `If` — conditionally show different blocks (use `if_`, `then_`, `else_`)
> [!TIP]
> All components support the `when` parameter for conditional display (value, expression, function, or magic_filter).
## ⚡️ How to Use
All usage examples can be found in the [`example`](./example) folder.
### Quick Start
See how easy it is to build structured, interactive text blocks:
```python
from magic_filter import F
from textcompose import Template
from textcompose.containers import Group, List
from textcompose.elements import Format, Jinja, Text
from textcompose.logics import If
template = Template(
Format("Hello, {name}!"),
Format("Status: {status}"), # or `lambda ctx: f"Status: {ctx['status']}"` with function
If(
F["notifications"] > 0, # `if_`: condition to check if there are notifications
Format("You have {notifications} new notifications."), # `then_`: content to render if condition is True
Format("You not have new notifications."), # `else_`: content to render if condition is False
),
Group(
Jinja("\nTotal messages {{ messages|length }}:"),
List(
Format("Time - {item[time]}:"),
Format("- {item[text]}"),
sep="\n", # `sep`: separator between list items
inner_sep="\n", # `inner_sep`: separator between parts of a single item
getter=lambda ctx: ctx["messages"], # `getter`: function or F to extract the list of messages from context
),
sep="\n", # `sep`: separator between children of Group
when=F["messages"].len() > 0, # `when`: show this block only if there are messages
),
Text("\nThank you for using our service!"), # or "Recent messages:" without class
)
context = {
"name": "Alexey",
"status": "Online",
"notifications": 2,
"messages": [
{"text": "Your package has been delivered.", "time": "09:15"},
{"text": "Reminder: meeting tomorrow at 10:00.", "time": "18:42"},
],
}
print(template.render(context))
```
**Output:**
```
Hello, Alexey!
Status: Online
You have 2 new notifications.
Total messages 2:
Time - 09:15:
- Your package has been delivered.
Time - 18:42:
- Reminder: meeting tomorrow at 10:00.
Thank you for using our service!
```
### ProgressBar
The `ProgressBar` component renders a textual progress bar. It supports various styles, customizable width, templates, and dynamic values.
#### Usage
```python
from textcompose.elements import ProgressBar
bar = ProgressBar(
current=42, # `current`: Current progress value
total=100, # `total`: Total value for the progress bar
width=20, # `width`: Number of characters in the bar.
style="symbol_square", # `style`: Style (string — built-in style name, or `ProgressBarStyle` object).
)
print(bar.render({}))
```
**Output:**
```
[■■■■■■■■ ] 42%
```
#### Styles
Built-in styles are listed in the `PROGRESS_BAR_STYLES` dictionary (see [`textcompose/styles/progress_bar.py`](./textcompose/styles/progress_bar.py)). Examples:
- `"symbol_square"`: `[■■■■■ ]`
- `"symbol_classic"`: `[#####-----]`
- `"emoj_square"`: `🟩🟩🟩⬜⬜⬜`
- `"emoji_circle"`: `🟢🟢⚪⚪⚪`
You can create a custom style using `ProgressBarStyle`:
```python
from textcompose.styles import ProgressBarStyle
from textcompose.elements import ProgressBar
custom_style = ProgressBarStyle(
left="<", fill="*", empty="-", right=">", template="{percent} {left}{bar}{right}"
)
bar = ProgressBar(current=7, total=10, width=10, style=custom_style)
print(bar.render({}))
```
**Output:**
```
70% <*******--->
```
##### Template
The `template` parameter in the style allows you to customize the output string. Available placeholders:
- `{left}` — left border
- `{bar}` — the bar itself (filled + empty part)
- `{right}` — right border
- `{percent}` — percent complete (e.g., `42%`)
- `{total}` — maximum value
- `{current}` — current value
## 🤝 Contributing
💡 Ideas? Issues? PRs are welcome!<br>
Open an issue or pull request to help TextCompose get even better.
<br>
> **Ready to supercharge your text formatting?<br>**
> **Try TextCompose today and make your bots, reports, and notifications shine! ✨**
Raw data
{
"_id": null,
"home_page": null,
"name": "textcompose",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "compose, logic templates, structured text, template, templating, text generation",
"author": null,
"author_email": "m-xim <i@m-xim.ru>",
"download_url": "https://files.pythonhosted.org/packages/00/ad/1c57d6f901b9ab1934db3eab591bf93ec7a54a7252ff41ef4a52c9b6b165/textcompose-1.2.0.tar.gz",
"platform": null,
"description": "# TextCompose\n\n[](https://pypi.org/project/textcompose)\n[](/LICENSE)\n[](https://github.com/m-xim/textcompose/actions)\n[](https://github.com/m-xim/textcompose/actions)\n[](https://deepwiki.com/m-xim/textcompose)\n\n**TextCompose** is a Python library for creating dynamic, structured text templates with an intuitive, component-based approach. Inspired by [aiogram-dialog](https://github.com/Tishka17/aiogram_dialog).\n\n<br>\n\n## \u2728 Features\n\n- \ud83e\uddf1 Flexible text composition from components\n- \ud83d\udd00 Conditional rendering support (`when`)\n- \ud83d\udd01 Grouping and repeating blocks\n- \ud83c\udfa8 Formatting via f-string and Jinja2\n- \ud83d\udd0c Easily extensible with new components\n\n## \ud83d\ude80 Installation\n\n```bash\nuv add textcompose\n# or\npip install textcompose\n```\n\n## \ud83e\udde9 Components Overview\n\n### General\n\n- `Template` \u2014 main class for combining and rendering components\n\n\n### Elements\n`Element` \u2014 abstract base class for all element components\n\n- `Text` \u2014 static text.\n- `Format` \u2014 dynamic python f-string formatting\n- `Jinja` \u2014 Jinja2 template rendering\n- [`ProgressBar`](#progressbar) \u2014 show progress visually\n\n### Containers\n`Container` \u2014 abstract base class for all container components\n\n- `Group` \u2014 group children with custom separators.\n- `List` \u2014 repeat templates for each item in a collection.\n\n### Logic Components\n`Logic` \u2014 abstract base class for all container components\n\n- `If` \u2014 conditionally show different blocks (use `if_`, `then_`, `else_`)\n\n> [!TIP]\n> All components support the `when` parameter for conditional display (value, expression, function, or magic_filter).\n\n## \u26a1\ufe0f How to Use\n\nAll usage examples can be found in the [`example`](./example) folder.\n\n### Quick Start\nSee how easy it is to build structured, interactive text blocks:\n\n```python\nfrom magic_filter import F\n\nfrom textcompose import Template\nfrom textcompose.containers import Group, List\nfrom textcompose.elements import Format, Jinja, Text\nfrom textcompose.logics import If\n\ntemplate = Template(\n Format(\"Hello, {name}!\"),\n Format(\"Status: {status}\"), # or `lambda ctx: f\"Status: {ctx['status']}\"` with function\n If(\n F[\"notifications\"] > 0, # `if_`: condition to check if there are notifications\n Format(\"You have {notifications} new notifications.\"), # `then_`: content to render if condition is True\n Format(\"You not have new notifications.\"), # `else_`: content to render if condition is False\n ),\n Group(\n Jinja(\"\\nTotal messages {{ messages|length }}:\"),\n List(\n Format(\"Time - {item[time]}:\"),\n Format(\"- {item[text]}\"),\n sep=\"\\n\", # `sep`: separator between list items\n inner_sep=\"\\n\", # `inner_sep`: separator between parts of a single item\n getter=lambda ctx: ctx[\"messages\"], # `getter`: function or F to extract the list of messages from context\n ),\n sep=\"\\n\", # `sep`: separator between children of Group\n when=F[\"messages\"].len() > 0, # `when`: show this block only if there are messages\n ),\n Text(\"\\nThank you for using our service!\"), # or \"Recent messages:\" without class\n)\n\ncontext = {\n \"name\": \"Alexey\",\n \"status\": \"Online\",\n \"notifications\": 2,\n \"messages\": [\n {\"text\": \"Your package has been delivered.\", \"time\": \"09:15\"},\n {\"text\": \"Reminder: meeting tomorrow at 10:00.\", \"time\": \"18:42\"},\n ],\n}\n\nprint(template.render(context))\n```\n\n**Output:**\n```\nHello, Alexey!\nStatus: Online\nYou have 2 new notifications.\n\nTotal messages 2:\nTime - 09:15:\n- Your package has been delivered.\nTime - 18:42:\n- Reminder: meeting tomorrow at 10:00.\n\nThank you for using our service!\n```\n\n### ProgressBar\n\nThe `ProgressBar` component renders a textual progress bar. It supports various styles, customizable width, templates, and dynamic values.\n\n#### Usage\n\n```python\nfrom textcompose.elements import ProgressBar\n\nbar = ProgressBar(\n current=42, # `current`: Current progress value\n total=100, # `total`: Total value for the progress bar\n width=20, # `width`: Number of characters in the bar.\n style=\"symbol_square\", # `style`: Style (string \u2014 built-in style name, or `ProgressBarStyle` object).\n)\nprint(bar.render({}))\n```\n\n**Output:**\n```\n[\u25a0\u25a0\u25a0\u25a0\u25a0\u25a0\u25a0\u25a0 ] 42%\n```\n\n#### Styles\n\nBuilt-in styles are listed in the `PROGRESS_BAR_STYLES` dictionary (see [`textcompose/styles/progress_bar.py`](./textcompose/styles/progress_bar.py)). Examples:\n\n- `\"symbol_square\"`: `[\u25a0\u25a0\u25a0\u25a0\u25a0 ]`\n- `\"symbol_classic\"`: `[#####-----]`\n- `\"emoj_square\"`: `\ud83d\udfe9\ud83d\udfe9\ud83d\udfe9\u2b1c\u2b1c\u2b1c`\n- `\"emoji_circle\"`: `\ud83d\udfe2\ud83d\udfe2\u26aa\u26aa\u26aa`\n\nYou can create a custom style using `ProgressBarStyle`:\n\n```python\nfrom textcompose.styles import ProgressBarStyle\nfrom textcompose.elements import ProgressBar\n\n\ncustom_style = ProgressBarStyle(\n left=\"<\", fill=\"*\", empty=\"-\", right=\">\", template=\"{percent} {left}{bar}{right}\"\n)\nbar = ProgressBar(current=7, total=10, width=10, style=custom_style)\nprint(bar.render({}))\n```\n\n**Output:**\n```\n70% <*******--->\n```\n\n##### Template\n\nThe `template` parameter in the style allows you to customize the output string. Available placeholders:\n- `{left}` \u2014 left border\n- `{bar}` \u2014 the bar itself (filled + empty part)\n- `{right}` \u2014 right border\n- `{percent}` \u2014 percent complete (e.g., `42%`)\n- `{total}` \u2014 maximum value\n- `{current}` \u2014 current value\n\n## \ud83e\udd1d Contributing\n\n\ud83d\udca1 Ideas? Issues? PRs are welcome!<br>\nOpen an issue or pull request to help TextCompose get even better.\n\n<br>\n\n> **Ready to supercharge your text formatting?<br>**\n> **Try TextCompose today and make your bots, reports, and notifications shine! \u2728**\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "A Python library for building dynamic, structured text templates using a declarative, compose-based approach",
"version": "1.2.0",
"project_urls": {
"Homepage": "https://github.com/m-xim/textcompose",
"Issues": "https://github.com/m-xim/textcompose/issues",
"Repository": "https://github.com/m-xim/textcompose"
},
"split_keywords": [
"compose",
" logic templates",
" structured text",
" template",
" templating",
" text generation"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "f79f16d0b4174cc25c6440ac6273dbd4e6820209822d8023ebe04e33d26c98e9",
"md5": "c717ca1a75548a0c03451fc0bc8aecdf",
"sha256": "f479d4ea56e2f9fcc32d1aa07653ea33793fae9d48528b7093308c47fc0f214e"
},
"downloads": -1,
"filename": "textcompose-1.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "c717ca1a75548a0c03451fc0bc8aecdf",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 17555,
"upload_time": "2025-07-08T15:31:08",
"upload_time_iso_8601": "2025-07-08T15:31:08.718910Z",
"url": "https://files.pythonhosted.org/packages/f7/9f/16d0b4174cc25c6440ac6273dbd4e6820209822d8023ebe04e33d26c98e9/textcompose-1.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "00ad1c57d6f901b9ab1934db3eab591bf93ec7a54a7252ff41ef4a52c9b6b165",
"md5": "8886791ffb27ef325e8b0cf1abd5cce6",
"sha256": "c2fbd8b6a578cee4feb91eb43b1ed045057a4824417ef69bbcc5a173ec72007a"
},
"downloads": -1,
"filename": "textcompose-1.2.0.tar.gz",
"has_sig": false,
"md5_digest": "8886791ffb27ef325e8b0cf1abd5cce6",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 34245,
"upload_time": "2025-07-08T15:31:10",
"upload_time_iso_8601": "2025-07-08T15:31:10.018247Z",
"url": "https://files.pythonhosted.org/packages/00/ad/1c57d6f901b9ab1934db3eab591bf93ec7a54a7252ff41ef4a52c9b6b165/textcompose-1.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-08 15:31:10",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "m-xim",
"github_project": "textcompose",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "textcompose"
}