Blame dnf/cli/term.py

Packit Service 21c75c
# Copyright (C) 2013-2014  Red Hat, Inc.
Packit Service 21c75c
# Terminal routines.
Packit Service 21c75c
#
Packit Service 21c75c
# This copyrighted material is made available to anyone wishing to use,
Packit Service 21c75c
# modify, copy, or redistribute it subject to the terms and conditions of
Packit Service 21c75c
# the GNU General Public License v.2, or (at your option) any later version.
Packit Service 21c75c
# This program is distributed in the hope that it will be useful, but WITHOUT
Packit Service 21c75c
# ANY WARRANTY expressed or implied, including the implied warranties of
Packit Service 21c75c
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
Packit Service 21c75c
# Public License for more details.  You should have received a copy of the
Packit Service 21c75c
# GNU General Public License along with this program; if not, write to the
Packit Service 21c75c
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit Service 21c75c
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
Packit Service 21c75c
# source code or documentation are not subject to the GNU General Public
Packit Service 21c75c
# License and may only be used or replicated with the express permission of
Packit Service 21c75c
# Red Hat, Inc.
Packit Service 21c75c
#
Packit Service 21c75c
Packit Service 21c75c
from __future__ import absolute_import
Packit Service 21c75c
from __future__ import unicode_literals
Packit Service 21c75c
import curses
Packit Service 21c75c
import dnf.pycomp
Packit Service 21c75c
import fcntl
Packit Service 21c75c
import re
Packit Service 21c75c
import struct
Packit Service 21c75c
import sys
Packit Service 21c75c
import termios
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def _real_term_width(fd=1):
Packit Service 21c75c
    """ Get the real terminal width """
Packit Service 21c75c
    try:
Packit Service 21c75c
        buf = 'abcdefgh'
Packit Service 21c75c
        buf = fcntl.ioctl(fd, termios.TIOCGWINSZ, buf)
Packit Service 21c75c
        ret = struct.unpack(b'hhhh', buf)[1]
Packit Service 21c75c
        return ret
Packit Service 21c75c
    except IOError:
Packit Service 21c75c
        return None
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
def _term_width(fd=1):
Packit Service 21c75c
    """ Compute terminal width falling to default 80 in case of trouble"""
Packit Service 21c75c
    tw = _real_term_width(fd=1)
Packit Service 21c75c
    if not tw:
Packit Service 21c75c
        return 80
Packit Service 21c75c
    elif tw < 20:
Packit Service 21c75c
        return 20
Packit Service 21c75c
    else:
Packit Service 21c75c
        return tw
Packit Service 21c75c
Packit Service 21c75c
Packit Service 21c75c
class Term(object):
Packit Service 21c75c
    """A class to provide some terminal "UI" helpers based on curses."""
Packit Service 21c75c
Packit Service 21c75c
    # From initial search for "terminfo and python" got:
Packit Service 21c75c
    # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/475116
Packit Service 21c75c
    # ...it's probably not copyrightable, but if so ASPN says:
Packit Service 21c75c
    #
Packit Service 21c75c
    #  Except where otherwise noted, recipes in the Python Cookbook are
Packit Service 21c75c
    # published under the Python license.
Packit Service 21c75c
Packit Service 21c75c
    __enabled = True
Packit Service 21c75c
Packit Service 21c75c
    real_columns = property(lambda self: _real_term_width())
Packit Service 21c75c
    columns = property(lambda self: _term_width())
Packit Service 21c75c
Packit Service 21c75c
    __cap_names = {
Packit Service 21c75c
        'underline' : 'smul',
Packit Service 21c75c
        'reverse' : 'rev',
Packit Service 21c75c
        'normal' : 'sgr0',
Packit Service 21c75c
        }
Packit Service 21c75c
Packit Service 21c75c
    __colors = {
Packit Service 21c75c
        'black' : 0,
Packit Service 21c75c
        'blue' : 1,
Packit Service 21c75c
        'green' : 2,
Packit Service 21c75c
        'cyan' : 3,
Packit Service 21c75c
        'red' : 4,
Packit Service 21c75c
        'magenta' : 5,
Packit Service 21c75c
        'yellow' : 6,
Packit Service 21c75c
        'white' : 7
Packit Service 21c75c
        }
Packit Service 21c75c
    __ansi_colors = {
Packit Service 21c75c
        'black' : 0,
Packit Service 21c75c
        'red' : 1,
Packit Service 21c75c
        'green' : 2,
Packit Service 21c75c
        'yellow' : 3,
Packit Service 21c75c
        'blue' : 4,
Packit Service 21c75c
        'magenta' : 5,
Packit Service 21c75c
        'cyan' : 6,
Packit Service 21c75c
        'white' : 7
Packit Service 21c75c
        }
Packit Service 21c75c
    __ansi_forced_MODE = {
Packit Service 21c75c
        'bold' : '\x1b[1m',
Packit Service 21c75c
        'blink' : '\x1b[5m',
Packit Service 21c75c
        'dim' : '',
Packit Service 21c75c
        'reverse' : '\x1b[7m',
Packit Service 21c75c
        'underline' : '\x1b[4m',
Packit Service 21c75c
        'normal' : '\x1b(B\x1b[m'
Packit Service 21c75c
        }
Packit Service 21c75c
    __ansi_forced_FG_COLOR = {
Packit Service 21c75c
        'black' : '\x1b[30m',
Packit Service 21c75c
        'red' : '\x1b[31m',
Packit Service 21c75c
        'green' : '\x1b[32m',
Packit Service 21c75c
        'yellow' : '\x1b[33m',
Packit Service 21c75c
        'blue' : '\x1b[34m',
Packit Service 21c75c
        'magenta' : '\x1b[35m',
Packit Service 21c75c
        'cyan' : '\x1b[36m',
Packit Service 21c75c
        'white' : '\x1b[37m'
Packit Service 21c75c
        }
Packit Service 21c75c
    __ansi_forced_BG_COLOR = {
Packit Service 21c75c
        'black' : '\x1b[40m',
Packit Service 21c75c
        'red' : '\x1b[41m',
Packit Service 21c75c
        'green' : '\x1b[42m',
Packit Service 21c75c
        'yellow' : '\x1b[43m',
Packit Service 21c75c
        'blue' : '\x1b[44m',
Packit Service 21c75c
        'magenta' : '\x1b[45m',
Packit Service 21c75c
        'cyan' : '\x1b[46m',
Packit Service 21c75c
        'white' : '\x1b[47m'
Packit Service 21c75c
        }
Packit Service 21c75c
Packit Service 21c75c
    def __forced_init(self):
Packit Service 21c75c
        self.MODE = self.__ansi_forced_MODE
Packit Service 21c75c
        self.FG_COLOR = self.__ansi_forced_FG_COLOR
Packit Service 21c75c
        self.BG_COLOR = self.__ansi_forced_BG_COLOR
Packit Service 21c75c
Packit Service 21c75c
    def reinit(self, term_stream=None, color='auto'):
Packit Service 21c75c
        """Reinitializes the :class:`Term`.
Packit Service 21c75c
Packit Service 21c75c
        :param term_stream:  the terminal stream that the
Packit Service 21c75c
           :class:`Term` should be initialized to use.  If
Packit Service 21c75c
           *term_stream* is not given, :attr:`sys.stdout` is used.
Packit Service 21c75c
        :param color: when to colorize output.  Valid values are
Packit Service 21c75c
           'always', 'auto', and 'never'.  'always' will use ANSI codes
Packit Service 21c75c
           to always colorize output, 'auto' will decide whether do
Packit Service 21c75c
           colorize depending on the terminal, and 'never' will never
Packit Service 21c75c
           colorize.
Packit Service 21c75c
        """
Packit Service 21c75c
        self.__enabled = True
Packit Service 21c75c
        self.lines = 24
Packit Service 21c75c
Packit Service 21c75c
        if color == 'always':
Packit Service 21c75c
            self.__forced_init()
Packit Service 21c75c
            return
Packit Service 21c75c
Packit Service 21c75c
        # Output modes:
Packit Service 21c75c
        self.MODE = {
Packit Service 21c75c
            'bold' : '',
Packit Service 21c75c
            'blink' : '',
Packit Service 21c75c
            'dim' : '',
Packit Service 21c75c
            'reverse' : '',
Packit Service 21c75c
            'underline' : '',
Packit Service 21c75c
            'normal' : ''
Packit Service 21c75c
            }
Packit Service 21c75c
Packit Service 21c75c
        # Colours
Packit Service 21c75c
        self.FG_COLOR = {
Packit Service 21c75c
            'black' : '',
Packit Service 21c75c
            'blue' : '',
Packit Service 21c75c
            'green' : '',
Packit Service 21c75c
            'cyan' : '',
Packit Service 21c75c
            'red' : '',
Packit Service 21c75c
            'magenta' : '',
Packit Service 21c75c
            'yellow' : '',
Packit Service 21c75c
            'white' : ''
Packit Service 21c75c
            }
Packit Service 21c75c
Packit Service 21c75c
        self.BG_COLOR = {
Packit Service 21c75c
            'black' : '',
Packit Service 21c75c
            'blue' : '',
Packit Service 21c75c
            'green' : '',
Packit Service 21c75c
            'cyan' : '',
Packit Service 21c75c
            'red' : '',
Packit Service 21c75c
            'magenta' : '',
Packit Service 21c75c
            'yellow' : '',
Packit Service 21c75c
            'white' : ''
Packit Service 21c75c
            }
Packit Service 21c75c
Packit Service 21c75c
        if color == 'never':
Packit Service 21c75c
            self.__enabled = False
Packit Service 21c75c
            return
Packit Service 21c75c
        assert color == 'auto'
Packit Service 21c75c
Packit Service 21c75c
        # If the stream isn't a tty, then assume it has no capabilities.
Packit Service 21c75c
        if not term_stream:
Packit Service 21c75c
            term_stream = sys.stdout
Packit Service 21c75c
        if not term_stream.isatty():
Packit Service 21c75c
            self.__enabled = False
Packit Service 21c75c
            return
Packit Service 21c75c
Packit Service 21c75c
        # Check the terminal type.  If we fail, then assume that the
Packit Service 21c75c
        # terminal has no capabilities.
Packit Service 21c75c
        try:
Packit Service 21c75c
            curses.setupterm(fd=term_stream.fileno())
Packit Service 21c75c
        except Exception:
Packit Service 21c75c
            self.__enabled = False
Packit Service 21c75c
            return
Packit Service 21c75c
        self._ctigetstr = curses.tigetstr
Packit Service 21c75c
Packit Service 21c75c
        self.lines = curses.tigetnum('lines')
Packit Service 21c75c
Packit Service 21c75c
        # Look up string capabilities.
Packit Service 21c75c
        for cap_name in self.MODE:
Packit Service 21c75c
            mode = cap_name
Packit Service 21c75c
            if cap_name in self.__cap_names:
Packit Service 21c75c
                cap_name = self.__cap_names[cap_name]
Packit Service 21c75c
            self.MODE[mode] = self._tigetstr(cap_name)
Packit Service 21c75c
Packit Service 21c75c
        # Colors
Packit Service 21c75c
        set_fg = self._tigetstr('setf').encode('utf-8')
Packit Service 21c75c
        if set_fg:
Packit Service 21c75c
            for (color, val) in self.__colors.items():
Packit Service 21c75c
                self.FG_COLOR[color] = curses.tparm(set_fg, val).decode() or ''
Packit Service 21c75c
        set_fg_ansi = self._tigetstr('setaf').encode('utf-8')
Packit Service 21c75c
        if set_fg_ansi:
Packit Service 21c75c
            for (color, val) in self.__ansi_colors.items():
Packit Service 21c75c
                fg_color = curses.tparm(set_fg_ansi, val).decode() or ''
Packit Service 21c75c
                self.FG_COLOR[color] = fg_color
Packit Service 21c75c
        set_bg = self._tigetstr('setb').encode('utf-8')
Packit Service 21c75c
        if set_bg:
Packit Service 21c75c
            for (color, val) in self.__colors.items():
Packit Service 21c75c
                self.BG_COLOR[color] = curses.tparm(set_bg, val).decode() or ''
Packit Service 21c75c
        set_bg_ansi = self._tigetstr('setab').encode('utf-8')
Packit Service 21c75c
        if set_bg_ansi:
Packit Service 21c75c
            for (color, val) in self.__ansi_colors.items():
Packit Service 21c75c
                bg_color = curses.tparm(set_bg_ansi, val).decode() or ''
Packit Service 21c75c
                self.BG_COLOR[color] = bg_color
Packit Service 21c75c
Packit Service 21c75c
    def __init__(self, term_stream=None, color='auto'):
Packit Service 21c75c
        self.reinit(term_stream, color)
Packit Service 21c75c
Packit Service 21c75c
    def _tigetstr(self, cap_name):
Packit Service 21c75c
        # String capabilities can include "delays" of the form "$<2>".
Packit Service 21c75c
        # For any modern terminal, we should be able to just ignore
Packit Service 21c75c
        # these, so strip them out.
Packit Service 21c75c
        cap = self._ctigetstr(cap_name) or ''
Packit Service 21c75c
        if dnf.pycomp.is_py3bytes(cap):
Packit Service 21c75c
            cap = cap.decode()
Packit Service 21c75c
        return re.sub(r'\$<\d+>[/*]?', '', cap)
Packit Service 21c75c
Packit Service 21c75c
    def color(self, color, s):
Packit Service 21c75c
        """Colorize string with color"""
Packit Service 21c75c
        return (self.MODE[color] + str(s) + self.MODE['normal'])
Packit Service 21c75c
Packit Service 21c75c
    def bold(self, s):
Packit Service 21c75c
        """Make string bold."""
Packit Service 21c75c
        return self.color('bold', s)
Packit Service 21c75c
Packit Service 21c75c
    def sub(self, haystack, beg, end, needles, escape=None, ignore_case=False):
Packit Service 21c75c
        """Search the string *haystack* for all occurrences of any
Packit Service 21c75c
        string in the list *needles*.  Prefix each occurrence with
Packit Service 21c75c
        *beg*, and postfix each occurrence with *end*, then return the
Packit Service 21c75c
        modified string.  For example::
Packit Service 21c75c
Packit Service 21c75c
           >>> yt = Term()
Packit Service 21c75c
           >>> yt.sub('spam and eggs', 'x', 'z', ['and'])
Packit Service 21c75c
           'spam xandz eggs'
Packit Service 21c75c
Packit Service 21c75c
        This is particularly useful for emphasizing certain words
Packit Service 21c75c
        in output: for example, calling :func:`sub` with *beg* =
Packit Service 21c75c
        MODE['bold'] and *end* = MODE['normal'] will return a string
Packit Service 21c75c
        that when printed to the terminal will appear to be *haystack*
Packit Service 21c75c
        with each occurrence of the strings in *needles* in bold
Packit Service 21c75c
        face.  Note, however, that the :func:`sub_mode`,
Packit Service 21c75c
        :func:`sub_bold`, :func:`sub_fg`, and :func:`sub_bg` methods
Packit Service 21c75c
        provide convenient ways to access this same emphasizing functionality.
Packit Service 21c75c
Packit Service 21c75c
        :param haystack: the string to be modified
Packit Service 21c75c
        :param beg: the string to be prefixed onto matches
Packit Service 21c75c
        :param end: the string to be postfixed onto matches
Packit Service 21c75c
        :param needles: a list of strings to add the prefixes and
Packit Service 21c75c
           postfixes to
Packit Service 21c75c
        :param escape: a function that accepts a string and returns
Packit Service 21c75c
           the same string with problematic characters escaped.  By
Packit Service 21c75c
           default, :func:`re.escape` is used.
Packit Service 21c75c
        :param ignore_case: whether case should be ignored when
Packit Service 21c75c
           searching for matches
Packit Service 21c75c
        :return: *haystack* with *beg* prefixing, and *end*
Packit Service 21c75c
          postfixing, occurrences of the strings in *needles*
Packit Service 21c75c
        """
Packit Service 21c75c
        if not self.__enabled:
Packit Service 21c75c
            return haystack
Packit Service 21c75c
Packit Service 21c75c
        if not escape:
Packit Service 21c75c
            escape = re.escape
Packit Service 21c75c
Packit Service 21c75c
        render = lambda match: beg + match.group() + end
Packit Service 21c75c
        for needle in needles:
Packit Service 21c75c
            pat = escape(needle)
Packit Service 21c75c
            if ignore_case:
Packit Service 21c75c
                pat = re.template(pat, re.I)
Packit Service 21c75c
            haystack = re.sub(pat, render, haystack)
Packit Service 21c75c
        return haystack
Packit Service 21c75c
    def sub_norm(self, haystack, beg, needles, **kwds):
Packit Service 21c75c
        """Search the string *haystack* for all occurrences of any
Packit Service 21c75c
        string in the list *needles*.  Prefix each occurrence with
Packit Service 21c75c
        *beg*, and postfix each occurrence with self.MODE['normal'],
Packit Service 21c75c
        then return the modified string.  If *beg* is an ANSI escape
Packit Service 21c75c
        code, such as given by self.MODE['bold'], this method will
Packit Service 21c75c
        return *haystack* with the formatting given by the code only
Packit Service 21c75c
        applied to the strings in *needles*.
Packit Service 21c75c
Packit Service 21c75c
        :param haystack: the string to be modified
Packit Service 21c75c
        :param beg: the string to be prefixed onto matches
Packit Service 21c75c
        :param end: the string to be postfixed onto matches
Packit Service 21c75c
        :param needles: a list of strings to add the prefixes and
Packit Service 21c75c
           postfixes to
Packit Service 21c75c
        :return: *haystack* with *beg* prefixing, and self.MODE['normal']
Packit Service 21c75c
          postfixing, occurrences of the strings in *needles*
Packit Service 21c75c
        """
Packit Service 21c75c
        return self.sub(haystack, beg, self.MODE['normal'], needles, **kwds)
Packit Service 21c75c
Packit Service 21c75c
    def sub_mode(self, haystack, mode, needles, **kwds):
Packit Service 21c75c
        """Search the string *haystack* for all occurrences of any
Packit Service 21c75c
        string in the list *needles*.  Prefix each occurrence with
Packit Service 21c75c
        self.MODE[*mode*], and postfix each occurrence with
Packit Service 21c75c
        self.MODE['normal'], then return the modified string.  This
Packit Service 21c75c
        will return a string that when printed to the terminal will
Packit Service 21c75c
        appear to be *haystack* with each occurrence of the strings in
Packit Service 21c75c
        *needles* in the given *mode*.
Packit Service 21c75c
Packit Service 21c75c
        :param haystack: the string to be modified
Packit Service 21c75c
        :param mode: the mode to set the matches to be in.  Valid
Packit Service 21c75c
           values are given by self.MODE.keys().
Packit Service 21c75c
        :param needles: a list of strings to add the prefixes and
Packit Service 21c75c
           postfixes to
Packit Service 21c75c
        :return: *haystack* with self.MODE[*mode*] prefixing, and
Packit Service 21c75c
          self.MODE['normal'] postfixing, occurrences of the strings
Packit Service 21c75c
          in *needles*
Packit Service 21c75c
        """
Packit Service 21c75c
        return self.sub_norm(haystack, self.MODE[mode], needles, **kwds)
Packit Service 21c75c
Packit Service 21c75c
    def sub_bold(self, haystack, needles, **kwds):
Packit Service 21c75c
        """Search the string *haystack* for all occurrences of any
Packit Service 21c75c
        string in the list *needles*.  Prefix each occurrence with
Packit Service 21c75c
        self.MODE['bold'], and postfix each occurrence with
Packit Service 21c75c
        self.MODE['normal'], then return the modified string.  This
Packit Service 21c75c
        will return a string that when printed to the terminal will
Packit Service 21c75c
        appear to be *haystack* with each occurrence of the strings in
Packit Service 21c75c
        *needles* in bold face.
Packit Service 21c75c
Packit Service 21c75c
        :param haystack: the string to be modified
Packit Service 21c75c
        :param needles: a list of strings to add the prefixes and
Packit Service 21c75c
           postfixes to
Packit Service 21c75c
        :return: *haystack* with self.MODE['bold'] prefixing, and
Packit Service 21c75c
          self.MODE['normal'] postfixing, occurrences of the strings
Packit Service 21c75c
          in *needles*
Packit Service 21c75c
        """
Packit Service 21c75c
        return self.sub_mode(haystack, 'bold', needles, **kwds)
Packit Service 21c75c
Packit Service 21c75c
    def sub_fg(self, haystack, color, needles, **kwds):
Packit Service 21c75c
        """Search the string *haystack* for all occurrences of any
Packit Service 21c75c
        string in the list *needles*.  Prefix each occurrence with
Packit Service 21c75c
        self.FG_COLOR[*color*], and postfix each occurrence with
Packit Service 21c75c
        self.MODE['normal'], then return the modified string.  This
Packit Service 21c75c
        will return a string that when printed to the terminal will
Packit Service 21c75c
        appear to be *haystack* with each occurrence of the strings in
Packit Service 21c75c
        *needles* in the given color.
Packit Service 21c75c
Packit Service 21c75c
        :param haystack: the string to be modified
Packit Service 21c75c
        :param color: the color to set the matches to be in.  Valid
Packit Service 21c75c
           values are given by self.FG_COLOR.keys().
Packit Service 21c75c
        :param needles: a list of strings to add the prefixes and
Packit Service 21c75c
           postfixes to
Packit Service 21c75c
        :return: *haystack* with self.FG_COLOR[*color*] prefixing, and
Packit Service 21c75c
          self.MODE['normal'] postfixing, occurrences of the strings
Packit Service 21c75c
          in *needles*
Packit Service 21c75c
        """
Packit Service 21c75c
        return self.sub_norm(haystack, self.FG_COLOR[color], needles, **kwds)
Packit Service 21c75c
Packit Service 21c75c
    def sub_bg(self, haystack, color, needles, **kwds):
Packit Service 21c75c
        """Search the string *haystack* for all occurrences of any
Packit Service 21c75c
        string in the list *needles*.  Prefix each occurrence with
Packit Service 21c75c
        self.BG_COLOR[*color*], and postfix each occurrence with
Packit Service 21c75c
        self.MODE['normal'], then return the modified string.  This
Packit Service 21c75c
        will return a string that when printed to the terminal will
Packit Service 21c75c
        appear to be *haystack* with each occurrence of the strings in
Packit Service 21c75c
        *needles* highlighted in the given background color.
Packit Service 21c75c
Packit Service 21c75c
        :param haystack: the string to be modified
Packit Service 21c75c
        :param color: the background color to set the matches to be in.  Valid
Packit Service 21c75c
           values are given by self.BG_COLOR.keys().
Packit Service 21c75c
        :param needles: a list of strings to add the prefixes and
Packit Service 21c75c
           postfixes to
Packit Service 21c75c
        :return: *haystack* with self.BG_COLOR[*color*] prefixing, and
Packit Service 21c75c
          self.MODE['normal'] postfixing, occurrences of the strings
Packit Service 21c75c
          in *needles*
Packit Service 21c75c
        """
Packit Service 21c75c
        return self.sub_norm(haystack, self.BG_COLOR[color], needles, **kwds)