meta_package_manager.bar_plugin module

SwiftBar and Xbar plugin for Meta Package Manager (the mpm CLI).

Set the update cycle to several hours. One notice a day is enough, and each check uses many resources. A short cycle can also use up the GitHub API quota that Homebrew needs.

Both hosts give the plugin options to the script as environment variables:

meta_package_manager.bar_plugin.SWIFTBAR_MIN_VERSION = (2, 1, 0)

SwiftBar 2.1.0 fixes a bug with multiple parameters in a font string.

The fix first went out in a test build labelled 2.1.2. No release used that number: the public releases numbered it 2.1.0. A floor at 2.1.2 would exclude every SwiftBar release.

See swiftbar/SwiftBar#445.

meta_package_manager.bar_plugin.MPM_MIN_VERSION = (5, 0, 0)

mpm 5.0.0 is the first version that renders the complete menu layout.

meta_package_manager.bar_plugin.SHELL_ENV_MIN_VERSION = (8, 0, 0)

First mpm release accepting --shell-env, which the plugin passes to it.

Both hosts start this plugin outside a terminal. SwiftBar runs it through $SHELL -l -c, a login shell that reads .zprofile and skips .zshrc, and Xbar runs it with no shell at all, on the PATH of the desktop session. The option makes mpm adopt the environment of an interactive login shell before it looks for managers, so the menu lists what a terminal would. An older mpm gets no such option: it would reject it as unknown, which is worse than the narrow PATH.

The GNOME Shell extension carries the same gate, and tests/test_gnome_extension.py holds the two in sync.

meta_package_manager.bar_plugin.INSTALL_ARGV = ('uv', 'tool', 'install', '--upgrade', 'meta-package-manager')

Install command offered when no runnable mpm is found.

A global uv tool install is the first method on the installation page. It puts mpm on the PATH of every shell. A pip install puts it in one interpreter, which may not be the one that runs this plugin. --upgrade also covers an mpm that is too old, so the command needs no version specifier: the latest release always satisfies MPM_MIN_VERSION. Xbar also breaks a quoted >= specifier (matryer/xbar#831).

The GNOME Shell extension offers the same command in its own menu for a missing mpm. tests/test_gnome_extension.py checks that the two are equal.

meta_package_manager.bar_plugin.INSTALL_DOCS_URL = 'https://mpm.run/install/'

Installation page, offered beside INSTALL_ARGV.

uv may be missing too, and it is not the only method: a distribution package, Homebrew or a standalone binary also install mpm.

meta_package_manager.bar_plugin.PLUGIN_DOCS_URL = 'https://mpm.run/bar-plugin/'

Documentation of this plugin, linked from the About submenu.

This is the same address as the one in the <xbar.abouturl> header. The host shows that header in its plugin browser only, and never in the menu.

meta_package_manager.bar_plugin.MPM_TIMEOUT = 60

Maximum duration in seconds for one mpm call.

Passed as --timeout to every mpm call. The mpm defaults are set for interactive use and are too long for a background menu bar refresh: 120 seconds for a read-only query, and 500 seconds for an operation that changes state, like sync. With this limit, a hung package manager fails the refresh after one minute, and it does not block the menu bar for several minutes.

meta_package_manager.bar_plugin.VERSION_REGEX = re.compile('\n    .+                      # Any string\n    \\                       # A space\n    version                 # The "version" string\n    \\                       # A space\n    [^\\.]*?           , re.MULTILINE|re.VERBOSE)

Both readings of the version that mpm --version prints, with or without ANSI colors.

version is the numeric part, and release is the whole token. A development build reports 8.0.0.dev0+40ce0879, and its suffix identifies the build.

class meta_package_manager.bar_plugin.Candidate(args: tuple[str, ...], runnable: bool = False, up_to_date: bool = False, version: tuple[int, ...] | None = None, error: str | Exception | None = None, release: str | None = None)[source]

Bases: NamedTuple

One way to run mpm found on the system, and the result of its version probe.

version is the tuple that the code compares with MPM_MIN_VERSION, and release is the token as printed. The probeMpm() function of the GNOME Shell extension returns the same fields.

Create new instance of Candidate(args, runnable, up_to_date, version, error, release)

args: tuple[str, ...]

Alias for field number 0

runnable: bool

Alias for field number 1

up_to_date: bool

Alias for field number 2

version: tuple[int, ...] | None

Alias for field number 3

error: str | Exception | None

Alias for field number 4

release: str | None

Alias for field number 5

property rank: tuple[bool, bool, tuple[int, ...]]

Sort key of MPMPlugin.ranked_mpm: runnable first, then up to date, then the highest version.

error and release are not part of the key. Python cannot compare two exceptions, and it cannot compare a missing version with a found one. Either case would make the sort raise an error.

class meta_package_manager.bar_plugin.MPMPlugin[source]

Bases: object

Finds the mpm CLI on the system and calls it.

mpm produces the main output of the plugin.

The output must follow both the Xbar dialect and the SwiftBar dialect.

static getenv_str(var, default=None)[source]

Read an environment variable.

All environment variables are strings. This method always returns a lowercase string.

Return type:

str | None

static getenv_bool(var, default=False)[source]

Read an environment variable as a boolean.

Uses configparser.RawConfigParser.BOOLEAN_STATES to convert the string.

Return type:

bool

static getenv_int(var, default)[source]

Read an environment variable as an integer.

Returns the default when the value is not a number. A typo in a plugin setting then changes the layout, and the menu still works.

Return type:

int

static normalize_params(font_string, valid_ids=None)[source]

Parse a string of parameters and return a normalized string.

The input is a space-separated list of parameters. Each parameter is a key/value pair separated by an equal sign.

The method keeps the parameters named in valid_ids and ignores the others. By default it keeps color, font and size.

One parameter can occur more than once. The method keeps the last value.

Both hosts document the available parameters:

Return type:

str

static str_to_version(version_string)[source]

Convert a version string into a tuple of integers.

Return type:

tuple[int, ...]

static version_to_str(version_tuple)[source]

Convert a tuple of integers into a version string.

Return type:

str

property align_columns: bool[source]

Center each version pair around its arrow, and align the package names.

The rows use a fixed-width font, so every padding space has the same width. See align_rows() for the reason to align on the arrow.

property plugin_version: str[source]

Version that this script reports to its host.

Read from the <xbar.version> header. That header is the only place where the number is written: both hosts parse it from the source, and bump-my-version updates it at each release. A constant beside it would be a second copy, and the two could become different.

property mpm_options: tuple[str, ...][source]

Options added to every mpm call.

They go after the options of the plugin and before the subcommand. A single-value option that occurs twice, like --verbosity, then takes the value from the user.

The version probe does not use them. A mistyped option then makes sync fail with mpm’s own usage error, which the menu shows. Without this, the same typo would look like a missing mpm. The upgrade actions of the menu use these options too: BarPluginRenderer builds them inside the outdated call and inherits this environment.

The value comes from VAR_MPM_OPTIONS, read without getenv_str() because that method lowercases it. A --verbosity INFO level and a path in --config must keep their case. The GNOME Shell extension’s mpm-options setting is the equivalent option.

property always_visible: bool[source]

Keep the menu bar icon when there is nothing to report.

SwiftBar hides a plugin that produces no output. To make the icon disappear, the plugin renders nothing. The plugin must then separate an empty output that the user asked for from the empty output of a failed mpm call. For this reason, print_menu() accepts an empty output only when this option is set.

Xbar does not hide a plugin, so the variable is declared for SwiftBar only.

The value comes from the VAR_ALWAYS_VISIBLE environment variable. The name matches the always-visible setting of the GNOME Shell extension.

property default_font: str[source]

Font, size and color of the regular text of the menu.

property monospace_font: str[source]

Font, size and color of the monospace text of the menu.

property error_font: str[source]

Error font: the monospace font, in red.

It sets no size of its own. The menu gives a row the height of the largest font in it, and both hosts put the extra space under the text instead of dividing it between the rows. With size=10, an error line had 15 pixels of space below it, and every other row had 9.

property is_swiftbar: bool[source]

True when the host is SwiftBar, which sets the SWIFTBAR variable.

static search_venv(folder)[source]

Return the command that runs mpm from the project in folder, or None.

A folder is a project when it holds a lockfile. The command is the one that the project’s tool provides to run a program inside the environment it manages. The result never starts with an environment assignment: check_mpm() runs the command without a shell, and a shell-less call would read the assignment as the program name. The other candidates of search_mpm() cover a folder with no lockfile.

Return type:

tuple[str, ...] | None

search_mpm()[source]

Yield each command that can run mpm.

The method covers every context of a system where mpm may be installed.

ranked_mpm keeps the order of these candidates.

The commands from a virtual environment come first. The plugin prefers the mpm that it belongs to. This file is part of the package, so the walk up its parent folders reaches the project that installed it. That mpm is the one released with this plugin, and its dependencies are already resolved. Both hosts put a symlink to this file in their own plugin folder, which is why the code below resolves the path first. An unresolved path would walk that plugin folder, find nothing, and leave the plugin with another mpm from the system.

The other candidates are for a plugin installed in the host alone: a system-wide installation, then the module under an interpreter. check_mpm() runs each candidate before the ranking, so none of them is accepted without a test.

Return type:

Generator[tuple[str, ...], None, None]

check_mpm(mpm_cli_args)[source]

Run the version probe of one command and read the result.

--no-color keeps the output parseable when Click detects a terminal. A command with a missing program is a candidate like the others, and its error is the exception.

Return type:

Candidate

property ranked_mpm: list[Candidate][source]

Every mpm found on the system, best candidate first.

Sorted on Candidate.rank. Two candidates with the same rank keep the order that search_mpm() gave them.

property best_mpm: Candidate[source]
property shell_env_options: tuple[str, ...]

--shell-env for an mpm that accepts it, nothing for an older one.

Goes right after the mpm command and ahead of mpm_options, so a --no-shell-env typed into VAR_MPM_OPTIONS wins.

static pp(label, *args)[source]

Print one menu line in the SwiftBar and Xbar dialect.

The first argument is the label of the menu line. A pipe separates it from the other parameters, and a space separates those parameters from each other.

The method prints nothing when the label is empty. A None parameter renders nothing, so a package with no upgrade command still gets a menu line with its label alone.

Return type:

None

static print_error_header()[source]

Print the menu header of a blocking error.

Return type:

None

print_error(message, submenu='')[source]

Print an error message, with one menu line per line of text.

The lines use a red, fixed-width font, which keeps the layout of a traceback or of an exception. The message is dedented and its empty lines are removed, to keep the block compact.

The message is converted to a string, so the caller can pass an exception object.

Return type:

None

print_about()[source]

Print the menu footer, with the version of the plugin and the version of mpm.

This script and mpm are installed and updated separately. A plugin file copied into the host’s folder keeps its version while mpm changes, and no other part of the menu shows the difference. The mpm line reports the release as printed, with its suffix. The ranking compares the numeric part only.

The footer is one collapsed row. Three visible rows would push the package list down for a user who opened the menu to read it.

Return type:

None

print_menu()[source]

Print the main menu.

Return type:

None