Coverage for /run/media/veganeco/water/status600.com/pypi/reptilian_climates/modules_series_2/ships/fields/gardens_pip/rich/theme.py: 59%

1 import configparser

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

3

4 from .default_styles import DEFAULT_STYLES

5 from .style import Style, StyleType

6

7

8 class 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

76 class ThemeStackError(Exception):

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

78

79

80 class 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

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

114 theme = Theme()

115 print(theme.config)