meta_package_manager.bar_plugin_renderer module¶

Renderer on the mpm side, which builds the SwiftBar and Xbar plugin output.

This code is in its own module. meta_package_manager.bar_plugin uses the Python standard library only, because its MPMPlugin class is the script that the user installs as the bar plugin, and that script must have few dependencies.

This module is the companion on the mpm side. It adds click_extra, boltons, the manager pool and the theme system to the plugin code, and produces the final output of mpm outdated --plugin-output.

meta_package_manager.bar_plugin_renderer.MAX_VERSION_WIDTH = 18¶

Maximum width of a version in a menu line, in characters.

A macOS menu cannot scroll sideways and a menu item is one line high, so a row wider than the menu loses its end. SwiftBar keeps the AppKit default paragraph style .byWordWrapping, which moves the last token to a second line that the item does not draw. That token is the new version, so one package with a long version hides the column that the menu shows. The limit also helps the other rows, because the table aligns on the widest cell of the column.

The value 18 comes from a survey of 1087 version strings: 578 from the bundled [samples] fixtures and the manager docstrings, and 509 from the inventory of one macOS system. The two groups are far apart. A version that a person reads is 17 characters long at most (152.0.7977.82-1.1, a Homebrew cask). A machine identifier starts at 26 characters: a Julia build triple, a commit SHA of 40 characters, and Homebrew’s 50-character version,revision pair. 18 is the smallest limit that keeps all of the first group. In the fixtures, the Linux package managers reach 22 characters (2.7+git1722+daf2f52-r0, an OpenWrt package). This renderer never sees them, because SwiftBar and Xbar run on macOS only.

Set the VAR_MAX_VERSION_WIDTH environment variable to change this value. See BarPluginRenderer.max_version_width().

meta_package_manager.bar_plugin_renderer.VERSION_ELLIPSIS = '…'¶

Marker for the characters that MAX_VERSION_WIDTH removes.

It is one character wide in a monospace font, so the table loses one column. ... would lose three.

meta_package_manager.bar_plugin_renderer.VERSION_PREFIX_COLOR = 245¶

Index in the xterm-256 palette for the unchanged prefix of a version.

The CLI table keeps the default bright_black (SGR 90) of meta_package_manager.version.diff_versions(), and a terminal changes that color to match its own theme. SwiftBar always maps SGR 90 to the fixed NSColor.darkGray, which is hard to see on a dark menu. Its support of 256 colors renders the palette index 245 as a mid-gray (#8a8a8a) that does not depend on the theme and is readable in both appearances. Xbar removes the ANSI codes that it does not render, so this choice has no effect there.

meta_package_manager.bar_plugin_renderer.LIGHT_MENU_OLD_COLOR = 124¶

Palette index for the red suffix of the old version on a light menu.

#af0000, with a contrast ratio of 6.5:1 on the cream material. See BarPluginRenderer.menu_diff_colors() for the reason for this value.

meta_package_manager.bar_plugin_renderer.LIGHT_MENU_NEW_COLOR = 23¶

Palette index for the green suffix of the new version on a light menu.

#006600, with a contrast ratio of 6.3:1 on the cream material. See BarPluginRenderer.menu_diff_colors().

meta_package_manager.bar_plugin_renderer.DARK_MENU_NEW_COLOR = 46¶

Palette index for the green suffix of the new version on a dark menu.

#00ff00. It raises the worst contrast from 4.0:1, the value of the adaptive NSColor.systemGreen, to 5.8:1 when a bright wallpaper shows through the translucent menu. The red suffix of the old version keeps systemRed. It is already the most readable red in the xterm-256 palette: a pure #ff0000 has a lower ratio, and a brighter red looks orange. See BarPluginRenderer.menu_diff_colors().

meta_package_manager.bar_plugin_renderer.elide_versions(old, new, width)[source]¶

Shorten a pair of versions to width characters, and keep the two versions different.

The function removes the end of each version first. The end holds the part that nobody reads: a commit SHA, a platform triple, or Homebrew’s version,revision pair. That method does not work for a rebuild whose last characters are the only difference (2.6.0-2.suse1699.10 against its .11), because the two results would be the same string with an arrow between them. The function then removes the common start instead. It puts the … at the boundary that common_prefix_length() reports, so the marker sits between the gray prefix and the colored suffix.

The function makes this choice for the pair, and not for each version on its own. With a choice per version, one run rendered 5.0.0~beta1-0ubuntu7 as ….0~beta1-0ubuntu7 next to 5.0.2-0ubuntu1~26…, which had kept its start. The column did not line up, and one side alone lost its 5.0.

Return type:

tuple[str, str]

class meta_package_manager.bar_plugin_renderer.BarPluginRenderer[source]¶

Bases: MPMPlugin

Renders mpm outdated in the SwiftBar and Xbar plugin dialect.

This class adds the rendering to MPMPlugin. The rendering can use mpm’s own dependencies. See the module docstring for the division between the two modules.

property group_by_manager: bool[source]¶

Give each manager its own section, in place of one flat list.

Each host draws that section in its own way: Xbar opens a sub-menu, SwiftBar folds an accordion into the same menu, and the GNOME Shell extension expands the section inline. The name of this option describes the grouping itself, and not the way one host draws it.

The value comes from the VAR_GROUP_BY_MANAGER environment variable.

property fold_sections: bool[source]¶

Render the manager sections as inline accordions instead of sub-menus.

SwiftBar 2.1.0 renders an item with fold=true and its ---prefixed children as one collapsible section. A click on the header expands the section in place. It does not open a sub-menu and it does not close the menu. The expanded state stays after a refresh (swiftbar/SwiftBar#480).

This option needs group_by_manager, which provides the children to fold. Xbar has no equivalent parameter and ignores it, so its grouped layout keeps the sub-menus.

property own_panel_per_manager: bool[source]¶

True when the packages of each manager have a panel of their own.

This is true for Xbar’s grouped layout only, where a ---prefixed row opens a sub-menu. SwiftBar folds the same rows into the same menu, and the flat layout stays in one menu too. In those cases every package is in one continuous column.

property max_version_width: int[source]¶

Maximum width of a rendered version, in characters, before the renderer shortens it.

The value comes from the VAR_MAX_VERSION_WIDTH environment variable, and its default is MAX_VERSION_WIDTH. A value lower than 2 leaves no space for the ellipsis and for one character of content, so the code reads it as no limit.

property menu_diff_colors: dict[str, int][source]¶

Colors of the version-diff suffixes, adapted to the menu appearance.

SwiftBar maps the default SGR 31 and 32 suffixes of meta_package_manager.version.diff_versions() to the adaptive colors NSColor.systemRed and NSColor.systemGreen. It also puts the menu appearance in the OS_APPEARANCE environment variable, which the mpm outdated --plugin-output subprocess inherits. On the translucent “Liquid Glass” menus of recent macOS releases, these system colors have too little contrast with the material. The method replaces them for each appearance:

  • On a light menu, both suffixes are too pale. The measured contrast of the green is 1.9:1. The method uses the darker LIGHT_MENU_OLD_COLOR and LIGHT_MENU_NEW_COLOR.

  • On a dark menu above a bright wallpaper, the contrast of the green falls to 4.0:1. The method uses the brighter DARK_MENU_NEW_COLOR, and keeps systemRed for the red. That red is already the most readable red in the palette.

The result is a set of keyword arguments for diff_versions. The method returns an empty mapping when the variable is not set, for a host like Xbar that removes these codes. The system colors then stay in force.

property mpm_cli: tuple[str, ...][source]¶

Absolute mpm command that runs the actions of the menu.

The command starts the same interpreter that renders the menu. A click then runs the same mpm that the plugin called, and reads the same configuration file. The value comes from sys.executable, not from sys.argv[0]. sys.executable is always an absolute path to a runnable entry point. sys.argv[0] can be a console script, a __main__.py file or a bare -c, and it depends on the way mpm started. An mpm compiled with Nuitka is its own interpreter, so the code calls it directly, with no module.

-P keeps the folder that the action starts from out of sys.path. Without it, -m puts that folder first. An action started from a source checkout would then import that source tree, with the dependencies of the installed version. A 7.6.1 interpreter that read an 8.0.0.dev0 tree stopped on from click_extra.table import AUTO_WIDTH. The flag needs Python 3.11. The interpreter that renders the menu is the one that the action starts, so its own version decides.

Note

This code does not reuse the candidates that meta_package_manager.bar_plugin.MPMPlugin.search_mpm() produces. The candidates from a virtual environment start with a bare uv, pipenv or poetry command name. A bar app starts a menu action with the launchd PATH alone, and that PATH cannot find such a name.

property shell_env_options: tuple[str, ...]¶

--shell-env when the run rendering the menu adopted the login shell environment, nothing otherwise.

An action starts the way the plugin does, outside a terminal, so it needs the environment the menu was rendered from. The flag is read off the root context of the mpm outdated --plugin-output run, and stays ahead of mpm_options so a --no-shell-env typed there wins. A renderer built outside a run, like the test suite does, carries none.

static render_cli(cmd_args)[source]¶

Return a command in the SwiftBar and Xbar plugin format.

The result is a string with this schema:

shell=cmd_args[0] param1=cmd_args[1] param2=cmd_args[2] ...
Return type:

str

print_cli_item(*args)[source]¶

Print two menu entries for one command:

  • one that opens a visible terminal, so the user can follow the execution

  • one that runs with no terminal, which the user reaches with the Option key

Return type:

None

print_upgrade_all_item(manager, submenu='')[source]¶

Print the menu entry that upgrades every outdated package of one manager.

Return type:

None

package_rows(manager)[source]¶

Return one row of cells for each outdated package.

Each result holds the cells, the command that the row runs, and the tooltip with the full versions that the width limit removed.

Return type:

list[tuple[tuple[str, ...], str, str]]

version_tooltip(old, new, elided)[source]¶

Return the full version pair for a menu item whose cells were shortened.

SwiftBar shows a tooltip when the pointer is above the item, so the user can still read the characters that max_version_width() removed. The SwiftBar parser reads a quoted value as one piece, so the spaces around the arrow are safe. Xbar has no such parameter and would render the text as part of the label, so the method returns an empty string for Xbar.

Return type:

str

static align_rows(rows)[source]¶

Put a set of rows into aligned columns.

The arrow is centered, so the user follows one vertical line down the column, and the versions grow away from it on both sides.

Return type:

list[str]

align_managers(rows_by_manager)[source]¶

Align the rows of every manager, in one table or in one table per manager.

One table serves the layouts where the managers share a column: the flat layout and the SwiftBar accordion. A table sized for one manager aligns its own arrows, but those arrows then do not line up with the section above. In a menu that the user reads in one pass, that looks like a mistake.

One table per manager serves the layouts where each manager has a panel of its own. There, a width taken from all the managers would pad every short sub-menu to the longest package name in the report.

Return type:

dict[str, list[str]]

render(outdated_data)[source]¶

Call _render() and capture its <stdout> output.

Every method on the _render path writes with a plain print call, and that includes the inherited pp and print_error. One redirection of <stdout> then captures the whole rendering.

Return type:

str

add_upgrade_cli(outdated_data)[source]¶

Add the upgrade commands to the data of mpm outdated, for the bar plugin.

Every menu action is one mpm_cli call limited to the manager of the section (mpm --brew upgrade wget). It is never the native command of that manager. A click that goes through mpm follows the same settings as the run that rendered the menu: the configuration file on the system, and with it the release-age cooldown, the manager selection, the sudo policy and the per-manager overrides. A native command ignores all of them, and would upgrade a package that mpm would have stopped.

The command passes only the manager selector, the operation and the package. mpm reads every other setting from the user’s configuration at the time of the click. The package is a pURL (manager_purl()), which names the section’s manager. mpm then upgrades the package without a search in the installed packages. That search misses the packages that a manager reports as outdated but not as installed.

A manager gets the action only when it implements() it. This is the same test that mpm uses to route the subcommand. A manager that mpm would skip gets a None command, and the menu renders its label alone.

print(outdated_data)[source]¶

Print the final plugin rendering to <stdout>.

Capturing the output and printing it again would add one line break, so the call to rstrip() removes it.

The colors are forced on, and echo does not run its own detection. The bar plugin captures mpm outdated --plugin-output through a pipe, and echo would remove every ANSI code there. The colors of the version diff would then never reach SwiftBar or Xbar. A TTY test has no meaning for this dialect, which sets the rendering of ANSI codes line by line with the ansi=true and ansi=false parameters. An explicit opt-out (--color=never, NO_COLOR) still applies: the code replaces the automatic (None) state only. The value comes from invocation_color(), not from ctx.color, so it is also correct for a rendering on a worker thread. The command context is thread-local and does not reach such a thread.

Return type:

None