Skip to content

Commit

Permalink
[IMP] Added the ability to print the inverse dependency tree
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandregaldeano committed Dec 9, 2024
1 parent d24b42b commit f66879d
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 13 deletions.
40 changes: 28 additions & 12 deletions src/manifestoo/commands/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@ class Node:
def __init__(self, addon_name: str, addon: Optional[Addon]):
self.addon_name = addon_name
self.addon = addon
self.children = [] # type: List[Node]
self.children = set() # type: Set[Node]
self.parents = set() # type: Set[Node]

def __hash__(self):
return hash(self.addon_name)

@staticmethod
def key(addon_name: str) -> NodeKey:
return addon_name

def print(self, odoo_series: OdooSeries, fold_core_addons: bool) -> None:
def print(
self, odoo_series: OdooSeries, fold_core_addons: bool, inverse: bool
) -> None:
seen: Set[Node] = set()

def _print(indent: List[str], node: Node) -> None:
Expand All @@ -41,22 +47,25 @@ def _print(indent: List[str], node: Node) -> None:
return
typer.secho(f" ({node.sversion(odoo_series)})", dim=True)
seen.add(node)
if not node.children:
sub_nodes = sorted(
# In inverse mode, we iterate over the parents instead of the children
node.parents if inverse else node.children,
key=lambda n: n.addon_name,
)
if not sub_nodes:
return
if fold_core_addons and is_core_addon(node.addon_name, odoo_series):
return
pointers = [TEE] * (len(node.children) - 1) + [LAST]
for pointer, child in zip(
pointers, sorted(node.children, key=lambda n: n.addon_name)
):
pointers = [TEE] * (len(sub_nodes) - 1) + [LAST]
for pointer, sub_node in zip(pointers, sub_nodes):
if indent:
if indent[-1] == TEE:
_print(indent[:-1] + [BRANCH, pointer], child)
_print(indent[:-1] + [BRANCH, pointer], sub_node)
else:
assert indent[-1] == LAST
_print(indent[:-1] + [SPACE, pointer], child)
_print(indent[:-1] + [SPACE, pointer], sub_node)
else:
_print([pointer], child)
_print([pointer], sub_node)

_print([], self)

Expand All @@ -76,6 +85,7 @@ def tree_command(
addons_set: AddonsSet,
odoo_series: OdooSeries,
fold_core_addons: bool,
inverse: bool,
) -> None:
nodes: Dict[NodeKey, Node] = {}

Expand All @@ -92,13 +102,19 @@ def add(addon_name: str) -> Node:
for depend in addon.manifest.depends:
if depend == "base":
continue
node.children.append(add(depend))
child = add(depend)
node.children.add(child)
child.parents.add(node)
return node

root_nodes: List[Node] = []
for addon_name in sorted(addons_selection):
if addon_name == "base":
continue
root_nodes.append(add(addon_name))
if inverse:
# In inverse mode, leaf nodes become root nodes
root_nodes = [node for node in nodes.values() if not node.children]
root_nodes = sorted(root_nodes, key=lambda n: n.addon_name)
for root_node in root_nodes:
root_node.print(odoo_series, fold_core_addons)
root_node.print(odoo_series, fold_core_addons, inverse)
11 changes: 11 additions & 0 deletions src/manifestoo/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,12 +495,22 @@ def tree(
help="Display an interactive tree.",
show_default=False,
),
inverse: bool = typer.Option(
False,
"--inverse",
help="Display the tree in inverse mode. Not available in interactive mode.",
show_default=False,
),
) -> None:
"""Print the dependency tree of selected addons."""
main_options: MainOptions = ctx.obj
ensure_odoo_series(main_options.odoo_series)
assert main_options.odoo_series
if interactive:
if inverse:
raise typer.BadParameter(
"The --inverse option is not available in interactive mode."
)
interactive_tree_command(
main_options.addons_selection,
main_options.addons_set,
Expand All @@ -513,4 +523,5 @@ def tree(
main_options.addons_set,
main_options.odoo_series,
fold_core_addons,
inverse,
)
31 changes: 30 additions & 1 deletion tests/test_tree.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import textwrap
from pathlib import Path

from typer.testing import CliRunner

Expand All @@ -7,7 +8,7 @@
from .common import populate_addons_dir


def test_integration(tmp_path):
def _init_test_addons(tmp_path: Path):
addons = {
"a": {"version": "13.0.1.0.0", "depends": ["b", "c"]},
"b": {"depends": ["base", "mail"]},
Expand All @@ -16,6 +17,10 @@ def test_integration(tmp_path):
"base": {},
}
populate_addons_dir(tmp_path, addons)


def test_integration(tmp_path: Path):
_init_test_addons(tmp_path)
runner = CliRunner(mix_stderr=False)
result = runner.invoke(
app,
Expand All @@ -34,3 +39,27 @@ def test_integration(tmp_path):
└── b ⬆
"""
)


def test_integration_inverse(tmp_path: Path):
_init_test_addons(tmp_path)
runner = CliRunner(mix_stderr=False)
result = runner.invoke(
app,
["--select=a", f"--addons-path={tmp_path}", "tree", "--inverse"],
catch_exceptions=False,
)
assert not result.exception
assert result.exit_code == 0, result.stderr
assert result.stdout == textwrap.dedent(
"""\
account (13.0+c)
└── c (no version)
└── a (13.0.1.0.0)
mail (✘ not installed)
└── b (no version)
├── a (13.0.1.0.0)
└── c (no version)
└── a ⬆
"""
)

0 comments on commit f66879d

Please sign in to comment.