codekingpro/portable-devtools
114k
1import configparser2from typing import Dict, List, IO, Mapping, Optional3 4from .default_styles import DEFAULT_STYLES5from .style import Style, StyleType6 7 8class Theme:9 """A container for style information, used by :class:`~rich.console.Console`.10 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 """15 16 styles: Dict[str, Style]17 18 def __init__(19 self, styles: Optional[Mapping[str, StyleType]] = None, inherit: bool = True20 ):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 )29 30 @property31 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 config37 38 @classmethod39 def from_file(40 cls, config_file: IO[str], source: Optional[str] = None, inherit: bool = True41 ) -> "Theme":42 """Load a theme from a text mode file.43 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.48 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 theme57 58 @classmethod59 def read(60 cls, path: str, inherit: bool = True, encoding: Optional[str] = None61 ) -> "Theme":62 """Read a theme from a path.63 64 Args:65 path (str): Path to a config file readable by Python configparser module.66 inherit (bool, optional): Inherit default styles. Defaults to True.67 encoding (str, optional): Encoding of the config file. Defaults to None.68 69 Returns:70 Theme: A new theme instance.71 """72 with open(path, "rt", encoding=encoding) as config_file:73 return cls.from_file(config_file, source=path, inherit=inherit)74 75 76class ThemeStackError(Exception):77 """Base exception for errors related to the theme stack."""78 79 80class ThemeStack:81 """A stack of themes.82 83 Args:84 theme (Theme): A theme instance85 """86 87 def __init__(self, theme: Theme) -> None:88 self._entries: List[Dict[str, Style]] = [theme.styles]89 self.get = self._entries[-1].get90 91 def push_theme(self, theme: Theme, inherit: bool = True) -> None:92 """Push a theme on the top of the stack.93 94 Args:95 theme (Theme): A Theme instance.96 inherit (boolean, optional): Inherit styles from current top of stack.97 """98 styles: Dict[str, Style]99 styles = (100 {**self._entries[-1], **theme.styles} if inherit else theme.styles.copy()101 )102 self._entries.append(styles)103 self.get = self._entries[-1].get104 105 def pop_theme(self) -> None:106 """Pop (and discard) the top-most theme."""107 if len(self._entries) == 1:108 raise ThemeStackError("Unable to pop base theme")109 self._entries.pop()110 self.get = self._entries[-1].get111 112 113if __name__ == "__main__": # pragma: no cover114 theme = Theme()115 print(theme.config)116 