%PDF- %PDF-
| Direktori : /lib/python3/dist-packages/nala/ |
| Current File : //lib/python3/dist-packages/nala/summary.py |
# __
# ____ _____ | | _____
# / \\__ \ | | \__ \
# | | \/ __ \| |__/ __ \_
# |___| (____ /____(____ /
# \/ \/ \/
#
# Copyright (C) 2021, 2022 Blake Lee
#
# This file is part of nala
#
# nala is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# nala is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with nala. If not, see <https://www.gnu.org/licenses/>.
"""Module for printing the transaction summary."""
from __future__ import annotations
from dataclasses import dataclass
from itertools import zip_longest
from typing import Generator, Iterable
from nala import _, color, console
from nala.cache import Cache
from nala.options import arguments
from nala.rich import HORIZONTALS, OVERFLOW, Column, Group, Table, Text, Tree, from_ansi
from nala.utils import NalaPackage, PackageHandler, dprint, unit_str
# NOTE: The following are the headers for the transaction summary.
# NOTE: Package: Version: Size:
# NOTE: ansible-core 2.12.4-1 1.2 MB
PACKAGE, VERSION, SIZE, OLD_VERSION, NEW_VERSION, EITHER = _(
"Package/Version/Size/Old Version/New Version/Either"
).split("/")
# NOTE: Verb Tenses are [ "Present/Present Participle/Past" ]
# NOTE: This ends up looking like [ "Auto-Purge 20 Packages" ]
_AUTO_PURGE, _AUTO_PURGING, _AUTO_PURGED = _(
"Auto-Purge/Auto-Purging/Auto-Purged",
).split("/")
# NOTE: Verb Tenses are [ "Present/Present Participle/Past" ]
# NOTE: This ends up looking like [ "Auto-Remove 20 Packages" ]
_AUTO_REMOVE, _AUTO_REMOVING, _AUTO_REMOVED = _(
"Auto-Remove/Auto-Removing/Auto-Removed",
).split("/")
# NOTE: Verb Tenses are [ "Present/Present Participle/Past" ]
# NOTE: This ends up looking like [ "Remove 20 Packages" ]
_REMOVE, _REMOVING, _REMOVED = _(
"Remove/Removing/Removed",
).split("/")
# NOTE: Verb Tenses are [ "Present/Present Participle/Past" ]
# NOTE: This ends up looking like [ "Purge 20 Packages" ]
_PURGE, _PURGING, _PURGED = _(
"Purge/Purging/Purged",
).split("/")
# NOTE: Verb Tenses are [ "Present/Present Participle/Past" ]
# NOTE: This ends up looking like [ "Install 20 Packages" ]
_INSTALL, _INSTALLING, _INSTALLED = _(
"Install/Installing/Installed",
).split("/")
# NOTE: Verb Tenses are [ "Present/Present Participle/Past" ]
# NOTE: This ends up looking like [ "Reinstall 20 Packages" ]
_REINSTALL, _REINSTALLING, _REINSTALLED = _(
"Reinstall/Reinstalling/Reinstalled",
).split("/")
# NOTE: Verb Tenses are [ "Present/Present Participle/Past" ]
# NOTE: This ends up looking like [ "Upgrade 20 Packages" ]
_UPGRADE, _UPGRADING, _UPGRADED = _(
"Upgrade/Upgrading/Upgraded",
).split("/")
# NOTE: Verb Tenses are [ "Present/Present Participle/Past" ]
# NOTE: This ends up looking like [ "Downgrade 20 Packages" ]
_DOWNGRADE, _DOWNGRADING, _DOWNGRADED = _(
"Downgrade/Downgrading/Downgraded",
).split("/")
# NOTE: Verb Tenses are [ "Present/Present Participle/Past" ]
# NOTE: This ends up looking like [ "Configure 20 Packages" ]
_CONFIGURE, _CONFIGURING, _CONFIGURED = _(
"Configure/Configuring/Configured",
).split("/")
SUMMARY_LAYOUT = ("left_adjust", "right_adjust", "left_adjust")
UPGRADE_LAYOUT = ("pkg_blue", "old_version", "new_version", "pkg_size")
DOWNGRADE_LAYOUT = ("pkg_yellow", "old_version", "new_version", "pkg_size")
DEFAULT_LAYOUT = ("pkg_green", "version", "pkg_size")
EXTRA_LAYOUT = ("pkg_magenta", "version", "pkg_size")
REMOVE_LAYOUT = ("pkg_red", "version", "pkg_size")
COLUMN_MAP: dict[str, dict[str, str | int]] = {
"left_adjust": {"overflow": OVERFLOW},
"right_adjust": {"justify": "right", "overflow": OVERFLOW},
"pkg_green": {
"header": f"{PACKAGE}:",
"style": "bold green",
"overflow": OVERFLOW,
"ratio": 2,
},
"pkg_red": {
"header": f"{PACKAGE}:",
"style": "bold red",
"overflow": OVERFLOW,
"ratio": 2,
},
"pkg_blue": {
"header": f"{PACKAGE}:",
"style": "bold blue",
"overflow": OVERFLOW,
"ratio": 3,
},
"pkg_yellow": {
"header": f"{PACKAGE}:",
"style": "bold orange_red1",
"overflow": OVERFLOW,
"ratio": 2,
},
"pkg_magenta": {
"header": f"{PACKAGE}:",
"style": "bold magenta",
"overflow": OVERFLOW,
"ratio": 2,
},
"pkg_size": {
"header": f"{SIZE}:",
"justify": "right",
"overflow": OVERFLOW,
"ratio": 2,
},
"version": {"header": f"{VERSION}:", "overflow": "fold", "ratio": 2},
"old_version": {"header": f"{OLD_VERSION}:", "overflow": "fold", "ratio": 2},
"new_version": {"header": f"{NEW_VERSION}:", "overflow": "fold", "ratio": 2},
}
ROW_MAP: dict[str, str] = {
"pkg_green": "name",
"pkg_red": "name",
"pkg_blue": "name",
"pkg_yellow": "name",
"pkg_magenta": "name",
"pkg_size": "unit_size",
"version": "version",
"old_version": "old_version",
"new_version": "version",
}
def get_columns(column_keys: Iterable[str]) -> Generator[Column, None, None]:
"""Get the columns from our column map."""
for key in column_keys:
yield Column(**COLUMN_MAP[key]) # type: ignore[arg-type]
def get_rows(pkg: NalaPackage, layout: Iterable[str]) -> Generator[Text, None, None]:
"""Get the rows from our row map."""
for key in layout:
if key == "new_version":
yield from_ansi(version_diff(pkg))
continue
yield from_ansi(getattr(pkg, ROW_MAP[key]))
def version_diff(pkg: NalaPackage) -> str:
"""Return a colored diff of the new version."""
if pkg.old_version:
for i, char in enumerate(zip_longest(pkg.old_version, pkg.version)):
if char[0] != char[1]:
return f"{pkg.version[:i]}{color(pkg.version[i:], 'YELLOW')}"
return pkg.version
@dataclass
class PackageHeaders:
"""Tuple for package headers."""
layout: tuple[str, ...]
title: str
summary: str = ""
@dataclass
class Headers: # pylint: disable=too-many-instance-attributes
"""Tuple for headers."""
deleting: PackageHeaders
auto_removing: PackageHeaders
installing: PackageHeaders
reinstalling: PackageHeaders
upgrading: PackageHeaders
downgrading: PackageHeaders
configuring: PackageHeaders
recommending: PackageHeaders
suggesting: PackageHeaders
not_needed: PackageHeaders | None = None
def auto_remove_header(history: bool) -> tuple[str, str]:
"""Get the auto remove header."""
if history and arguments.is_purge():
# NOTE: `/` will convert to a space for example in Irish it may be:
# NOTE: "{package_total}/bpacáistí/a uasghrádú" -> "20 bpacáistí a uasghrádú"
return _AUTO_PURGED, _AUTO_PURGED
if history:
return _AUTO_REMOVED, _AUTO_REMOVED
if arguments.is_purge():
return _AUTO_PURGING, _AUTO_PURGE
return _AUTO_REMOVING, _AUTO_REMOVE
def remove_header(history: bool) -> tuple[str, str]:
"""Get the remove header."""
if history and arguments.is_purge():
return _PURGED, _PURGED
if history:
return _REMOVED, _REMOVED
if arguments.is_purge():
return _PURGING, _PURGE
return _REMOVING, _REMOVE
def get_headers() -> Headers:
"""Get the headers for the summary table."""
return Headers(
PackageHeaders(REMOVE_LAYOUT, *remove_header(history=False)),
PackageHeaders(REMOVE_LAYOUT, *auto_remove_header(history=False)),
PackageHeaders(DEFAULT_LAYOUT, _INSTALLING, _INSTALL),
PackageHeaders(DEFAULT_LAYOUT, _REINSTALLING, _REINSTALL),
PackageHeaders(UPGRADE_LAYOUT, _UPGRADING, _UPGRADE),
PackageHeaders(DOWNGRADE_LAYOUT, _DOWNGRADING, _DOWNGRADE),
PackageHeaders(EXTRA_LAYOUT, _CONFIGURING, _CONFIGURE),
PackageHeaders(EXTRA_LAYOUT, _("Recommended, Will Not Be Installed")),
PackageHeaders(EXTRA_LAYOUT, _("Suggested, Will Not Be Installed")),
PackageHeaders(REMOVE_LAYOUT, _("Auto-Removable, Will Not Be Removed")),
)
def get_history_headers() -> Headers:
"""Get the headers for the summary table."""
return Headers(
PackageHeaders(REMOVE_LAYOUT, *remove_header(history=True)),
PackageHeaders(REMOVE_LAYOUT, *auto_remove_header(history=True)),
PackageHeaders(DEFAULT_LAYOUT, _INSTALLED, _INSTALLED),
PackageHeaders(DEFAULT_LAYOUT, _REINSTALLED, _REINSTALLED),
PackageHeaders(UPGRADE_LAYOUT, _UPGRADED, _UPGRADED),
PackageHeaders(DOWNGRADE_LAYOUT, _DOWNGRADED, _DOWNGRADED),
PackageHeaders(EXTRA_LAYOUT, _CONFIGURED, _CONFIGURED),
PackageHeaders(EXTRA_LAYOUT, _("Recommended, Will Not Be Installed")),
PackageHeaders(EXTRA_LAYOUT, _("Suggested, Will Not Be Installed")),
)
def gen_printers(
nala_pkgs: PackageHandler, headers: Headers
) -> Generator[tuple[list[NalaPackage], PackageHeaders], None, None]:
"""Generate the printers."""
yield from ( # type: ignore[misc]
(nala_pkgs.not_needed, headers.not_needed),
(nala_pkgs.delete_pkgs + nala_pkgs.delete_config, headers.deleting),
(
nala_pkgs.autoremove_pkgs + nala_pkgs.autoremove_config,
headers.auto_removing,
),
(nala_pkgs.install_pkgs, headers.installing),
(nala_pkgs.reinstall_pkgs, headers.reinstalling),
(nala_pkgs.upgrade_pkgs, headers.upgrading),
(nala_pkgs.downgrade_pkgs, headers.downgrading),
(nala_pkgs.configure_pkgs, headers.configuring),
(nala_pkgs.recommend_pkgs, headers.recommending),
(nala_pkgs.suggest_pkgs, headers.suggesting),
)
def gen_package_table(
nala_packages: list[NalaPackage] | list[NalaPackage | list[NalaPackage]],
pkg_headers: PackageHeaders,
) -> Table:
"""Print package transactions in a pretty format."""
package_table = Table(
*get_columns(pkg_headers.layout), padding=(0, 1), box=None, expand=True
)
# Add our packages
or_deps: list[list[NalaPackage]] = []
for pkg in nala_packages:
if isinstance(pkg, list):
or_deps.append(pkg)
continue
if pkg.old_version:
package_table.add_row(*get_rows(pkg, pkg_headers.layout))
continue
package_table.add_row(*get_rows(pkg, pkg_headers.layout))
# Add any or_deps
for pkg in or_deps:
package_table.add_row(*summary_or_depends(pkg))
return package_table
def summary_or_depends(pkg: list[NalaPackage]) -> tuple[Tree, Group, Group]:
"""Format Recommend and Suggests or dependencies."""
pkg_tree = Tree(f"[default]{EITHER}[/default]", guide_style="default")
_children = [pkg_tree.add(npkg.name) for npkg in pkg]
return (
pkg_tree,
Group("", *(npkg.version for npkg in pkg)),
Group("", *(npkg.unit_size for npkg in pkg)),
)
def print_update_summary(nala_pkgs: PackageHandler, cache: Cache | None = None) -> None:
"""Print our transaction summary."""
dprint("Printing Update Summary")
headers = get_headers() if cache else get_history_headers()
main_table = Table.grid(expand=True)
summary_header = Table(_("Summary"), padding=0, box=HORIZONTALS, expand=True)
summary_table = Table.grid(*get_columns(SUMMARY_LAYOUT), padding=(0, 1))
for pkg_set, header in gen_printers(nala_pkgs, headers):
if not pkg_set:
continue
package_table = Table(header.title, padding=0, box=HORIZONTALS, expand=True)
package_table.add_row(gen_package_table(pkg_set, header))
main_table.add_row(package_table)
# We don't need empty rows from these in the summary
if nala_pkgs.no_summary(pkg_set):
continue
# NOTE: This ends up looking like [ "Configure 20 Packages" ]
summary_table.add_row(header.summary, f"{len(pkg_set)}", _("Packages"))
summary_header.add_row(summary_table)
main_table.add_row(summary_header)
console.print(main_table)
if cache:
footer_table = Table(
*get_columns(SUMMARY_LAYOUT), box=None, show_footer=True, show_header=False
)
if (download := cache.required_download) > 0:
footer_table.add_row(_("Total download size"), unit_str(download))
if (space := cache.required_space) < 0:
footer_table.add_row(_("Disk space to free"), unit_str(-space))
if space > 0:
footer_table.add_row(_("Disk space required"), unit_str(space))
console.print(footer_table)
if cache and arguments.download_only:
print(_("Nala will only download the packages"))