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

39 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

1import configparser 

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

3 

4from .default_styles import DEFAULT_STYLES 

5from .style import Style, StyleType 

6 

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 = 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 ) 

29 

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 

37 

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. 

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 theme 

57 

58 @classmethod 

59 def read( 

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

61 ) -> "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 instance 

85 """ 

86 

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

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

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

90 

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].get 

104 

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].get 

111 

112 

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

114 theme = Theme() 

115 print(theme.config)