Skip to content

Commit 6d64998

Browse files
committed
feat: add ProgressBar component with customizable styles
1 parent b3cb4fc commit 6d64998

5 files changed

Lines changed: 264 additions & 125 deletions

File tree

textcompose/core/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
from textcompose.core.component import Component # type: ignore[unused-import]
77

88
Value = Union[MagicFilter, str, Callable[[Mapping[str, Any]], str | None], "Component"]
9-
Condition = Union[MagicFilter, Callable[[Mapping[str, Any]], bool], bool, "Component"]
9+
Condition = Union[MagicFilter, Callable[[Mapping[str, Any]], bool], bool, str, "Component"]
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from textcompose.core import Condition, Value, resolve_value
2+
from textcompose.elements.base import Element
3+
from textcompose.styles.progress_bar import PROGRESS_BAR_STYLES, ProgressBarStyle
4+
5+
6+
class ProgressBar(Element):
7+
def __init__(
8+
self,
9+
current: Value,
10+
total: Value = 100,
11+
width: Value | int = 20,
12+
style: Value | ProgressBarStyle = "emoj_square",
13+
when: Condition | None = None,
14+
):
15+
super().__init__(when=when)
16+
self.current = current
17+
self.total = total
18+
self.width = width
19+
self.style = style
20+
self.when = when
21+
22+
def render(self, context, **kwargs) -> str | None:
23+
if not self._check_when(context, **kwargs):
24+
return None
25+
26+
if isinstance(self.style, str):
27+
style_obj = PROGRESS_BAR_STYLES.get(self.style, None)
28+
else:
29+
style_obj = ProgressBarStyle(
30+
left=resolve_value(self.style.left, context),
31+
fill=resolve_value(self.style.fill, context),
32+
empty=resolve_value(self.style.empty, context),
33+
right=resolve_value(self.style.right, context),
34+
)
35+
if self.style.template is not None:
36+
style_obj.template = resolve_value(self.style.template, context)
37+
38+
if style_obj is None:
39+
raise ValueError(f"Unknown style: {self.style}. Available styles: {', '.join(PROGRESS_BAR_STYLES.keys())}")
40+
41+
length = int(resolve_value(self.width, context))
42+
if length <= 0:
43+
raise ValueError("Progress bar length must be a positive integer.")
44+
current = float(resolve_value(self.current, context))
45+
if current < 0:
46+
raise ValueError("Current value must be non-negative.")
47+
total = float(resolve_value(self.total, context))
48+
if total <= 0:
49+
raise ValueError("Total value must be greater than zero.")
50+
51+
percent = min(max(current / total, 0), 1)
52+
filled_len = int(round(length * percent))
53+
empty_len = length - filled_len
54+
55+
bar_str = (style_obj.fill * filled_len) + (style_obj.empty * empty_len)
56+
57+
return style_obj.template.format(
58+
left=style_obj.left,
59+
bar=bar_str,
60+
right=style_obj.right,
61+
percent=f"{int(percent * 100)}%",
62+
total=total,
63+
current=current,
64+
)

textcompose/styles/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__all__ = ["ProgressBarStyle"]
2+
3+
from textcompose.styles.progress_bar import ProgressBarStyle

textcompose/styles/progress_bar.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from dataclasses import dataclass
2+
3+
from textcompose.core import Value
4+
5+
6+
@dataclass
7+
class ProgressBarStyle:
8+
"""
9+
Configuration for customizing the appearance of a progress bar.
10+
11+
Attributes:
12+
left (str | Value): Character or value for the left border.
13+
fill (str | Value): Character or value for the filled portion.
14+
empty (str | Value): Character or value for the empty portion.
15+
right (str | Value): Character or value for the right border.
16+
template (str): Format string for rendering the progress bar.
17+
18+
Defaults to "{left}{bar}{right} {percent}".
19+
20+
Supported placeholders:
21+
- {left}: Left border.
22+
- {bar}: Combined filled and empty segments.
23+
- {right}: Right border.
24+
- {percent}: Progress percentage (e.g., "50%").
25+
- {total}: Total number of steps.
26+
- {current}: Current step number.
27+
28+
Built-in styles:
29+
Several built-in progress bar styles are available in the `PROGRESS_BAR_STYLES` dictionary
30+
defined in this file. To use a built-in style, simply specify its name (string key).
31+
For example:
32+
33+
style = "symbol_square"
34+
35+
See the `PROGRESS_BAR_STYLES` dictionary in this file for the full list of available style names.
36+
"""
37+
38+
left: str | Value
39+
fill: str | Value
40+
empty: str | Value
41+
right: str | Value
42+
template: str = "{left}{bar}{right} {percent}"
43+
44+
45+
PROGRESS_BAR_STYLES = {
46+
# Symbol styles
47+
"symbol_square": ProgressBarStyle(left="[", fill="■", empty=" ", right="]"),
48+
"symbol_simple": ProgressBarStyle(left="[", fill="=", empty=" ", right="]"),
49+
"symbol_modern": ProgressBarStyle(left="|", fill="█", empty=" ", right="|"),
50+
"symbol_classic": ProgressBarStyle(left="[", fill="#", empty="-", right="]"),
51+
"symbol_block": ProgressBarStyle(left="[", fill="█", empty="░", right="]"),
52+
"symbol_arrow": ProgressBarStyle(left="|", fill="=", empty="-", right=">"),
53+
"symbol_circle": ProgressBarStyle(left="(", fill="●", empty="○", right=")"),
54+
"symbol_bracket": ProgressBarStyle(left="<", fill="#", empty=".", right=">"),
55+
"symbol_star": ProgressBarStyle(left="[", fill="★", empty="☆", right="]"),
56+
"symbol_magic": ProgressBarStyle(left="{", fill="▓", empty="░", right="}"),
57+
"symbol_line": ProgressBarStyle(left="[", fill="=", empty=" ", right="]"),
58+
"symbol_pipe": ProgressBarStyle(left="|", fill="█", empty=" ", right="|"),
59+
# Emoji styles
60+
"emoj_square": ProgressBarStyle(left="", fill="🟩", empty="⬜", right=""),
61+
"emoji_circle": ProgressBarStyle(left="", fill="🟢", empty="⚪", right=""),
62+
}

0 commit comments

Comments
 (0)