Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMP] Added the ability to print the inverse dependency tree #75

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ⬆
"""
)