Skip to content

Commit

Permalink
[PACKAGES] Reworks entry to simplify module and ease API consumption
Browse files Browse the repository at this point in the history
  • Loading branch information
HorlogeSkynet committed May 9, 2024
1 parent 3c426c6 commit a163a51
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 91 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,13 @@ Below stand further descriptions for each available (default) option :
// Set it to `false` to allow compatibility with non-Unicode locales.
"use_unicode": true
},
{ "type": "Packages" },
{
"type": "Packages",
// Set to `false` not to join all package tool counts on the same line.
"one_line": true,
// Set to `true` to include tools with no installed package.
"show_zeros": false
},
{
"type": "Temperature",
// The character to display between the temperature value and the unit (as '°' in 53.2°C).
Expand Down
73 changes: 45 additions & 28 deletions archey/entries/packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,33 @@ def get_homebrew_cellar_path() -> str:


PACKAGES_TOOLS = (
{"tool": "apk", "cmd": ("apk", "list", "--installed")},
{"cmd": ("apk", "list", "--installed")},
# As of 2020, `apt` is _very_ slow compared to `dpkg` on Debian-based distributions.
# Additional note : `apt`'s CLI is currently not "stable" in Debian terms.
# If `apt` happens to be preferred over `dpkg` in the future, don't forget to remove the latter.
# {"cmd": ("apt", "list", "-qq", "--installed")},
{"tool": "dnf", "cmd": ("dnf", "list", "installed"), "skew": 1},
{"tool": "dpkg", "cmd": ("dpkg", "--get-selections")},
{"tool": "emerge", "cmd": ("emerge", "-ep", "world"), "skew": 5},
{"tool": "homebrew", "cmd": ("ls", "-1", get_homebrew_cellar_path())}, # Homebrew.
{"tool": "nix-env", "cmd": ("nix-env", "-q")},
{"tool": "pacman", "cmd": ("pacman", "-Q")},
{"tool": "pacstall", "cmd": ("pacstall", "-L")},
{"tool": "pkg_info", "cmd": ("pkg_info", "-a")},
{"cmd": ("dnf", "list", "installed"), "skew": 1},
{"cmd": ("dpkg", "--get-selections")},
{"cmd": ("emerge", "-ep", "world"), "skew": 5},
{"cmd": ("flatpak", "list"), "skew": 1},
{"cmd": ("ls", "-1", get_homebrew_cellar_path()), "name": "homebrew"},
{"cmd": ("nix-env", "-q")},
{"cmd": ("pacman", "-Q")},
{"cmd": ("pacstall", "-L")},
{"cmd": ("pkg_info", "-a")},
{
"tool": "pkg",
"cmd": ("pkg", "-N", "info", "-a"),
# Query `pkg` only on *BSD systems to avoid inconsistencies.
"only_on": (Distributions.FREEBSD, Distributions.NETBSD, Distributions.OPENBSD),
},
{"tool": "pkgin", "cmd": ("pkgin", "list")},
{"tool": "port", "cmd": ("port", "installed"), "skew": 1},
{"tool": "rpm", "cmd": ("rpm", "-qa")},
{"tool": "slackware", "cmd": ("ls", "-1", "/var/log/packages/")}, # SlackWare.
{"tool": "yum", "cmd": ("yum", "list", "installed"), "skew": 2},
{"tool": "zypper", "cmd": ("zypper", "search", "-i"), "skew": 5},
{"tool": "snap", "cmd": ("snap", "list", "--all"), "skew": 1},
{"tool": "flatpak", "cmd": ("flatpak", "list"), "skew": 1},
{"cmd": ("pkgin", "list")},
{"cmd": ("port", "installed"), "skew": 1},
{"cmd": ("rpm", "-qa")},
{"cmd": ("ls", "-1", "/var/log/packages/"), "name": "slackware"},
{"cmd": ("snap", "list", "--all"), "skew": 1},
{"cmd": ("yum", "list", "installed"), "skew": 2},
{"cmd": ("zypper", "search", "-i"), "skew": 5},
)


Expand All @@ -56,6 +56,8 @@ class Packages(Entry):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self.value = {}

for packages_tool in PACKAGES_TOOLS:
packages_tool = typing.cast(dict, packages_tool)
if (
Expand All @@ -78,23 +80,38 @@ def __init__(self, *args, **kwargs):
)
except (OSError, CalledProcessError):
continue


# Here we *may* use `\n` as `universal_newlines` has been set.
count = results.count("\n")
if count == 0:
continue

# If any, deduct output skew present due to the packages tool itself.
if "skew" in packages_tool:
count -= packages_tool["skew"]

pkg_tool_name = packages_tool.get("name", packages_tool["cmd"][0])

# For DPKG only, remove any not purged package.
if packages_tool["tool"] == "dpkg":
if pkg_tool_name == "dpkg":
count -= results.count("deinstall")

# Here we *may* use `\n` as `universal_newlines` has been set.
if self.value:
self.value += ", (" + packages_tool["tool"] + ") " + str(count)
else:
self.value = "(" + packages_tool["tool"] + ") " + str(count)

# Let's just loop over, in case there are multiple package managers.
self.value[pkg_tool_name] = count

def output(self, output) -> None:
"""Adds the entry to `output` after pretty-formatting packages tool counts"""
if not self.value:
# Fall back on the default behavior if no temperatures were detected.
super().output(output)
return

entries = []
for pkg_tool_name, count in self.value.items():
if count > 0 or self.options.get("show_zeros"):
entries.append(f"({pkg_tool_name}) {count}")

if self.options.get("one_line", True):
# One-line output is enabled : Join the results !
output.append(self.name, ", ".join(entries))
else:
# One-line output has been disabled, add one entry per item.
for entry in entries:
output.append(self.name, entry)
139 changes: 78 additions & 61 deletions archey/test/entries/test_archey_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import unittest
from unittest.mock import DEFAULT as DEFAULT_SENTINEL
from unittest.mock import MagicMock, patch
from unittest.mock import MagicMock, call, patch

from archey.configuration import DEFAULT_CONFIG
from archey.distributions import Distributions
Expand Down Expand Up @@ -41,7 +41,7 @@ def test_match_with_apk(self, check_output_mock):
"""Simple test for the APK packages manager"""
check_output_mock.side_effect = self._check_output_side_effect("apk")

self.assertEqual(Packages().value, '(apk) 8')
self.assertDictEqual(Packages().value, {"apk": 8})

@patch(
"archey.entries.packages.check_output",
Expand All @@ -57,7 +57,7 @@ def test_match_with_dnf(self, check_output_mock):
"""Simple test for the DNF packages manager"""
check_output_mock.side_effect = self._check_output_side_effect("dnf")

self.assertEqual(Packages().value, '(dnf) 4')
self.assertDictEqual(Packages().value, {"dnf": 4})

@patch(
"archey.entries.packages.check_output",
Expand All @@ -75,7 +75,7 @@ def test_match_with_dpkg(self, check_output_mock):
"""Simple test for the DPKG packages manager"""
check_output_mock.side_effect = self._check_output_side_effect("dpkg")

self.assertEqual(Packages().value, '(dpkg) 6')
self.assertDictEqual(Packages().value, {"dpkg": 6})

@patch(
"archey.entries.packages.check_output",
Expand All @@ -97,7 +97,23 @@ def test_match_with_emerge(self, check_output_mock):
"""Simple test for the Emerge packages manager"""
check_output_mock.side_effect = self._check_output_side_effect("emerge")

self.assertEqual(Packages().value, '(emerge) 5')
self.assertDictEqual(Packages().value, {"emerge": 5})

@patch(
"archey.entries.packages.check_output",
return_value="""\
Name Application ID Version Branch Origin Installation
Discord com.discordapp.Discord 0.0.35 stable flathub system
Xournal++ com.github.xournalpp.xournalpp 1.2.2 stable flathub system
draw.io com.jgraph.drawio.desktop 22.0.2 stable flathub system
Extension Manager com.mattjakeman.ExtensionManager 0.4.2 stable flathub system
""",
)
def test_match_with_flatpak(self, check_output_mock):
"""Simple test for the Flatpak packages manager"""
check_output_mock.side_effect = self._check_output_side_effect("flatpak")

self.assertDictEqual(Packages().value, {"flatpak": 4})

@patch(
"archey.entries.packages.check_output",
Expand All @@ -112,7 +128,7 @@ def test_match_with_nix_env(self, check_output_mock):
"""Simple test for the Emerge packages manager"""
check_output_mock.side_effect = self._check_output_side_effect("nix-env")

self.assertEqual(Packages().value, '(nix-env) 4')
self.assertDictEqual(Packages().value, {"nix-env": 4})

@patch(
"archey.entries.packages.check_output",
Expand All @@ -127,7 +143,7 @@ def test_match_with_pacman(self, check_output_mock):
"""Simple test for the Pacman packages manager"""
check_output_mock.side_effect = self._check_output_side_effect("pacman")

self.assertEqual(Packages().value, '(pacman) 4')
self.assertDictEqual(Packages().value, {"pacman": 4})

@patch(
"archey.entries.packages.check_output",
Expand All @@ -147,7 +163,7 @@ def test_match_with_pkg_info(self, check_output_mock):
"""Simple test for the OpenBSD `pkg_*` package manager"""
check_output_mock.side_effect = self._check_output_side_effect("pkg_info")

self.assertEqual(Packages().value, '(pkg_info) 9')
self.assertDictEqual(Packages().value, {"pkg_info": 9})

@patch("archey.entries.packages.Distributions.get_local", return_value=Distributions.FREEBSD)
@patch(
Expand All @@ -167,7 +183,7 @@ def test_match_with_pkg(self, check_output_mock, _):
"""Simple test for the FreeBSD `pkg` package manager"""
check_output_mock.side_effect = self._check_output_side_effect("pkg")

self.assertEqual(Packages().value, '(pkg) 8')
self.assertDictEqual(Packages().value, {"pkg": 8})

@patch(
"archey.entries.packages.check_output",
Expand All @@ -191,7 +207,7 @@ def test_match_with_pkgin(self, check_output_mock):
"""Simple test for the (NetBSD) `pkgin` package manager"""
check_output_mock.side_effect = self._check_output_side_effect("pkgin")

self.assertEqual(Packages().value, '(pkgin) 13')
self.assertDictEqual(Packages().value, {"pkgin": 13})

@patch(
"archey.entries.packages.check_output",
Expand All @@ -217,7 +233,7 @@ def test_match_with_macports(self, check_output_mock):
"""Simple test for the MacPorts CLI client (`port`) package manager"""
check_output_mock.side_effect = self._check_output_side_effect("port")

self.assertEqual(Packages().value, '(port) 14')
self.assertDictEqual(Packages().value, {"port": 14})

@patch(
"archey.entries.packages.check_output",
Expand All @@ -232,7 +248,7 @@ def test_match_with_rpm(self, check_output_mock):
"""Simple test for the RPM packages manager"""
check_output_mock.side_effect = self._check_output_side_effect("rpm")

self.assertEqual(Packages().value, '(rpm) 4')
self.assertDictEqual(Packages().value, {"rpm": 4})

@patch(
"archey.entries.packages.check_output",
Expand All @@ -249,7 +265,7 @@ def test_match_with_yum(self, check_output_mock):
"""Simple test for the Yum packages manager"""
check_output_mock.side_effect = self._check_output_side_effect("yum")

self.assertEqual(Packages().value, '(yum) 4')
self.assertDictEqual(Packages().value, {"yum": 4})

@patch(
"archey.entries.packages.check_output",
Expand All @@ -270,46 +286,13 @@ def test_match_with_zypper(self, check_output_mock):
"""Simple test for the Zypper packages manager"""
check_output_mock.side_effect = self._check_output_side_effect("zypper")

self.assertEqual(Packages().value, '(zypper) 5')

@patch(
"archey.entries.packages.check_output",
return_value="""\
Name Version Rev Tracking Publisher Notes
gnome-3-38-2004 0+git.efb213a 143 latest/stable/… canonical✓ -
gnome-42-2204 0+git.510a601 176 latest/stable canonical✓ -
gtk-common-themes 0.1-81-g442e511 1535 latest/stable/… canonical✓ -
snap-store 41.3-66-gfe1e325 638 latest/stable/… canonical✓ -
""",
)
def test_match_with_snap(self, check_output_mock):
"""Simple test for the Snap packages manager"""
check_output_mock.side_effect = self._check_output_side_effect("snap")

self.assertEqual(Packages().value, '(snap) 4')


@patch(
"archey.entries.packages.check_output",
return_value="""\
Name Application ID Version Branch Origin Installation
Discord com.discordapp.Discord 0.0.35 stable flathub system
Xournal++ com.github.xournalpp.xournalpp 1.2.2 stable flathub system
draw.io com.jgraph.drawio.desktop 22.0.2 stable flathub system
Extension Manager com.mattjakeman.ExtensionManager 0.4.2 stable flathub system
""",
)
def test_match_with_flatpak(self, check_output_mock):
"""Simple test for the Flatpak packages manager"""
check_output_mock.side_effect = self._check_output_side_effect("flatpak")

self.assertEqual(Packages().value, '(flatpak) 4')
self.assertDictEqual(Packages().value, {"zypper": 5})

@patch(
"archey.entries.packages.PACKAGES_TOOLS",
new=(
{"tool": "pkg_tool_1", "cmd": ("pkg_tool_1")},
{"tool": "pkg_tool_2", "cmd": ("pkg_tool_2"), "skew": 2},
{"cmd": ("pkg_tool_1",), "name": "acae_loot_42"},
{"cmd": ("pkg_tool_2",), "skew": 2},
),
)
@patch(
Expand All @@ -329,23 +312,57 @@ def test_match_with_flatpak(self, check_output_mock):
)
def test_multiple_package_managers(self, _):
"""Simple test for multiple packages managers"""
self.assertEqual(Packages().value, '(pkg_tool_1) 2, (pkg_tool_2) 2')
self.assertDictEqual(Packages().value, {"acae_loot_42": 2, "pkg_tool_2": 2})

@patch("archey.entries.packages.check_output")
@HelperMethods.patch_clean_configuration
def test_no_packages_manager(self, check_output_mock):
"""No packages manager is available at the moment..."""
check_output_mock.side_effect = self._check_output_side_effect()
def test_various_output_configuration(self):
"""Test `output` overloading based on user preferences combination"""
packages_instance_mock = HelperMethods.entry_mock(Packages)
output_mock = MagicMock()

packages = Packages()
packages_instance_mock.value = {"pkg_tool_0": 0, "pkg_tool_18": 18, "pkg_tool_42": 42}

output_mock = MagicMock()
packages.output(output_mock)
with self.subTest("Single-line combined output (without zero counts)."):
Packages.output(packages_instance_mock, output_mock)
output_mock.append.assert_called_once_with(
"Packages", "(pkg_tool_18) 18, (pkg_tool_42) 42"
)

output_mock.reset_mock()

with self.subTest("Single-line combined output (with zero counts)."):
packages_instance_mock.options["show_zeros"] = True

Packages.output(packages_instance_mock, output_mock)
output_mock.append.assert_called_once_with(
"Packages", "(pkg_tool_0) 0, (pkg_tool_18) 18, (pkg_tool_42) 42"
)

output_mock.reset_mock()

with self.subTest("Multi-lines output (with zero counts)."):
packages_instance_mock.options["one_line"] = False
packages_instance_mock.options["show_zeros"] = True

Packages.output(packages_instance_mock, output_mock)
self.assertEqual(output_mock.append.call_count, 3)
output_mock.append.assert_has_calls(
[
call("Packages", "(pkg_tool_0) 0"),
call("Packages", "(pkg_tool_18) 18"),
call("Packages", "(pkg_tool_42) 42"),
]
)

output_mock.reset_mock()

with self.subTest("No available packages tool."):
packages_instance_mock.value = {}

self.assertIsNone(packages.value)
self.assertEqual(
output_mock.append.call_args[0][1], DEFAULT_CONFIG["default_strings"]["not_detected"]
)
Packages.output(packages_instance_mock, output_mock)
output_mock.append.assert_called_once_with(
"Packages", DEFAULT_CONFIG["default_strings"]["not_detected"]
)

@staticmethod
def _check_output_side_effect(pkg_manager_cmd=None):
Expand Down
6 changes: 5 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@
"type": "Terminal",
"use_unicode": true
},
{ "type": "Packages" },
{
"type": "Packages",
"one_line": true,
"show_zeros": false
},
{
"type": "Temperature",
"char_before_unit": " ",
Expand Down

0 comments on commit a163a51

Please sign in to comment.