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

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(cls, path: str, inherit: bool = True) -> "Theme": 

60 """Read a theme from a path. 

61 

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. 

65 

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) 

71 

72 

73class ThemeStackError(Exception): 

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

75 

76 

77class ThemeStack: 

78 """A stack of themes. 

79 

80 Args: 

81 theme (Theme): A theme instance 

82 """ 

83 

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

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

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

87 

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

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

90 

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 

101 

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 

108 

109 

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

111 theme = Theme() 

112 print(theme.config)