]> jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/pip/_vendor/rich/style.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / pip / _vendor / rich / style.py
1 import sys
2 from functools import lru_cache
3 from marshal import dumps, loads
4 from random import randint
5 from typing import Any, Dict, Iterable, List, Optional, Type, Union, cast
6
7 from . import errors
8 from .color import Color, ColorParseError, ColorSystem, blend_rgb
9 from .repr import Result, rich_repr
10 from .terminal_theme import DEFAULT_TERMINAL_THEME, TerminalTheme
11
12 # Style instances and style definitions are often interchangeable
13 StyleType = Union[str, "Style"]
14
15
16 class _Bit:
17 """A descriptor to get/set a style attribute bit."""
18
19 __slots__ = ["bit"]
20
21 def __init__(self, bit_no: int) -> None:
22 self.bit = 1 << bit_no
23
24 def __get__(self, obj: "Style", objtype: Type["Style"]) -> Optional[bool]:
25 if obj._set_attributes & self.bit:
26 return obj._attributes & self.bit != 0
27 return None
28
29
30 @rich_repr
31 class Style:
32 """A terminal style.
33
34 A terminal style consists of a color (`color`), a background color (`bgcolor`), and a number of attributes, such
35 as bold, italic etc. The attributes have 3 states: they can either be on
36 (``True``), off (``False``), or not set (``None``).
37
38 Args:
39 color (Union[Color, str], optional): Color of terminal text. Defaults to None.
40 bgcolor (Union[Color, str], optional): Color of terminal background. Defaults to None.
41 bold (bool, optional): Enable bold text. Defaults to None.
42 dim (bool, optional): Enable dim text. Defaults to None.
43 italic (bool, optional): Enable italic text. Defaults to None.
44 underline (bool, optional): Enable underlined text. Defaults to None.
45 blink (bool, optional): Enabled blinking text. Defaults to None.
46 blink2 (bool, optional): Enable fast blinking text. Defaults to None.
47 reverse (bool, optional): Enabled reverse text. Defaults to None.
48 conceal (bool, optional): Enable concealed text. Defaults to None.
49 strike (bool, optional): Enable strikethrough text. Defaults to None.
50 underline2 (bool, optional): Enable doubly underlined text. Defaults to None.
51 frame (bool, optional): Enable framed text. Defaults to None.
52 encircle (bool, optional): Enable encircled text. Defaults to None.
53 overline (bool, optional): Enable overlined text. Defaults to None.
54 link (str, link): Link URL. Defaults to None.
55
56 """
57
58 _color: Optional[Color]
59 _bgcolor: Optional[Color]
60 _attributes: int
61 _set_attributes: int
62 _hash: Optional[int]
63 _null: bool
64 _meta: Optional[bytes]
65
66 __slots__ = [
67 "_color",
68 "_bgcolor",
69 "_attributes",
70 "_set_attributes",
71 "_link",
72 "_link_id",
73 "_ansi",
74 "_style_definition",
75 "_hash",
76 "_null",
77 "_meta",
78 ]
79
80 # maps bits on to SGR parameter
81 _style_map = {
82 0: "1",
83 1: "2",
84 2: "3",
85 3: "4",
86 4: "5",
87 5: "6",
88 6: "7",
89 7: "8",
90 8: "9",
91 9: "21",
92 10: "51",
93 11: "52",
94 12: "53",
95 }
96
97 STYLE_ATTRIBUTES = {
98 "dim": "dim",
99 "d": "dim",
100 "bold": "bold",
101 "b": "bold",
102 "italic": "italic",
103 "i": "italic",
104 "underline": "underline",
105 "u": "underline",
106 "blink": "blink",
107 "blink2": "blink2",
108 "reverse": "reverse",
109 "r": "reverse",
110 "conceal": "conceal",
111 "c": "conceal",
112 "strike": "strike",
113 "s": "strike",
114 "underline2": "underline2",
115 "uu": "underline2",
116 "frame": "frame",
117 "encircle": "encircle",
118 "overline": "overline",
119 "o": "overline",
120 }
121
122 def __init__(
123 self,
124 *,
125 color: Optional[Union[Color, str]] = None,
126 bgcolor: Optional[Union[Color, str]] = None,
127 bold: Optional[bool] = None,
128 dim: Optional[bool] = None,
129 italic: Optional[bool] = None,
130 underline: Optional[bool] = None,
131 blink: Optional[bool] = None,
132 blink2: Optional[bool] = None,
133 reverse: Optional[bool] = None,
134 conceal: Optional[bool] = None,
135 strike: Optional[bool] = None,
136 underline2: Optional[bool] = None,
137 frame: Optional[bool] = None,
138 encircle: Optional[bool] = None,
139 overline: Optional[bool] = None,
140 link: Optional[str] = None,
141 meta: Optional[Dict[str, Any]] = None,
142 ):
143 self._ansi: Optional[str] = None
144 self._style_definition: Optional[str] = None
145
146 def _make_color(color: Union[Color, str]) -> Color:
147 return color if isinstance(color, Color) else Color.parse(color)
148
149 self._color = None if color is None else _make_color(color)
150 self._bgcolor = None if bgcolor is None else _make_color(bgcolor)
151 self._set_attributes = sum(
152 (
153 bold is not None,
154 dim is not None and 2,
155 italic is not None and 4,
156 underline is not None and 8,
157 blink is not None and 16,
158 blink2 is not None and 32,
159 reverse is not None and 64,
160 conceal is not None and 128,
161 strike is not None and 256,
162 underline2 is not None and 512,
163 frame is not None and 1024,
164 encircle is not None and 2048,
165 overline is not None and 4096,
166 )
167 )
168 self._attributes = (
169 sum(
170 (
171 bold and 1 or 0,
172 dim and 2 or 0,
173 italic and 4 or 0,
174 underline and 8 or 0,
175 blink and 16 or 0,
176 blink2 and 32 or 0,
177 reverse and 64 or 0,
178 conceal and 128 or 0,
179 strike and 256 or 0,
180 underline2 and 512 or 0,
181 frame and 1024 or 0,
182 encircle and 2048 or 0,
183 overline and 4096 or 0,
184 )
185 )
186 if self._set_attributes
187 else 0
188 )
189
190 self._link = link
191 self._meta = None if meta is None else dumps(meta)
192 self._link_id = (
193 f"{randint(0, 999999)}{hash(self._meta)}" if (link or meta) else ""
194 )
195 self._hash: Optional[int] = None
196 self._null = not (self._set_attributes or color or bgcolor or link or meta)
197
198 @classmethod
199 def null(cls) -> "Style":
200 """Create an 'null' style, equivalent to Style(), but more performant."""
201 return NULL_STYLE
202
203 @classmethod
204 def from_color(
205 cls, color: Optional[Color] = None, bgcolor: Optional[Color] = None
206 ) -> "Style":
207 """Create a new style with colors and no attributes.
208
209 Returns:
210 color (Optional[Color]): A (foreground) color, or None for no color. Defaults to None.
211 bgcolor (Optional[Color]): A (background) color, or None for no color. Defaults to None.
212 """
213 style: Style = cls.__new__(Style)
214 style._ansi = None
215 style._style_definition = None
216 style._color = color
217 style._bgcolor = bgcolor
218 style._set_attributes = 0
219 style._attributes = 0
220 style._link = None
221 style._link_id = ""
222 style._meta = None
223 style._null = not (color or bgcolor)
224 style._hash = None
225 return style
226
227 @classmethod
228 def from_meta(cls, meta: Optional[Dict[str, Any]]) -> "Style":
229 """Create a new style with meta data.
230
231 Returns:
232 meta (Optional[Dict[str, Any]]): A dictionary of meta data. Defaults to None.
233 """
234 style: Style = cls.__new__(Style)
235 style._ansi = None
236 style._style_definition = None
237 style._color = None
238 style._bgcolor = None
239 style._set_attributes = 0
240 style._attributes = 0
241 style._link = None
242 style._meta = dumps(meta)
243 style._link_id = f"{randint(0, 999999)}{hash(style._meta)}"
244 style._hash = None
245 style._null = not (meta)
246 return style
247
248 @classmethod
249 def on(cls, meta: Optional[Dict[str, Any]] = None, **handlers: Any) -> "Style":
250 """Create a blank style with meta information.
251
252 Example:
253 style = Style.on(click=self.on_click)
254
255 Args:
256 meta (Optional[Dict[str, Any]], optional): An optional dict of meta information.
257 **handlers (Any): Keyword arguments are translated in to handlers.
258
259 Returns:
260 Style: A Style with meta information attached.
261 """
262 meta = {} if meta is None else meta
263 meta.update({f"@{key}": value for key, value in handlers.items()})
264 return cls.from_meta(meta)
265
266 bold = _Bit(0)
267 dim = _Bit(1)
268 italic = _Bit(2)
269 underline = _Bit(3)
270 blink = _Bit(4)
271 blink2 = _Bit(5)
272 reverse = _Bit(6)
273 conceal = _Bit(7)
274 strike = _Bit(8)
275 underline2 = _Bit(9)
276 frame = _Bit(10)
277 encircle = _Bit(11)
278 overline = _Bit(12)
279
280 @property
281 def link_id(self) -> str:
282 """Get a link id, used in ansi code for links."""
283 return self._link_id
284
285 def __str__(self) -> str:
286 """Re-generate style definition from attributes."""
287 if self._style_definition is None:
288 attributes: List[str] = []
289 append = attributes.append
290 bits = self._set_attributes
291 if bits & 0b0000000001111:
292 if bits & 1:
293 append("bold" if self.bold else "not bold")
294 if bits & (1 << 1):
295 append("dim" if self.dim else "not dim")
296 if bits & (1 << 2):
297 append("italic" if self.italic else "not italic")
298 if bits & (1 << 3):
299 append("underline" if self.underline else "not underline")
300 if bits & 0b0000111110000:
301 if bits & (1 << 4):
302 append("blink" if self.blink else "not blink")
303 if bits & (1 << 5):
304 append("blink2" if self.blink2 else "not blink2")
305 if bits & (1 << 6):
306 append("reverse" if self.reverse else "not reverse")
307 if bits & (1 << 7):
308 append("conceal" if self.conceal else "not conceal")
309 if bits & (1 << 8):
310 append("strike" if self.strike else "not strike")
311 if bits & 0b1111000000000:
312 if bits & (1 << 9):
313 append("underline2" if self.underline2 else "not underline2")
314 if bits & (1 << 10):
315 append("frame" if self.frame else "not frame")
316 if bits & (1 << 11):
317 append("encircle" if self.encircle else "not encircle")
318 if bits & (1 << 12):
319 append("overline" if self.overline else "not overline")
320 if self._color is not None:
321 append(self._color.name)
322 if self._bgcolor is not None:
323 append("on")
324 append(self._bgcolor.name)
325 if self._link:
326 append("link")
327 append(self._link)
328 self._style_definition = " ".join(attributes) or "none"
329 return self._style_definition
330
331 def __bool__(self) -> bool:
332 """A Style is false if it has no attributes, colors, or links."""
333 return not self._null
334
335 def _make_ansi_codes(self, color_system: ColorSystem) -> str:
336 """Generate ANSI codes for this style.
337
338 Args:
339 color_system (ColorSystem): Color system.
340
341 Returns:
342 str: String containing codes.
343 """
344
345 if self._ansi is None:
346 sgr: List[str] = []
347 append = sgr.append
348 _style_map = self._style_map
349 attributes = self._attributes & self._set_attributes
350 if attributes:
351 if attributes & 1:
352 append(_style_map[0])
353 if attributes & 2:
354 append(_style_map[1])
355 if attributes & 4:
356 append(_style_map[2])
357 if attributes & 8:
358 append(_style_map[3])
359 if attributes & 0b0000111110000:
360 for bit in range(4, 9):
361 if attributes & (1 << bit):
362 append(_style_map[bit])
363 if attributes & 0b1111000000000:
364 for bit in range(9, 13):
365 if attributes & (1 << bit):
366 append(_style_map[bit])
367 if self._color is not None:
368 sgr.extend(self._color.downgrade(color_system).get_ansi_codes())
369 if self._bgcolor is not None:
370 sgr.extend(
371 self._bgcolor.downgrade(color_system).get_ansi_codes(
372 foreground=False
373 )
374 )
375 self._ansi = ";".join(sgr)
376 return self._ansi
377
378 @classmethod
379 @lru_cache(maxsize=1024)
380 def normalize(cls, style: str) -> str:
381 """Normalize a style definition so that styles with the same effect have the same string
382 representation.
383
384 Args:
385 style (str): A style definition.
386
387 Returns:
388 str: Normal form of style definition.
389 """
390 try:
391 return str(cls.parse(style))
392 except errors.StyleSyntaxError:
393 return style.strip().lower()
394
395 @classmethod
396 def pick_first(cls, *values: Optional[StyleType]) -> StyleType:
397 """Pick first non-None style."""
398 for value in values:
399 if value is not None:
400 return value
401 raise ValueError("expected at least one non-None style")
402
403 def __rich_repr__(self) -> Result:
404 yield "color", self.color, None
405 yield "bgcolor", self.bgcolor, None
406 yield "bold", self.bold, None,
407 yield "dim", self.dim, None,
408 yield "italic", self.italic, None
409 yield "underline", self.underline, None,
410 yield "blink", self.blink, None
411 yield "blink2", self.blink2, None
412 yield "reverse", self.reverse, None
413 yield "conceal", self.conceal, None
414 yield "strike", self.strike, None
415 yield "underline2", self.underline2, None
416 yield "frame", self.frame, None
417 yield "encircle", self.encircle, None
418 yield "link", self.link, None
419 if self._meta:
420 yield "meta", self.meta
421
422 def __eq__(self, other: Any) -> bool:
423 if not isinstance(other, Style):
424 return NotImplemented
425 return self.__hash__() == other.__hash__()
426
427 def __ne__(self, other: Any) -> bool:
428 if not isinstance(other, Style):
429 return NotImplemented
430 return self.__hash__() != other.__hash__()
431
432 def __hash__(self) -> int:
433 if self._hash is not None:
434 return self._hash
435 self._hash = hash(
436 (
437 self._color,
438 self._bgcolor,
439 self._attributes,
440 self._set_attributes,
441 self._link,
442 self._meta,
443 )
444 )
445 return self._hash
446
447 @property
448 def color(self) -> Optional[Color]:
449 """The foreground color or None if it is not set."""
450 return self._color
451
452 @property
453 def bgcolor(self) -> Optional[Color]:
454 """The background color or None if it is not set."""
455 return self._bgcolor
456
457 @property
458 def link(self) -> Optional[str]:
459 """Link text, if set."""
460 return self._link
461
462 @property
463 def transparent_background(self) -> bool:
464 """Check if the style specified a transparent background."""
465 return self.bgcolor is None or self.bgcolor.is_default
466
467 @property
468 def background_style(self) -> "Style":
469 """A Style with background only."""
470 return Style(bgcolor=self.bgcolor)
471
472 @property
473 def meta(self) -> Dict[str, Any]:
474 """Get meta information (can not be changed after construction)."""
475 return {} if self._meta is None else cast(Dict[str, Any], loads(self._meta))
476
477 @property
478 def without_color(self) -> "Style":
479 """Get a copy of the style with color removed."""
480 if self._null:
481 return NULL_STYLE
482 style: Style = self.__new__(Style)
483 style._ansi = None
484 style._style_definition = None
485 style._color = None
486 style._bgcolor = None
487 style._attributes = self._attributes
488 style._set_attributes = self._set_attributes
489 style._link = self._link
490 style._link_id = f"{randint(0, 999999)}" if self._link else ""
491 style._null = False
492 style._meta = None
493 style._hash = None
494 return style
495
496 @classmethod
497 @lru_cache(maxsize=4096)
498 def parse(cls, style_definition: str) -> "Style":
499 """Parse a style definition.
500
501 Args:
502 style_definition (str): A string containing a style.
503
504 Raises:
505 errors.StyleSyntaxError: If the style definition syntax is invalid.
506
507 Returns:
508 `Style`: A Style instance.
509 """
510 if style_definition.strip() == "none" or not style_definition:
511 return cls.null()
512
513 STYLE_ATTRIBUTES = cls.STYLE_ATTRIBUTES
514 color: Optional[str] = None
515 bgcolor: Optional[str] = None
516 attributes: Dict[str, Optional[Any]] = {}
517 link: Optional[str] = None
518
519 words = iter(style_definition.split())
520 for original_word in words:
521 word = original_word.lower()
522 if word == "on":
523 word = next(words, "")
524 if not word:
525 raise errors.StyleSyntaxError("color expected after 'on'")
526 try:
527 Color.parse(word) is None
528 except ColorParseError as error:
529 raise errors.StyleSyntaxError(
530 f"unable to parse {word!r} as background color; {error}"
531 ) from None
532 bgcolor = word
533
534 elif word == "not":
535 word = next(words, "")
536 attribute = STYLE_ATTRIBUTES.get(word)
537 if attribute is None:
538 raise errors.StyleSyntaxError(
539 f"expected style attribute after 'not', found {word!r}"
540 )
541 attributes[attribute] = False
542
543 elif word == "link":
544 word = next(words, "")
545 if not word:
546 raise errors.StyleSyntaxError("URL expected after 'link'")
547 link = word
548
549 elif word in STYLE_ATTRIBUTES:
550 attributes[STYLE_ATTRIBUTES[word]] = True
551
552 else:
553 try:
554 Color.parse(word)
555 except ColorParseError as error:
556 raise errors.StyleSyntaxError(
557 f"unable to parse {word!r} as color; {error}"
558 ) from None
559 color = word
560 style = Style(color=color, bgcolor=bgcolor, link=link, **attributes)
561 return style
562
563 @lru_cache(maxsize=1024)
564 def get_html_style(self, theme: Optional[TerminalTheme] = None) -> str:
565 """Get a CSS style rule."""
566 theme = theme or DEFAULT_TERMINAL_THEME
567 css: List[str] = []
568 append = css.append
569
570 color = self.color
571 bgcolor = self.bgcolor
572 if self.reverse:
573 color, bgcolor = bgcolor, color
574 if self.dim:
575 foreground_color = (
576 theme.foreground_color if color is None else color.get_truecolor(theme)
577 )
578 color = Color.from_triplet(
579 blend_rgb(foreground_color, theme.background_color, 0.5)
580 )
581 if color is not None:
582 theme_color = color.get_truecolor(theme)
583 append(f"color: {theme_color.hex}")
584 append(f"text-decoration-color: {theme_color.hex}")
585 if bgcolor is not None:
586 theme_color = bgcolor.get_truecolor(theme, foreground=False)
587 append(f"background-color: {theme_color.hex}")
588 if self.bold:
589 append("font-weight: bold")
590 if self.italic:
591 append("font-style: italic")
592 if self.underline:
593 append("text-decoration: underline")
594 if self.strike:
595 append("text-decoration: line-through")
596 if self.overline:
597 append("text-decoration: overline")
598 return "; ".join(css)
599
600 @classmethod
601 def combine(cls, styles: Iterable["Style"]) -> "Style":
602 """Combine styles and get result.
603
604 Args:
605 styles (Iterable[Style]): Styles to combine.
606
607 Returns:
608 Style: A new style instance.
609 """
610 iter_styles = iter(styles)
611 return sum(iter_styles, next(iter_styles))
612
613 @classmethod
614 def chain(cls, *styles: "Style") -> "Style":
615 """Combine styles from positional argument in to a single style.
616
617 Args:
618 *styles (Iterable[Style]): Styles to combine.
619
620 Returns:
621 Style: A new style instance.
622 """
623 iter_styles = iter(styles)
624 return sum(iter_styles, next(iter_styles))
625
626 def copy(self) -> "Style":
627 """Get a copy of this style.
628
629 Returns:
630 Style: A new Style instance with identical attributes.
631 """
632 if self._null:
633 return NULL_STYLE
634 style: Style = self.__new__(Style)
635 style._ansi = self._ansi
636 style._style_definition = self._style_definition
637 style._color = self._color
638 style._bgcolor = self._bgcolor
639 style._attributes = self._attributes
640 style._set_attributes = self._set_attributes
641 style._link = self._link
642 style._link_id = f"{randint(0, 999999)}" if self._link else ""
643 style._hash = self._hash
644 style._null = False
645 style._meta = self._meta
646 return style
647
648 @lru_cache(maxsize=128)
649 def clear_meta_and_links(self) -> "Style":
650 """Get a copy of this style with link and meta information removed.
651
652 Returns:
653 Style: New style object.
654 """
655 if self._null:
656 return NULL_STYLE
657 style: Style = self.__new__(Style)
658 style._ansi = self._ansi
659 style._style_definition = self._style_definition
660 style._color = self._color
661 style._bgcolor = self._bgcolor
662 style._attributes = self._attributes
663 style._set_attributes = self._set_attributes
664 style._link = None
665 style._link_id = ""
666 style._hash = self._hash
667 style._null = False
668 style._meta = None
669 return style
670
671 def update_link(self, link: Optional[str] = None) -> "Style":
672 """Get a copy with a different value for link.
673
674 Args:
675 link (str, optional): New value for link. Defaults to None.
676
677 Returns:
678 Style: A new Style instance.
679 """
680 style: Style = self.__new__(Style)
681 style._ansi = self._ansi
682 style._style_definition = self._style_definition
683 style._color = self._color
684 style._bgcolor = self._bgcolor
685 style._attributes = self._attributes
686 style._set_attributes = self._set_attributes
687 style._link = link
688 style._link_id = f"{randint(0, 999999)}" if link else ""
689 style._hash = None
690 style._null = False
691 style._meta = self._meta
692 return style
693
694 def render(
695 self,
696 text: str = "",
697 *,
698 color_system: Optional[ColorSystem] = ColorSystem.TRUECOLOR,
699 legacy_windows: bool = False,
700 ) -> str:
701 """Render the ANSI codes for the style.
702
703 Args:
704 text (str, optional): A string to style. Defaults to "".
705 color_system (Optional[ColorSystem], optional): Color system to render to. Defaults to ColorSystem.TRUECOLOR.
706
707 Returns:
708 str: A string containing ANSI style codes.
709 """
710 if not text or color_system is None:
711 return text
712 attrs = self._ansi or self._make_ansi_codes(color_system)
713 rendered = f"\x1b[{attrs}m{text}\x1b[0m" if attrs else text
714 if self._link and not legacy_windows:
715 rendered = (
716 f"\x1b]8;id={self._link_id};{self._link}\x1b\\{rendered}\x1b]8;;\x1b\\"
717 )
718 return rendered
719
720 def test(self, text: Optional[str] = None) -> None:
721 """Write text with style directly to terminal.
722
723 This method is for testing purposes only.
724
725 Args:
726 text (Optional[str], optional): Text to style or None for style name.
727
728 """
729 text = text or str(self)
730 sys.stdout.write(f"{self.render(text)}\n")
731
732 @lru_cache(maxsize=1024)
733 def _add(self, style: Optional["Style"]) -> "Style":
734 if style is None or style._null:
735 return self
736 if self._null:
737 return style
738 new_style: Style = self.__new__(Style)
739 new_style._ansi = None
740 new_style._style_definition = None
741 new_style._color = style._color or self._color
742 new_style._bgcolor = style._bgcolor or self._bgcolor
743 new_style._attributes = (self._attributes & ~style._set_attributes) | (
744 style._attributes & style._set_attributes
745 )
746 new_style._set_attributes = self._set_attributes | style._set_attributes
747 new_style._link = style._link or self._link
748 new_style._link_id = style._link_id or self._link_id
749 new_style._null = style._null
750 if self._meta and style._meta:
751 new_style._meta = dumps({**self.meta, **style.meta})
752 else:
753 new_style._meta = self._meta or style._meta
754 new_style._hash = None
755 return new_style
756
757 def __add__(self, style: Optional["Style"]) -> "Style":
758 combined_style = self._add(style)
759 return combined_style.copy() if combined_style.link else combined_style
760
761
762 NULL_STYLE = Style()
763
764
765 class StyleStack:
766 """A stack of styles."""
767
768 __slots__ = ["_stack"]
769
770 def __init__(self, default_style: "Style") -> None:
771 self._stack: List[Style] = [default_style]
772
773 def __repr__(self) -> str:
774 return f"<stylestack {self._stack!r}>"
775
776 @property
777 def current(self) -> Style:
778 """Get the Style at the top of the stack."""
779 return self._stack[-1]
780
781 def push(self, style: Style) -> None:
782 """Push a new style on to the stack.
783
784 Args:
785 style (Style): New style to combine with current style.
786 """
787 self._stack.append(self._stack[-1] + style)
788
789 def pop(self) -> Style:
790 """Pop last style and discard.
791
792 Returns:
793 Style: New current style (also available as stack.current)
794 """
795 self._stack.pop()
796 return self._stack[-1]