Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rich/theme.py: 51%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

39 statements  

1from typing import IO, Dict, List, Mapping, Optional 

2 

3from .default_styles import DEFAULT_STYLES 

4from .style import Style, StyleType 

5 

6 

7class Theme: 

8 """A container for style information, used by :class:`~rich.console.Console`. 

9 

10 Args: 

11 styles (Dict[str, Style], optional): A mapping of style names on to styles. Defaults to None for a theme with no styles. 

12 inherit (bool, optional): Inherit default styles. Defaults to True. 

13 """ 

14 

15 styles: Dict[str, Style] 

16 

17 def __init__( 

18 self, styles: Optional[Mapping[str, StyleType]] = None, inherit: bool = True 

19 ): 

20 self.styles = DEFAULT_STYLES.copy() if inherit else {} 

21 if styles is not None: 

22 self.styles.update( 

23 { 

24 name: style if isinstance(style, Style) else Style.parse(style) 

25 for name, style in styles.items() 

26 } 

27 ) 

28 

29 @property 

30 def config(self) -> str: 

31 """Get contents of a config file for this theme.""" 

32 config = "[styles]\n" + "\n".join( 

33 f"{name} = {style}" for name, style in sorted(self.styles.items()) 

34 ) 

35 return config 

36 

37 @classmethod 

38 def from_file( 

39 cls, config_file: IO[str], source: Optional[str] = None, inherit: bool = True 

40 ) -> "Theme": 

41 """Load a theme from a text mode file. 

42 

43 Args: 

44 config_file (IO[str]): An open conf file. 

45 source (str, optional): The filename of the open file. Defaults to None. 

46 inherit (bool, optional): Inherit default styles. Defaults to True. 

47 

48 Returns: 

49 Theme: A New theme instance. 

50 """ 

51 import configparser 

52 

53 config = configparser.ConfigParser() 

54 config.read_file(config_file, source=source) 

55 styles = {name: Style.parse(value) for name, value in config.items("styles")} 

56 theme = Theme(styles, inherit=inherit) 

57 return theme 

58 

59 @classmethod 

60 def read( 

61 cls, path: str, inherit: bool = True, encoding: Optional[str] = None 

62 ) -> "Theme": 

63 """Read a theme from a path. 

64 

65 Args: 

66 path (str): Path to a config file readable by Python configparser module. 

67 inherit (bool, optional): Inherit default styles. Defaults to True. 

68 encoding (str, optional): Encoding of the config file. Defaults to None. 

69 

70 Returns: 

71 Theme: A new theme instance. 

72 """ 

73 with open(path, encoding=encoding) as config_file: 

74 return cls.from_file(config_file, source=path, inherit=inherit) 

75 

76 

77class ThemeStackError(Exception): 

78 """Base exception for errors related to the theme stack.""" 

79 

80 

81class ThemeStack: 

82 """A stack of themes. 

83 

84 Args: 

85 theme (Theme): A theme instance 

86 """ 

87 

88 def __init__(self, theme: Theme) -> None: 

89 self._entries: List[Dict[str, Style]] = [theme.styles] 

90 self.get = self._entries[-1].get 

91 

92 def push_theme(self, theme: Theme, inherit: bool = True) -> None: 

93 """Push a theme on the top of the stack. 

94 

95 Args: 

96 theme (Theme): A Theme instance. 

97 inherit (boolean, optional): Inherit styles from current top of stack. 

98 """ 

99 styles: Dict[str, Style] 

100 styles = ( 

101 {**self._entries[-1], **theme.styles} if inherit else theme.styles.copy() 

102 ) 

103 self._entries.append(styles) 

104 self.get = self._entries[-1].get 

105 

106 def pop_theme(self) -> None: 

107 """Pop (and discard) the top-most theme.""" 

108 if len(self._entries) == 1: 

109 raise ThemeStackError("Unable to pop base theme") 

110 self._entries.pop() 

111 self.get = self._entries[-1].get 

112 

113 

114if __name__ == "__main__": # pragma: no cover 

115 theme = Theme() 

116 print(theme.config)