Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rich/theme.py: 59%
39 statements
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
« prev ^ index » next coverage.py v7.0.1, created at 2022-12-25 06:11 +0000
1import configparser
2from typing import Dict, List, IO, Mapping, Optional
4from .default_styles import DEFAULT_STYLES
5from .style import Style, StyleType
8class Theme:
9 """A container for style information, used by :class:`~rich.console.Console`.
11 Args:
12 styles (Dict[str, Style], optional): A mapping of style names on to styles. Defaults to None for a theme with no styles.
13 inherit (bool, optional): Inherit default styles. Defaults to True.
14 """
16 styles: Dict[str, Style]
18 def __init__(
19 self, styles: Optional[Mapping[str, StyleType]] = None, inherit: bool = True
20 ):
21 self.styles = DEFAULT_STYLES.copy() if inherit else {}
22 if styles is not None:
23 self.styles.update(
24 {
25 name: style if isinstance(style, Style) else Style.parse(style)
26 for name, style in styles.items()
27 }
28 )
30 @property
31 def config(self) -> str:
32 """Get contents of a config file for this theme."""
33 config = "[styles]\n" + "\n".join(
34 f"{name} = {style}" for name, style in sorted(self.styles.items())
35 )
36 return config
38 @classmethod
39 def from_file(
40 cls, config_file: IO[str], source: Optional[str] = None, inherit: bool = True
41 ) -> "Theme":
42 """Load a theme from a text mode file.
44 Args:
45 config_file (IO[str]): An open conf file.
46 source (str, optional): The filename of the open file. Defaults to None.
47 inherit (bool, optional): Inherit default styles. Defaults to True.
49 Returns:
50 Theme: A New theme instance.
51 """
52 config = configparser.ConfigParser()
53 config.read_file(config_file, source=source)
54 styles = {name: Style.parse(value) for name, value in config.items("styles")}
55 theme = Theme(styles, inherit=inherit)
56 return theme
58 @classmethod
59 def read(cls, path: str, inherit: bool = True) -> "Theme":
60 """Read a theme from a path.
62 Args:
63 path (str): Path to a config file readable by Python configparser module.
64 inherit (bool, optional): Inherit default styles. Defaults to True.
66 Returns:
67 Theme: A new theme instance.
68 """
69 with open(path, "rt") as config_file:
70 return cls.from_file(config_file, source=path, inherit=inherit)
73class ThemeStackError(Exception):
74 """Base exception for errors related to the theme stack."""
77class ThemeStack:
78 """A stack of themes.
80 Args:
81 theme (Theme): A theme instance
82 """
84 def __init__(self, theme: Theme) -> None:
85 self._entries: List[Dict[str, Style]] = [theme.styles]
86 self.get = self._entries[-1].get
88 def push_theme(self, theme: Theme, inherit: bool = True) -> None:
89 """Push a theme on the top of the stack.
91 Args:
92 theme (Theme): A Theme instance.
93 inherit (boolean, optional): Inherit styles from current top of stack.
94 """
95 styles: Dict[str, Style]
96 styles = (
97 {**self._entries[-1], **theme.styles} if inherit else theme.styles.copy()
98 )
99 self._entries.append(styles)
100 self.get = self._entries[-1].get
102 def pop_theme(self) -> None:
103 """Pop (and discard) the top-most theme."""
104 if len(self._entries) == 1:
105 raise ThemeStackError("Unable to pop base theme")
106 self._entries.pop()
107 self.get = self._entries[-1].get
110if __name__ == "__main__": # pragma: no cover
111 theme = Theme()
112 print(theme.config)