Skip to content

Commit 8fd7561

Browse files
[IMP] Added the ability to print the inverse dependency tree
1 parent d48f7f7 commit 8fd7561

File tree

4 files changed

+71
-13
lines changed

4 files changed

+71
-13
lines changed

news/75.feature

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added the ability to print the inverted dependency tree using the `--inverse-tree` command line option.
2+

src/manifestoo/commands/tree.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,19 @@ class Node:
2020
def __init__(self, addon_name: str, addon: Optional[Addon]):
2121
self.addon_name = addon_name
2222
self.addon = addon
23-
self.children = [] # type: List[Node]
23+
self.children = set() # type: Set[Node]
24+
self.parents = set() # type: Set[Node]
25+
26+
def __hash__(self):
27+
return hash(self.addon_name)
2428

2529
@staticmethod
2630
def key(addon_name: str) -> NodeKey:
2731
return addon_name
2832

29-
def print(self, odoo_series: OdooSeries, fold_core_addons: bool) -> None:
33+
def print(
34+
self, odoo_series: OdooSeries, fold_core_addons: bool, inverse: bool
35+
) -> None:
3036
seen: Set[Node] = set()
3137

3238
def _print(indent: List[str], node: Node) -> None:
@@ -41,22 +47,25 @@ def _print(indent: List[str], node: Node) -> None:
4147
return
4248
typer.secho(f" ({node.sversion(odoo_series)})", dim=True)
4349
seen.add(node)
44-
if not node.children:
50+
sub_nodes = sorted(
51+
# In inverse mode, we iterate over the parents instead of the children
52+
node.parents if inverse else node.children,
53+
key=lambda n: n.addon_name,
54+
)
55+
if not sub_nodes:
4556
return
4657
if fold_core_addons and is_core_addon(node.addon_name, odoo_series):
4758
return
48-
pointers = [TEE] * (len(node.children) - 1) + [LAST]
49-
for pointer, child in zip(
50-
pointers, sorted(node.children, key=lambda n: n.addon_name)
51-
):
59+
pointers = [TEE] * (len(sub_nodes) - 1) + [LAST]
60+
for pointer, sub_node in zip(pointers, sub_nodes):
5261
if indent:
5362
if indent[-1] == TEE:
54-
_print(indent[:-1] + [BRANCH, pointer], child)
63+
_print(indent[:-1] + [BRANCH, pointer], sub_node)
5564
else:
5665
assert indent[-1] == LAST
57-
_print(indent[:-1] + [SPACE, pointer], child)
66+
_print(indent[:-1] + [SPACE, pointer], sub_node)
5867
else:
59-
_print([pointer], child)
68+
_print([pointer], sub_node)
6069

6170
_print([], self)
6271

@@ -76,6 +85,7 @@ def tree_command(
7685
addons_set: AddonsSet,
7786
odoo_series: OdooSeries,
7887
fold_core_addons: bool,
88+
inverse: bool,
7989
) -> None:
8090
nodes: Dict[NodeKey, Node] = {}
8191

@@ -92,13 +102,19 @@ def add(addon_name: str) -> Node:
92102
for depend in addon.manifest.depends:
93103
if depend == "base":
94104
continue
95-
node.children.append(add(depend))
105+
child = add(depend)
106+
node.children.add(child)
107+
child.parents.add(node)
96108
return node
97109

98110
root_nodes: List[Node] = []
99111
for addon_name in sorted(addons_selection):
100112
if addon_name == "base":
101113
continue
102114
root_nodes.append(add(addon_name))
115+
if inverse:
116+
# In inverse mode, leaf nodes become root nodes
117+
root_nodes = [node for node in nodes.values() if not node.children]
118+
root_nodes = sorted(root_nodes, key=lambda n: n.addon_name)
103119
for root_node in root_nodes:
104-
root_node.print(odoo_series, fold_core_addons)
120+
root_node.print(odoo_series, fold_core_addons, inverse)

src/manifestoo/main.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,12 +495,22 @@ def tree(
495495
help="Display an interactive tree.",
496496
show_default=False,
497497
),
498+
inverse: bool = typer.Option(
499+
False,
500+
"--inverse",
501+
help="Display the tree in inverse mode. Not available in interactive mode.",
502+
show_default=False,
503+
),
498504
) -> None:
499505
"""Print the dependency tree of selected addons."""
500506
main_options: MainOptions = ctx.obj
501507
ensure_odoo_series(main_options.odoo_series)
502508
assert main_options.odoo_series
503509
if interactive:
510+
if inverse:
511+
raise typer.BadParameter(
512+
"The --inverse option is not available in interactive mode."
513+
)
504514
interactive_tree_command(
505515
main_options.addons_selection,
506516
main_options.addons_set,
@@ -513,4 +523,5 @@ def tree(
513523
main_options.addons_set,
514524
main_options.odoo_series,
515525
fold_core_addons,
526+
inverse,
516527
)

tests/test_tree.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import textwrap
2+
from pathlib import Path
23

34
from manifestoo.main import app
45

56
from .common import CliRunner, populate_addons_dir
67

78

8-
def test_integration(tmp_path):
9+
def _init_test_addons(tmp_path: Path):
910
addons = {
1011
"a": {"version": "13.0.1.0.0", "depends": ["b", "c"]},
1112
"b": {"depends": ["base", "mail"]},
@@ -14,6 +15,10 @@ def test_integration(tmp_path):
1415
"base": {},
1516
}
1617
populate_addons_dir(tmp_path, addons)
18+
19+
20+
def test_integration(tmp_path: Path):
21+
_init_test_addons(tmp_path)
1722
runner = CliRunner()
1823
result = runner.invoke(
1924
app,
@@ -32,3 +37,27 @@ def test_integration(tmp_path):
3237
└── b ⬆
3338
"""
3439
)
40+
41+
42+
def test_integration_inverse(tmp_path: Path):
43+
_init_test_addons(tmp_path)
44+
runner = CliRunner()
45+
result = runner.invoke(
46+
app,
47+
["--select=a", f"--addons-path={tmp_path}", "tree", "--inverse"],
48+
catch_exceptions=False,
49+
)
50+
assert not result.exception
51+
assert result.exit_code == 0, result.stderr
52+
assert result.stdout == textwrap.dedent(
53+
"""\
54+
account (13.0+c)
55+
└── c (no version)
56+
└── a (13.0.1.0.0)
57+
mail (✘ not installed)
58+
└── b (no version)
59+
├── a (13.0.1.0.0)
60+
└── c (no version)
61+
└── a ⬆
62+
"""
63+
)

0 commit comments

Comments
 (0)