meta_package_manager.bitbar — Meta Package Manager 2.4.0 documentation

# Source code for meta_package_manager.bitbar

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # <bitbar.title>Meta Package Manager</bitbar.title>
    # <bitbar.version>v2.4.0</bitbar.version>
    # <bitbar.author>Kevin Deldycke</bitbar.author>
    # <bitbar.author.github>kdeldycke</bitbar.author.github>
    # <bitbar.desc>List outdated packages and manage upgrades.</bitbar.desc>
    # <bitbar.dependencies>python,mpm</bitbar.dependencies>
    # <bitbar.image>https://i.imgur.com/CiQpQ42.png</bitbar.image>
    # <bitbar.abouturl>https://github.com/kdeldycke/meta-package-manager</bitbar.abouturl>

    """
    Bitbar plugin for Meta Package Manager (a.k.a. the mpm CLI).

    Default update cycle is set to 7 hours so we have a chance to get user's
    attention once a day. Higher frequency might ruin the system as all checks are
    quite resource intensive, and Homebrew might hit GitHub's API calls quota.
    """

    from __future__ import print_function, unicode_literals

    import json
    import os
    import sys
    from operator import itemgetter
    from subprocess import PIPE, Popen

    PY2 = sys.version_info[0] == 2

    # Set to ``False`` to replace the default flat layout with an alternative
    # structure where all upgrade actions are put into submenus, one for each
    # manager.
    FLAT_LAYOUT = True

    # Make it easier to change font, sizes and colors of the output
    # See https://github.com/matryer/bitbar#writing-plugins for details
    # An alternate "good looking" font is "font=NotoMono size=13" (not installed
    # on MacOS by default though) that matches the system font quite well.
    FONTS = {
        'normal':  '',                              # Use default system font
        'summary': '',                              # Package summary
        'package': '',                              # Indiviual packages
        'error':   'color=red font=Menlo size=12',  # Errors
    }
    # Use a monospaced font when using submenus
    if not FLAT_LAYOUT:
        FONTS['summary'] = 'font=Menlo size=12'

    def fix_environment():
        """Tweak environment variable to find non-default system-wide binaries.

        macOS does not put ``/usr/local/bin`` or ``/opt/local/bin`` in the ``PATH``
        for GUI apps. For some package managers this is a problem. Additioanlly
        Homebrew and Macports are using different pathes. So, to make sure we can
        always get to the necessary binaries, we overload the path. Current
        preference order would equate to Homebrew, Macports, then system.
        """
        os.environ['PATH'] = ':'.join([
            '/usr/local/bin',
            '/usr/local/sbin',
            '/opt/local/bin',
            '/opt/local/sbin',
            os.environ.get('PATH', '')])

        # Python 3 Surrogate Handling. See:
        # https://click.pocoo.org/6/python3/#python-3-surrogate-handling
        os.environ['LC_ALL'] = os.environ['LANG'] = 'en_US.UTF-8'

    def run(*args):
        """Run a shell command, return error code, output and error message."""
        assert isinstance(args, tuple)
        try:
            process = Popen(args, stdout=PIPE, stderr=PIPE)
        except OSError:
            return None, None, "`{}` executable not found.".format(args[0])
        output, error = process.communicate()
        return (
            process.returncode,
            output.decode('utf-8') if output else None,
            error.decode('utf-8') if error else None)

    def echo(message):
        """Print message to the output.

        Not unlike ``click.echo()``, this method is required to support
        discrepencies in the way strings are handled in different Python versions
        and platforms.
        """
        if PY2:
            message = message.encode('utf-8')
        print(message)

    def print_error_header():
        """Generic header for blockng error."""
        echo("❌ | dropdown=false")
        echo("---")

    def print_error(message, submenu=""):
        """Print a formatted error line by line.

        A red, fixed-width font is used to preserve traceback and exception layout.
        """
        for line in message.strip().split("\n"):
            echo(
                "{}{} | {f_error} trim=false emojize=false"
                "".format(submenu, line, f_error=FONTS['error']))

    def print_package_items(packages, submenu=""):
        """Print a menu entry for each outdated packages available for upgrade."""
        for pkg_info in packages:
            echo(
                "{}{name} {installed_version} → {latest_version} | {upgrade_cli}"
                " terminal=false refresh=true {f_package} emojize=false".format(
                    submenu, f_package=FONTS['package'], **pkg_info))

    def print_upgrade_all_item(manager, submenu=""):
        """Print the menu entry to upgrade all outdated package of a manager."""
        if manager.get('upgrade_all_cli'):
            if not FLAT_LAYOUT:
                echo("-----")
            echo(
                "{}Upgrade all | {} terminal=false refresh=true {f_normal}".format(
                    submenu, manager['upgrade_all_cli'], f_normal=FONTS['normal']))

    if __name__ == '__main__':
        fix_environment()
        print_menu()