|
8 | 8 | from bigtree.tree import search |
9 | 9 | from bigtree.utils import assertions, constants, exceptions |
10 | 10 |
|
| 11 | +try: |
| 12 | + import rich |
| 13 | +except ImportError: # pragma: no cover |
| 14 | + from unittest.mock import MagicMock |
| 15 | + |
| 16 | + rich = MagicMock() |
| 17 | + |
| 18 | + |
11 | 19 | __all__ = [ |
12 | 20 | "add_path_to_tree", |
13 | 21 | "str_to_tree", |
14 | 22 | "newick_to_tree", |
| 23 | + "rich_to_tree", |
15 | 24 | ] |
16 | 25 |
|
17 | 26 | T = TypeVar("T", bound=node.Node) |
@@ -469,3 +478,61 @@ def _raise_value_error(tree_idx: int) -> None: |
469 | 478 | current_depth, |
470 | 479 | ) |
471 | 480 | return current_node |
| 481 | + |
| 482 | + |
| 483 | +@exceptions.optional_dependencies_rich |
| 484 | +def rich_to_tree( |
| 485 | + rich_tree: rich.tree.Tree, node_format_attr: str = "style" |
| 486 | +) -> node.Node: |
| 487 | + """Construct tree from rich tree to allow more tree operations, return root of tree. |
| 488 | +
|
| 489 | + If the rich node label is of type rich.text.Text, it will save the style, if any, as |
| 490 | + a node attribute. This does not support inferring rich node label string using |
| 491 | + square brackets. |
| 492 | +
|
| 493 | + Examples: |
| 494 | + >>> from bigtree import Tree |
| 495 | + >>> from rich.tree import Tree as RichTree |
| 496 | + >>> from rich.text import Text |
| 497 | + >>> rich_root = RichTree(Text("Grandparent", style="magenta")) |
| 498 | + >>> child = rich_root.add("Child") |
| 499 | + >>> _ = child.add(Text("Grandchild", style="red")) |
| 500 | + >>> _ = rich_root.add("Child 2") |
| 501 | + >>> tree = Tree.rich_to_tree(rich_root) |
| 502 | + >>> tree.show(all_attrs=True) # Try with rich=True, node_format_attr="style" |
| 503 | + Grandparent [style=magenta] |
| 504 | + ├── Child |
| 505 | + │ └── Grandchild [style=red] |
| 506 | + └── Child 2 |
| 507 | +
|
| 508 | + Args: |
| 509 | + rich_tree: rich Tree |
| 510 | + node_format_attr: attribute name for the node format |
| 511 | +
|
| 512 | + Returns: |
| 513 | + Node |
| 514 | + """ |
| 515 | + |
| 516 | + def extract_label_and_style(label: rich.text.Text | str) -> tuple[str, str | None]: |
| 517 | + """Extract label and list of styles spans from a rich label. |
| 518 | +
|
| 519 | + Returns: |
| 520 | + label and style (if label is rich.text.Text and has style) |
| 521 | + """ |
| 522 | + if isinstance(label, rich.text.Text): |
| 523 | + return label.plain, label.style |
| 524 | + else: |
| 525 | + return str(label), None |
| 526 | + |
| 527 | + def convert_node( |
| 528 | + rich_node: rich.tree.Tree, parent: node.Node | None = None |
| 529 | + ) -> node.Node: |
| 530 | + node_name, node_format = extract_label_and_style(rich_node.label) |
| 531 | + _node = node.Node(node_name, parent=parent) |
| 532 | + if node_format: |
| 533 | + _node.set_attrs({node_format_attr: node_format}) |
| 534 | + for child in rich_node.children: |
| 535 | + convert_node(child, parent=_node) |
| 536 | + return _node |
| 537 | + |
| 538 | + return convert_node(rich_tree) |
0 commit comments