def check(self): ''' Internal consistency check. ''' for tag in sorted(self._tag_up.keys()): ups = self._tag_up[tag] downs = self._tag_down.get(tag, 0) if ups != downs: warning("%s: ups=%d, downs=%d: tag %r", self, ups, downs, tag)
def unrfc2047(s): ''' Accept a string `s` containing RFC2047 text encodings (or the whitespace littered varieties that come from some low quality mail clients) and decode them into flat Unicode. See http://tools.ietf.org/html/rfc2047 for the specification. ''' if not isinstance(s, unicode): # TODO: should this come from the locale? that seems arbitrary as well s = unicode(s, FALLBACK_CHARSET) chunks = [] sofar = 0 for m in re_RFC2047.finditer(s): encoded_word = m.group() with Pfx(encoded_word): start = m.start() end = m.end() if start > sofar: chunks.append(s[sofar:start]) charset = m.group(1) encoding = m.group(2).upper() encoded_text = m.group(3) decoded = None realtext = None if encoding == 'B': try: decoded = base64.b64decode(encoded_text) except (ValueError, TypeError) as e: warning("%r: %s", encoded_text, e) realtext = encoded_word elif encoding == 'Q': try: decoded = quopri.decodestring(encoded_text.replace('_', ' ')) except (UnicodeEncodeError, ValueError) as e: warning("%r: %s", encoded_text, e) realtext = encoded_word else: warning("unhandled RFC2047 encoding %r, not decoding", encoding) realtext = encoded_word if realtext is None: try: realtext = decoded.decode(charset, 'replace') except LookupError as e: warning( "charset %r: %s; falling back to %r", charset, e, FALLBACK_CHARSET ) try: realtext = decoded.decode(FALLBACK_CHARSET, 'replace') except LookupError: warning("fallback charset %r: %s; not decoding", charset, e) realtext = encoded_word chunks.append(realtext) sofar = end if sofar < len(s): chunks.append(s[sofar:]) return unicode('').join(chunks)
def convert_sequence(sequence): ''' Convert a colour specification to an escape sequence. ''' escs = [] for colour_code in sequence.split(';'): try: escs.append(colour_escape(colour_code)) except KeyError as e: warning("%r: %r: %e", sequence, colour_code, e) return ''.join(escs)
def __init__( self, util_name=None, term_name=None, type_name=None, colors_dirpath=None, envvar=None, ): ''' Initialise the `TerminalColors` instance. Parameters: * `util_name`: optional utility name, default from `sys.argv[0]` * `term_name`: optional terminal name, default from the `$TERM` envvar * `type_name`: optional type name, default `'enable'` * `colors_dirpath`: optional specification files directory path, default from `TerminalColors.TERMINAL_COLORS_D` * `envvar`: environment variable to override matches; the default `util_name+'_COLORS'`, thus `$LS_COLORS` if `util_name=='ls'`. That may be the value `False` if no environment variable should be an override. ''' if util_name is None: try: util_name = basename(sys.argv[0]) except KeyError: util_name = '' if term_name is None: term_name = os.environ.get('TERM', 'dumb') if type_name is None: type_name = 'enable' if colors_dirpath is None: colors_dirpath = self.TERMINAL_COLORS_D if envvar is None: envvar = self.envvar = util_name.upper() + '_COLORS' self.util_name = util_name self.term_name = term_name self.type_name = type_name self.colors_dirpath = colors_dirpath self.envvar = envvar self._mapping = None # prefill the mapping if the environment variable is present if envvar is not False: envval = os.environ.get(envvar) if envval: m = {} for field in envval.strip().split(':'): if not field: continue try: name, sequence = field.strip().split('=', 1) except ValueError as e: warning("$%s: %r: %e", envvar, field, e) else: m[name] = self.convert_sequence(sequence) self._mapping = m
def placeApp(self, appname, startpos=0, allowDupe=False): ''' Place the named application at the first free slot at startpos or beyond. ''' if not allowDupe and self._byApp.get(appname, ()): warning("placeApp(%s, %d, allowDupe = %s): app exists at: %s", appname, startpos, allowDupe, self._byApp[appname]) pos = startpos while pos < len(self) and self[pos] is not None: pos += 1 self.placeIcon({"displayIdentifier": appname}, pos)
def stack_termios(fd=0, set_modes=None, clear_modes=None, strict=False): ''' Context manager to apply and restore changes to a tty. Yield the previous tty modes as from `termios.tcgetattr` or `None` if the changes could not be applied. If `strict`, raise an exception instead of yielding `None`. Parameters: * `fd`: optional tty file descriptor, default `0`. * `set_modes`: an optional mapping of attribute name to new value for values to set * `clear_modes`: an optional mapping of attribute name to new value for values to clear * `strict`: optional flag, default `False`; if true, raise exceptions from failed `tcgetattr` and `tcsetattr` calls otherwise issue a warning if the errno is not `ENOTTY` and proceed. This aims to provide ease of use in batch mode by default while providing a mode to fail overtly if required. The attribute names are from `iflag`, `oflag`, `cflag`, `lflag`, `ispeed`, `ospeed`, `cc`, corresponding to the list entries defined by the `termios.tcgetattr` call. For `set_modes`, the attributes `ispeed`, `ospeed` and `cc` are applied directly; the other attributes are binary ORed into the existing modes. For `clear_modes`, the attributes `ispeed`, `ospeed` and `cc` cannot be cleared; the other attributes are binary removed from the existing modes. For example, to turn off the terminal echo during some operation: with stack_termios(clear_modes={'lflag': termios.ECHO}): ... do something with tty echo disabled ... ''' try: restore_modes = modify_termios( fd, set_modes=set_modes, clear_modes=clear_modes, strict=strict ) yield restore_modes finally: if restore_modes: try: tcsetattr(fd, TCSANOW, restore_modes) except OSError as e: if strict: raise warning("tcsetattr(%d,TCSANOW,%r): %e", fd, restore_modes, e)
def status(msg, *args, **kwargs): ''' Write a message to the terminal's status line. Parameters: * `msg`: message string * `args`: if not empty, the message is %-formatted with `args` * `file`: optional keyword argument specifying the output file. Default: `sys.stderr`. Hack: if there is no status line use the xterm title bar sequence :-( ''' if args: msg = msg % args f = kwargs.pop('file', None) if kwargs: raise ValueError("unexpected keyword arguments: %r" % (kwargs,)) if f is None: f = sys.stderr try: has_ansi_status = f.has_ansi_status except AttributeError: try: import curses # pylint: disable=import-outside-toplevel except ImportError: has_ansi_status = None else: curses.setupterm() has_status = curses.tigetflag('hs') if has_status == -1: warning( 'status: curses.tigetflag(hs): not a Boolean capability, presuming false' ) has_ansi_status = None elif has_status > 0: has_ansi_status = ( curses.tigetstr('to_status_line'), curses.tigetstr('from_status_line') ) else: warning('status: hs=%s, presuming false', has_status) has_ansi_status = None f.has_ansi_status = has_ansi_status if has_ansi_status: msg = has_ansi_status[0] + msg + has_ansi_status[1] else: msg = '\033]0;' + msg + '\007' f.write(msg) f.flush()
def dec(self, tag=None): ''' Decrement the counter. Wake up any threads waiting for its new value. ''' with self._lock: self.value -= 1 if tag is not None: tag = str(tag) self._tag_down.setdefault(tag, 0) self._tag_down[tag] += 1 if self._tag_up.get(tag, 0) < self._tag_down[tag]: warning("%s.dec: more .decs than .incs for tag %r", self, tag) if self.value < 0: warning("%s.dec: value < 0!", self) self._notify()
def report_observation(self, attr): ''' Notify all the observers of the current value of `attr`. ''' val_attr = '_' + attr value = getattr(self, val_attr, None) for observer in self._observable_class__observers[attr]: try: observer(self, attr, value) except Exception as e: # pylint: disable=broad-except warning("%s.%s=%r: observer %s(...) raises: %s", self, val_attr, value, observer, e, exc_info=True)
def wrapped(*args, **kwargs): ''' Wrap `func` to emit an "OBSOLETE" warning before calling `func`. ''' frame = traceback.extract_stack(None, 2)[0] caller = frame[0], frame[1] # pylint: disable=protected-access try: callers = func._OBSOLETE_callers except AttributeError: callers = func._OBSOLETE_callers = set() if caller not in callers: callers.add(caller) warning("OBSOLETE call to %s:%d %s(), called from %s:%d %s", func.__code__.co_filename, func.__code__.co_firstlineno, func.__name__, frame[0], frame[1], frame[2]) return func(*args, **kwargs)
def scan(self, path=None): ''' Scan the colour specification in `path` and yield `(name,escape_sequence)` tuples. ''' if path is None: path = self.find_specfile() if path is None: # no matching specfile return with open(path) as f: # pylint: disable=unspecified-encoding for lineno, line in enumerate(f, 1): line = line.strip() if not line or line.startswith('#'): continue try: name, sequence = line.split() except ValueError as e: warning("%s, %d: %s", path, lineno, e) continue yield name, self.convert_sequence(sequence)
def modify_termios(fd=0, set_modes=None, clear_modes=None, strict=False): ''' Apply mode changes to a tty. Return the previous tty modes as from `termios.tcgetattr` or `None` if the changes could not be applied. If `strict`, raise an exception instead of returning `None`. Parameters: * `fd`: optional tty file descriptor, default `0`. * `set_modes`: an optional mapping of attribute name to new value for values to set * `clear_modes`: an optional mapping of attribute name to new value for values to clear * `strict`: optional flag, default `False`; if true, raise exceptions from failed `tcgetattr` and `tcsetattr` calls otherwise issue a warning if the errno is not `ENOTTY` and proceed. This aims to provide ease of use in batch mode by default while providing a mode to fail overtly if required. The attribute names are from `iflag`, `oflag`, `cflag`, `lflag`, `ispeed`, `ospeed`, `cc`, corresponding to the list entries defined by the `termios.tcgetattr` call. For `set_modes`, the attributes `ispeed`, `ospeed` and `cc` are applied directly; the other attributes are binary ORed into the existing modes. For `clear_modes`, the attributes `ispeed`, `ospeed` and `cc` cannot be cleared; the other attributes are binary removed from the existing modes. For example, to turn off the terminal echo during some operation: old_modes = apply_termios(clear_modes={'lflag': termios.ECHO}): ... do something with tty echo disabled ... if old_modes: termios.tcsetattr(fd, termios.TCSANOW, old_modes) ''' if set_modes: if not all(map(lambda k: k in _termios_modes_names, set_modes.keys())): raise ValueError( "set_modes: invalid mode keys: known=%r, supplied=%r" % (sorted(_termios_modes_names.keys()), set_modes) ) if clear_modes: if not all(map(lambda k: k in _termios_modes_names, clear_modes.keys())): raise ValueError( "clear_modes: invalid mode keys: known=%r, supplied=%r" % (sorted(_termios_modes_names.keys()), clear_modes) ) for k in 'ispeed', 'ospeed', 'cc': if k in clear_modes: raise ValueError("clear_modes: cannot clear %r" % (k,)) try: original_modes = tcgetattr(fd) except OSError as e: if strict: raise if e.errno != errno.ENOTTY: warning("tcgetattr(%d): %s", fd, e) original_modes = None restore_modes = None if original_modes: new_modes = list(original_modes) if set_modes: for k, v in set_modes.items(): i = _termios_modes_names[k] if k in ('ispeed', 'ospeed', 'cc'): new_modes[i] = v else: new_modes[i] |= v if clear_modes: for k, v in clear_modes.items(): i = _termios_modes_names[k] new_modes[i] &= ~v if new_modes == original_modes: restore_modes = None else: try: tcsetattr(fd, TCSANOW, new_modes) except OSError as e: if strict: raise warning("tcsetattr(%d,TCSANOW,%r): %e", fd, new_modes, e) else: restore_modes = original_modes return restore_modes
def cachedmethod_wrapper(self, *a, **kw): with Pfx("%s.%s", self, attr): now = None value0 = getattr(self, val_attr, unset_value) sig0 = getattr(self, sig_attr, None) sig = getattr(self, sig_attr, None) if value0 is unset_value: # value unknown, needs compute pass # we have a cached value for return in the following logic elif poll_delay is None: return value0 # see if the value is stale lastpoll = getattr(self, lastpoll_attr, None) now = time.time() if lastpoll is not None and now - lastpoll < poll_delay: # reuse cache return value0 # update the poll time if we use it setattr(self, lastpoll_attr, now) # check the signature if provided # see if the signature is unchanged if sig_func is not None: try: sig = sig_func(self) except Exception as e: # pylint: disable=broad-except # signature function fails, use the cache warning("sig func %s(self): %s", sig_func, e, exc_info=True) return value0 if sig0 is not None and sig0 == sig: # signature unchanged return value0 # update signature setattr(self, sig_attr, sig) # compute the current value try: value = method(self, *a, **kw) except Exception as e: # pylint: disable=broad-except # computation fails, return cached value if value0 is unset_value: # no cached value raise warning("exception calling %s(self): %s", method, e, exc_info=True) return value0 # update the cache setattr(self, val_attr, value) # bump revision if the value changes # noncomparable values are always presumed changed changed = value0 is unset_value or value0 is not value if not changed: try: changed = value0 != value except TypeError: changed = True if changed: setattr(self, rev_attr, (getattr(self, rev_attr, None) or 0) + 1) return value