]> jfr.im git - dlqueue.git/blame - venv/lib/python3.11/site-packages/pip/_vendor/rich/tree.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / pip / _vendor / rich / tree.py
CommitLineData
e0df8241
JR
1from typing import Iterator, List, Optional, Tuple
2
3from ._loop import loop_first, loop_last
4from .console import Console, ConsoleOptions, RenderableType, RenderResult
5from .jupyter import JupyterMixin
6from .measure import Measurement
7from .segment import Segment
8from .style import Style, StyleStack, StyleType
9from .styled import Styled
10
11
12class Tree(JupyterMixin):
13 """A renderable for a tree structure.
14
15 Args:
16 label (RenderableType): The renderable or str for the tree label.
17 style (StyleType, optional): Style of this tree. Defaults to "tree".
18 guide_style (StyleType, optional): Style of the guide lines. Defaults to "tree.line".
19 expanded (bool, optional): Also display children. Defaults to True.
20 highlight (bool, optional): Highlight renderable (if str). Defaults to False.
21 """
22
23 def __init__(
24 self,
25 label: RenderableType,
26 *,
27 style: StyleType = "tree",
28 guide_style: StyleType = "tree.line",
29 expanded: bool = True,
30 highlight: bool = False,
31 hide_root: bool = False,
32 ) -> None:
33 self.label = label
34 self.style = style
35 self.guide_style = guide_style
36 self.children: List[Tree] = []
37 self.expanded = expanded
38 self.highlight = highlight
39 self.hide_root = hide_root
40
41 def add(
42 self,
43 label: RenderableType,
44 *,
45 style: Optional[StyleType] = None,
46 guide_style: Optional[StyleType] = None,
47 expanded: bool = True,
48 highlight: Optional[bool] = False,
49 ) -> "Tree":
50 """Add a child tree.
51
52 Args:
53 label (RenderableType): The renderable or str for the tree label.
54 style (StyleType, optional): Style of this tree. Defaults to "tree".
55 guide_style (StyleType, optional): Style of the guide lines. Defaults to "tree.line".
56 expanded (bool, optional): Also display children. Defaults to True.
57 highlight (Optional[bool], optional): Highlight renderable (if str). Defaults to False.
58
59 Returns:
60 Tree: A new child Tree, which may be further modified.
61 """
62 node = Tree(
63 label,
64 style=self.style if style is None else style,
65 guide_style=self.guide_style if guide_style is None else guide_style,
66 expanded=expanded,
67 highlight=self.highlight if highlight is None else highlight,
68 )
69 self.children.append(node)
70 return node
71
72 def __rich_console__(
73 self, console: "Console", options: "ConsoleOptions"
74 ) -> "RenderResult":
75
76 stack: List[Iterator[Tuple[bool, Tree]]] = []
77 pop = stack.pop
78 push = stack.append
79 new_line = Segment.line()
80
81 get_style = console.get_style
82 null_style = Style.null()
83 guide_style = get_style(self.guide_style, default="") or null_style
84 SPACE, CONTINUE, FORK, END = range(4)
85
86 ASCII_GUIDES = (" ", "| ", "+-- ", "`-- ")
87 TREE_GUIDES = [
88 (" ", "│ ", "├── ", "└── "),
89 (" ", "┃ ", "┣━━ ", "┗━━ "),
90 (" ", "║ ", "╠══ ", "╚══ "),
91 ]
92 _Segment = Segment
93
94 def make_guide(index: int, style: Style) -> Segment:
95 """Make a Segment for a level of the guide lines."""
96 if options.ascii_only:
97 line = ASCII_GUIDES[index]
98 else:
99 guide = 1 if style.bold else (2 if style.underline2 else 0)
100 line = TREE_GUIDES[0 if options.legacy_windows else guide][index]
101 return _Segment(line, style)
102
103 levels: List[Segment] = [make_guide(CONTINUE, guide_style)]
104 push(iter(loop_last([self])))
105
106 guide_style_stack = StyleStack(get_style(self.guide_style))
107 style_stack = StyleStack(get_style(self.style))
108 remove_guide_styles = Style(bold=False, underline2=False)
109
110 depth = 0
111
112 while stack:
113 stack_node = pop()
114 try:
115 last, node = next(stack_node)
116 except StopIteration:
117 levels.pop()
118 if levels:
119 guide_style = levels[-1].style or null_style
120 levels[-1] = make_guide(FORK, guide_style)
121 guide_style_stack.pop()
122 style_stack.pop()
123 continue
124 push(stack_node)
125 if last:
126 levels[-1] = make_guide(END, levels[-1].style or null_style)
127
128 guide_style = guide_style_stack.current + get_style(node.guide_style)
129 style = style_stack.current + get_style(node.style)
130 prefix = levels[(2 if self.hide_root else 1) :]
131 renderable_lines = console.render_lines(
132 Styled(node.label, style),
133 options.update(
134 width=options.max_width
135 - sum(level.cell_length for level in prefix),
136 highlight=self.highlight,
137 height=None,
138 ),
139 pad=options.justify is not None,
140 )
141
142 if not (depth == 0 and self.hide_root):
143 for first, line in loop_first(renderable_lines):
144 if prefix:
145 yield from _Segment.apply_style(
146 prefix,
147 style.background_style,
148 post_style=remove_guide_styles,
149 )
150 yield from line
151 yield new_line
152 if first and prefix:
153 prefix[-1] = make_guide(
154 SPACE if last else CONTINUE, prefix[-1].style or null_style
155 )
156
157 if node.expanded and node.children:
158 levels[-1] = make_guide(
159 SPACE if last else CONTINUE, levels[-1].style or null_style
160 )
161 levels.append(
162 make_guide(END if len(node.children) == 1 else FORK, guide_style)
163 )
164 style_stack.push(get_style(node.style))
165 guide_style_stack.push(get_style(node.guide_style))
166 push(iter(loop_last(node.children)))
167 depth += 1
168
169 def __rich_measure__(
170 self, console: "Console", options: "ConsoleOptions"
171 ) -> "Measurement":
172 stack: List[Iterator[Tree]] = [iter([self])]
173 pop = stack.pop
174 push = stack.append
175 minimum = 0
176 maximum = 0
177 measure = Measurement.get
178 level = 0
179 while stack:
180 iter_tree = pop()
181 try:
182 tree = next(iter_tree)
183 except StopIteration:
184 level -= 1
185 continue
186 push(iter_tree)
187 min_measure, max_measure = measure(console, options, tree.label)
188 indent = level * 4
189 minimum = max(min_measure + indent, minimum)
190 maximum = max(max_measure + indent, maximum)
191 if tree.expanded and tree.children:
192 push(iter(tree.children))
193 level += 1
194 return Measurement(minimum, maximum)
195
196
197if __name__ == "__main__": # pragma: no cover
198
199 from pip._vendor.rich.console import Group
200 from pip._vendor.rich.markdown import Markdown
201 from pip._vendor.rich.panel import Panel
202 from pip._vendor.rich.syntax import Syntax
203 from pip._vendor.rich.table import Table
204
205 table = Table(row_styles=["", "dim"])
206
207 table.add_column("Released", style="cyan", no_wrap=True)
208 table.add_column("Title", style="magenta")
209 table.add_column("Box Office", justify="right", style="green")
210
211 table.add_row("Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$952,110,690")
212 table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
213 table.add_row("Dec 15, 2017", "Star Wars Ep. V111: The Last Jedi", "$1,332,539,889")
214 table.add_row("Dec 16, 2016", "Rogue One: A Star Wars Story", "$1,332,439,889")
215
216 code = """\
217class Segment(NamedTuple):
218 text: str = ""
219 style: Optional[Style] = None
220 is_control: bool = False
221"""
222 syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
223
224 markdown = Markdown(
225 """\
226### example.md
227> Hello, World!
228>
229> Markdown _all_ the things
230"""
231 )
232
233 root = Tree("🌲 [b green]Rich Tree", highlight=True, hide_root=True)
234
235 node = root.add(":file_folder: Renderables", guide_style="red")
236 simple_node = node.add(":file_folder: [bold yellow]Atomic", guide_style="uu green")
237 simple_node.add(Group("📄 Syntax", syntax))
238 simple_node.add(Group("📄 Markdown", Panel(markdown, border_style="green")))
239
240 containers_node = node.add(
241 ":file_folder: [bold magenta]Containers", guide_style="bold magenta"
242 )
243 containers_node.expanded = True
244 panel = Panel.fit("Just a panel", border_style="red")
245 containers_node.add(Group("📄 Panels", panel))
246
247 containers_node.add(Group("📄 [b magenta]Table", table))
248
249 console = Console()
250
251 console.print(root)