diff --git a/powerline/lib/threaded.py b/powerline/lib/threaded.py index b253ae8f..acb258c3 100644 --- a/powerline/lib/threaded.py +++ b/powerline/lib/threaded.py @@ -2,9 +2,11 @@ from __future__ import absolute_import -from powerline.lib.monotonic import monotonic - from threading import Thread, Lock, Event +from types import MethodType + +from powerline.lib.monotonic import monotonic +from powerline.segments import Segment class MultiRunnedThread(object): @@ -28,12 +30,14 @@ class MultiRunnedThread(object): return None -class ThreadedSegment(MultiRunnedThread): +class ThreadedSegment(Segment, MultiRunnedThread): min_sleep_time = 0.1 update_first = True interval = 1 daemon = False + argmethods = ('render', 'set_state') + def __init__(self): super(ThreadedSegment, self).__init__() self.run_once = True @@ -145,10 +149,34 @@ class ThreadedSegment(MultiRunnedThread): def debug(self, *args, **kwargs): self.pl.debug(prefix=self.__class__.__name__, *args, **kwargs) + def argspecobjs(self): + for name in self.argmethods: + try: + yield name, getattr(self, name) + except AttributeError: + pass + + def additional_args(self): + return (('interval', self.interval),) + + def omitted_args(self, name, method): + if isinstance(getattr(self, name, None), MethodType): + omitted_indexes = (0,) + else: + omitted_indexes = () + if name.startswith('render'): + if omitted_indexes: + omitted_indexes += (1,) + else: + omitted_indexes = (0,) + return omitted_indexes + class KwThreadedSegment(ThreadedSegment): update_first = True + argmethods = ('render', 'set_state', 'key', 'render_one') + def __init__(self): super(KwThreadedSegment, self).__init__() self.updated = True diff --git a/powerline/lint/inspect.py b/powerline/lint/inspect.py index dc7b0182..58d0dc05 100644 --- a/powerline/lint/inspect.py +++ b/powerline/lint/inspect.py @@ -1,72 +1,63 @@ # vim:fileencoding=utf-8:noet from __future__ import absolute_import + from inspect import ArgSpec, getargspec -from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment from itertools import count +from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment +from powerline.segments import Segment + def getconfigargspec(obj): - if isinstance(obj, ThreadedSegment): - args = ['interval'] - defaults = [getattr(obj, 'interval', 1)] - if obj.update_first: - args.append('update_first') - defaults.append(True) - methods = ['render', 'set_state'] - if isinstance(obj, KwThreadedSegment): - methods += ['key', 'render_one'] - - for method in methods: - if hasattr(obj, method): - # Note: on = i: - default = argspec.defaults[-i] - defaults.append(default) - args.append(arg) - else: - args.insert(0, arg) - argspec = ArgSpec(args=args, varargs=None, keywords=None, defaults=tuple(defaults)) + if hasattr(obj, 'powerline_origin'): + obj = obj.powerline_origin else: - if hasattr(obj, 'powerline_origin'): - obj = obj.powerline_origin - else: - obj = obj + obj = obj - remove_self = False - try: - argspec = getargspec(obj) - except TypeError: # Workaround for now_playing segment - # TODO For NowPlayingSegment for linter: merge in information about - # player-specific arguments - argspec = getargspec(obj.__call__) - remove_self = True - args = [] - defaults = [] - for i, arg in zip(count(1), reversed(argspec.args)): + args = [] + defaults = [] + + if isinstance(obj, Segment): + additional_args = obj.additional_args() + argspecobjs = obj.argspecobjs() + get_omitted_args = obj.omitted_args + else: + additional_args = () + argspecobjs = ((None, obj),) + get_omitted_args = lambda *args: () + + for arg in additional_args: + args.append(arg[0]) + if len(arg) > 1: + defaults.append(arg[1]) + + requires_segment_info = getattr(obj, 'powerline_requires_segment_info', False) + requires_filesystem_watcher = getattr(obj, 'powerline_requires_filesystem_watcher', False) + + for name, method in argspecobjs: + argspec = getargspec(method) + omitted_args = get_omitted_args(name, method) + largs = len(argspec.args) + for i, arg in enumerate(reversed(argspec.args)): if ( - (arg == 'segment_info' and getattr(obj, 'powerline_requires_segment_info', None)) + largs - (i + 1) in omitted_args or arg == 'pl' - or (arg == 'self' and remove_self) + or (arg == 'create_watcher' and requires_filesystem_watcher) + or (arg == 'segment_info' and requires_segment_info) ): continue - if argspec.defaults and len(argspec.defaults) >= i: - default = argspec.defaults[-i] + if argspec.defaults and len(argspec.defaults) > i: + if arg in args: + idx = args.index(arg) + if len(args) - idx > len(defaults): + args.pop(idx) + else: + continue + default = argspec.defaults[-(i + 1)] defaults.append(default) args.append(arg) else: - args.insert(0, arg) - argspec = ArgSpec(args=args, varargs=argspec.varargs, keywords=argspec.keywords, defaults=tuple(defaults)) + if arg not in args: + args.insert(0, arg) - return argspec + return ArgSpec(args=args, varargs=None, keywords=None, defaults=tuple(defaults)) diff --git a/powerline/segment.py b/powerline/segment.py index 8d5a956c..97538813 100644 --- a/powerline/segment.py +++ b/powerline/segment.py @@ -1,10 +1,10 @@ # vim:fileencoding=utf-8:noet - from __future__ import absolute_import, unicode_literals, division, print_function -from powerline.lib.file_watcher import create_file_watcher import sys +from powerline.lib.file_watcher import create_file_watcher + def list_segment_key_values(segment, theme_configs, key, module=None, default=None): try: diff --git a/powerline/segments/__init__.py b/powerline/segments/__init__.py index 3ad9513f..3c2da39c 100644 --- a/powerline/segments/__init__.py +++ b/powerline/segments/__init__.py @@ -1,2 +1,51 @@ +# vim:fileencoding=utf-8:noet +from __future__ import absolute_import + +import sys + from pkgutil import extend_path +from types import MethodType + + __path__ = extend_path(__path__, __name__) + + +class Segment(object): + '''Base class for any segment that is not a function + + Required for powerline.lint.inspect to work properly. + ''' + if sys.version_info < (3, 4): + def argspecobjs(self): + yield '__call__', self.__call__ + else: + def argspecobjs(self): # NOQA + yield '__call__', self + + argspecobjs.__doc__ = ( + '''Return a list of valid arguments for inspect.getargspec + + Used to determine function arguments. + ''' + ) + + def omitted_args(self, name, method): + '''List arguments which should be omitted + + Returns a tuple with indexes of omitted arguments. + + .. note::``segment_info``, ``create_watcher`` and ``pl`` will be omitted + regardless of the below return (for ``segment_info`` and + ``create_watcher``: only if object was marked to require segment + info or filesystem watcher). + ''' + if isinstance(self.__call__, MethodType): + return (0,) + else: + return () + + @staticmethod + def additional_args(): + '''Returns a list of (additional argument name[, default value]) tuples. + ''' + return () diff --git a/powerline/segments/common.py b/powerline/segments/common.py index a0e54492..365a92da 100644 --- a/powerline/segments/common.py +++ b/powerline/segments/common.py @@ -19,6 +19,7 @@ from powerline.lib.monotonic import monotonic from powerline.lib.humanize_bytes import humanize_bytes from powerline.lib.unicode import u from powerline.theme import requires_segment_info, requires_filesystem_watcher +from powerline.segments import Segment from collections import namedtuple @@ -904,7 +905,7 @@ STATE_SYMBOLS = { } -class NowPlayingSegment(object): +class NowPlayingSegment(Segment): def __call__(self, player='mpd', format='{state_symbol} {artist} - {title} ({total})', state_symbols=STATE_SYMBOLS, **kwargs): player_func = getattr(self, 'player_{0}'.format(player)) stats = {