Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pip/_vendor/rich/columns.py: 16%

98 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-02-26 06:33 +0000

1from collections import defaultdict 

2from itertools import chain 

3from operator import itemgetter 

4from typing import Dict, Iterable, List, Optional, Tuple 

5 

6from .align import Align, AlignMethod 

7from .console import Console, ConsoleOptions, RenderableType, RenderResult 

8from .constrain import Constrain 

9from .measure import Measurement 

10from .padding import Padding, PaddingDimensions 

11from .table import Table 

12from .text import TextType 

13from .jupyter import JupyterMixin 

14 

15 

16class Columns(JupyterMixin): 

17 """Display renderables in neat columns. 

18 

19 Args: 

20 renderables (Iterable[RenderableType]): Any number of Rich renderables (including str). 

21 width (int, optional): The desired width of the columns, or None to auto detect. Defaults to None. 

22 padding (PaddingDimensions, optional): Optional padding around cells. Defaults to (0, 1). 

23 expand (bool, optional): Expand columns to full width. Defaults to False. 

24 equal (bool, optional): Arrange in to equal sized columns. Defaults to False. 

25 column_first (bool, optional): Align items from top to bottom (rather than left to right). Defaults to False. 

26 right_to_left (bool, optional): Start column from right hand side. Defaults to False. 

27 align (str, optional): Align value ("left", "right", or "center") or None for default. Defaults to None. 

28 title (TextType, optional): Optional title for Columns. 

29 """ 

30 

31 def __init__( 

32 self, 

33 renderables: Optional[Iterable[RenderableType]] = None, 

34 padding: PaddingDimensions = (0, 1), 

35 *, 

36 width: Optional[int] = None, 

37 expand: bool = False, 

38 equal: bool = False, 

39 column_first: bool = False, 

40 right_to_left: bool = False, 

41 align: Optional[AlignMethod] = None, 

42 title: Optional[TextType] = None, 

43 ) -> None: 

44 self.renderables = list(renderables or []) 

45 self.width = width 

46 self.padding = padding 

47 self.expand = expand 

48 self.equal = equal 

49 self.column_first = column_first 

50 self.right_to_left = right_to_left 

51 self.align: Optional[AlignMethod] = align 

52 self.title = title 

53 

54 def add_renderable(self, renderable: RenderableType) -> None: 

55 """Add a renderable to the columns. 

56 

57 Args: 

58 renderable (RenderableType): Any renderable object. 

59 """ 

60 self.renderables.append(renderable) 

61 

62 def __rich_console__( 

63 self, console: Console, options: ConsoleOptions 

64 ) -> RenderResult: 

65 render_str = console.render_str 

66 renderables = [ 

67 render_str(renderable) if isinstance(renderable, str) else renderable 

68 for renderable in self.renderables 

69 ] 

70 if not renderables: 

71 return 

72 _top, right, _bottom, left = Padding.unpack(self.padding) 

73 width_padding = max(left, right) 

74 max_width = options.max_width 

75 widths: Dict[int, int] = defaultdict(int) 

76 column_count = len(renderables) 

77 

78 get_measurement = Measurement.get 

79 renderable_widths = [ 

80 get_measurement(console, options, renderable).maximum 

81 for renderable in renderables 

82 ] 

83 if self.equal: 

84 renderable_widths = [max(renderable_widths)] * len(renderable_widths) 

85 

86 def iter_renderables( 

87 column_count: int, 

88 ) -> Iterable[Tuple[int, Optional[RenderableType]]]: 

89 item_count = len(renderables) 

90 if self.column_first: 

91 width_renderables = list(zip(renderable_widths, renderables)) 

92 

93 column_lengths: List[int] = [item_count // column_count] * column_count 

94 for col_no in range(item_count % column_count): 

95 column_lengths[col_no] += 1 

96 

97 row_count = (item_count + column_count - 1) // column_count 

98 cells = [[-1] * column_count for _ in range(row_count)] 

99 row = col = 0 

100 for index in range(item_count): 

101 cells[row][col] = index 

102 column_lengths[col] -= 1 

103 if column_lengths[col]: 

104 row += 1 

105 else: 

106 col += 1 

107 row = 0 

108 for index in chain.from_iterable(cells): 

109 if index == -1: 

110 break 

111 yield width_renderables[index] 

112 else: 

113 yield from zip(renderable_widths, renderables) 

114 # Pad odd elements with spaces 

115 if item_count % column_count: 

116 for _ in range(column_count - (item_count % column_count)): 

117 yield 0, None 

118 

119 table = Table.grid(padding=self.padding, collapse_padding=True, pad_edge=False) 

120 table.expand = self.expand 

121 table.title = self.title 

122 

123 if self.width is not None: 

124 column_count = (max_width) // (self.width + width_padding) 

125 for _ in range(column_count): 

126 table.add_column(width=self.width) 

127 else: 

128 while column_count > 1: 

129 widths.clear() 

130 column_no = 0 

131 for renderable_width, _ in iter_renderables(column_count): 

132 widths[column_no] = max(widths[column_no], renderable_width) 

133 total_width = sum(widths.values()) + width_padding * ( 

134 len(widths) - 1 

135 ) 

136 if total_width > max_width: 

137 column_count = len(widths) - 1 

138 break 

139 else: 

140 column_no = (column_no + 1) % column_count 

141 else: 

142 break 

143 

144 get_renderable = itemgetter(1) 

145 _renderables = [ 

146 get_renderable(_renderable) 

147 for _renderable in iter_renderables(column_count) 

148 ] 

149 if self.equal: 

150 _renderables = [ 

151 None 

152 if renderable is None 

153 else Constrain(renderable, renderable_widths[0]) 

154 for renderable in _renderables 

155 ] 

156 if self.align: 

157 align = self.align 

158 _Align = Align 

159 _renderables = [ 

160 None if renderable is None else _Align(renderable, align) 

161 for renderable in _renderables 

162 ] 

163 

164 right_to_left = self.right_to_left 

165 add_row = table.add_row 

166 for start in range(0, len(_renderables), column_count): 

167 row = _renderables[start : start + column_count] 

168 if right_to_left: 

169 row = row[::-1] 

170 add_row(*row) 

171 yield table 

172 

173 

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

175 import os 

176 

177 console = Console() 

178 

179 files = [f"{i} {s}" for i, s in enumerate(sorted(os.listdir()))] 

180 columns = Columns(files, padding=(0, 1), expand=False, equal=False) 

181 console.print(columns) 

182 console.rule() 

183 columns.column_first = True 

184 console.print(columns) 

185 columns.right_to_left = True 

186 console.rule() 

187 console.print(columns)