mirror of
https://github.com/powerline/powerline.git
synced 2025-04-08 19:25:04 +02:00
Merge remote-tracking branch 'zyx-i/threaded' into develop
This commit is contained in:
commit
f0f1f3f85e
2
.local.vimrc
Normal file
2
.local.vimrc
Normal file
@ -0,0 +1,2 @@
|
||||
setlocal noexpandtab
|
||||
let g:syntastic_python_flake8_args = '--ignore=W191,E501,E121,E122,E123,E128'
|
@ -3,9 +3,10 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(os.getcwd()))))
|
||||
sys.path.insert(0, os.path.abspath(os.getcwd()))
|
||||
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
|
||||
extensions = ['powerline_autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
|
||||
source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
project = u'Powerline'
|
||||
|
78
docs/source/powerline_autodoc.py
Normal file
78
docs/source/powerline_autodoc.py
Normal file
@ -0,0 +1,78 @@
|
||||
from sphinx.ext import autodoc
|
||||
from sphinx.util.inspect import getargspec
|
||||
from inspect import ArgSpec, getargspec, formatargspec
|
||||
from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment
|
||||
from itertools import count
|
||||
|
||||
try:
|
||||
from __builtin__ import unicode
|
||||
except ImportError:
|
||||
unicode = lambda s, enc: s
|
||||
|
||||
|
||||
def formatvalue(val):
|
||||
if type(val) is str:
|
||||
return '="' + unicode(val, 'utf-8').replace('"', '\\"').replace('\\', '\\\\') + '"'
|
||||
else:
|
||||
return '=' + repr(val)
|
||||
|
||||
|
||||
class ThreadedDocumenter(autodoc.FunctionDocumenter):
|
||||
'''Specialized documenter subclass for ThreadedSegment subclasses.'''
|
||||
@classmethod
|
||||
def can_document_member(cls, member, membername, isattr, parent):
|
||||
return (isinstance(member, ThreadedSegment) or
|
||||
super(ThreadedDocumenter, cls).can_document_member(member, membername, isattr, parent))
|
||||
|
||||
def format_args(self):
|
||||
if isinstance(self.object, ThreadedSegment):
|
||||
args = ['interval']
|
||||
defaults = [getattr(self.object, 'interval', 1)]
|
||||
methods = ['render', 'set_state']
|
||||
if isinstance(self.object, KwThreadedSegment):
|
||||
methods += ['key', 'render_one']
|
||||
|
||||
for method in methods:
|
||||
if hasattr(self.object, method):
|
||||
# Note: on <python-2.6 it may return simple tuple, not
|
||||
# ArgSpec instance.
|
||||
argspec = getargspec(getattr(self.object, method))
|
||||
for i, arg in zip(count(-1, -1), reversed(argspec.args)):
|
||||
if (arg == 'self' or
|
||||
(arg == 'segment_info' and
|
||||
getattr(self.object, 'powerline_requires_segment_info', None)) or
|
||||
(method == 'render_one' and -i == len(argspec.args))):
|
||||
continue
|
||||
if argspec.defaults and len(argspec.defaults) >= -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))
|
||||
else:
|
||||
if hasattr(self.object, 'powerline_origin'):
|
||||
obj = self.object.powerline_origin
|
||||
else:
|
||||
obj = self.object
|
||||
|
||||
argspec = getargspec(obj)
|
||||
args = []
|
||||
defaults = []
|
||||
for i, arg in zip(count(-1, -1), reversed(argspec.args)):
|
||||
if (arg == 'segment_info' and getattr(self.object, 'powerline_requires_segment_info', None)):
|
||||
continue
|
||||
if argspec.defaults and len(argspec.defaults) >= -i:
|
||||
default = argspec.defaults[i]
|
||||
defaults.append(default)
|
||||
args.append(arg)
|
||||
else:
|
||||
args.insert(0, arg)
|
||||
argspec = ArgSpec(args=args, varargs=argspec.varargs, keywords=argspec.keywords, defaults=tuple(defaults))
|
||||
|
||||
return formatargspec(*argspec, formatvalue=formatvalue).replace('\\', '\\\\')
|
||||
|
||||
|
||||
def setup(app):
|
||||
autodoc.setup(app)
|
||||
app.add_autodocumenter(ThreadedDocumenter)
|
@ -36,7 +36,7 @@ class Powerline(object):
|
||||
the package imported like this: ``powerline.renders.{render_module}``.
|
||||
'''
|
||||
|
||||
def __init__(self, ext, renderer_module=None):
|
||||
def __init__(self, ext, renderer_module=None, run_once=False):
|
||||
self.config_paths = self.get_config_paths()
|
||||
|
||||
# Load main config file
|
||||
@ -58,6 +58,7 @@ class Powerline(object):
|
||||
'ext': ext,
|
||||
'common_config': common_config,
|
||||
'segment_info': self.get_segment_info(),
|
||||
'run_once': run_once,
|
||||
}
|
||||
local_themes = self.get_local_themes(ext_config.get('local_themes'))
|
||||
|
||||
@ -69,11 +70,12 @@ class Powerline(object):
|
||||
except ImportError as e:
|
||||
sys.stderr.write('Error while importing renderer module: {0}\n'.format(e))
|
||||
sys.exit(1)
|
||||
options = {'term_truecolor': common_config.get('term_truecolor', False),
|
||||
'ambiwidth': common_config.get('ambiwidth', 1),
|
||||
'tmux_escape': common_config.get('additional_escapes') == 'tmux',
|
||||
'screen_escape': common_config.get('additional_escapes') == 'screen',
|
||||
}
|
||||
options = {
|
||||
'term_truecolor': common_config.get('term_truecolor', False),
|
||||
'ambiwidth': common_config.get('ambiwidth', 1),
|
||||
'tmux_escape': common_config.get('additional_escapes') == 'tmux',
|
||||
'screen_escape': common_config.get('additional_escapes') == 'screen',
|
||||
}
|
||||
self.renderer = Renderer(theme_config, local_themes, theme_kwargs, colorscheme, **options)
|
||||
|
||||
@staticmethod
|
||||
|
@ -2,6 +2,7 @@
|
||||
from powerline.ipython import IpythonPowerline
|
||||
|
||||
from IPython.core.prompts import PromptManager
|
||||
from IPython.core.hooks import TryNext
|
||||
|
||||
|
||||
class PowerlinePromptManager(PromptManager):
|
||||
@ -41,6 +42,12 @@ def load_ipython_extension(ip):
|
||||
ip.prompt_manager = PowerlinePromptManager(powerline=powerline,
|
||||
shell=ip.prompt_manager.shell, config=ip.prompt_manager.config)
|
||||
|
||||
def shutdown_hook():
|
||||
powerline.renderer.shutdown()
|
||||
raise TryNext()
|
||||
|
||||
ip.hooks.shutdown_hook.add(shutdown_hook)
|
||||
|
||||
|
||||
def unload_ipython_extension(ip):
|
||||
ip.prompt_manager = old_prompt_manager
|
||||
|
@ -2,6 +2,7 @@
|
||||
from powerline.ipython import IpythonPowerline
|
||||
from IPython.Prompts import BasePrompt
|
||||
from IPython.ipapi import get as get_ipython
|
||||
from IPython.ipapi import TryNext
|
||||
|
||||
|
||||
class PowerlinePrompt(BasePrompt):
|
||||
@ -38,4 +39,11 @@ def setup(prompt='1', **kwargs):
|
||||
old_prompt = getattr(ip.IP.outputcache, attr)
|
||||
setattr(ip.IP.outputcache, attr, PowerlinePrompt(powerline,
|
||||
old_prompt.cache, old_prompt.sep, '', old_prompt.pad_left))
|
||||
raise TryNext()
|
||||
|
||||
def shutdown_hook():
|
||||
powerline.renderer.shutdown()
|
||||
raise TryNext()
|
||||
|
||||
ip.IP.hooks.late_startup_hook.add(late_startup_hook)
|
||||
ip.IP.hooks.shutdown_hook.add(shutdown_hook)
|
||||
|
@ -92,4 +92,5 @@ augroup Powerline
|
||||
autocmd!
|
||||
autocmd ColorScheme * :exec s:powerline_pycmd 'powerline.renderer.reset_highlight()'
|
||||
autocmd VimEnter * :redrawstatus!
|
||||
autocmd VimLeave * :exec s:powerline_pycmd 'powerline.renderer.shutdown()'
|
||||
augroup END
|
||||
|
@ -2,9 +2,13 @@
|
||||
from functools import wraps
|
||||
import json
|
||||
|
||||
from powerline.lib.memoize import memoize # NOQA
|
||||
from powerline.lib.humanize_bytes import humanize_bytes # NOQA
|
||||
from powerline.lib.url import urllib_read, urllib_urlencode # NOQA
|
||||
|
||||
def wraps_saveargs(wrapped):
|
||||
def dec(wrapper):
|
||||
r = wraps(wrapped)(wrapper)
|
||||
r.powerline_origin = getattr(wrapped, 'powerline_origin', wrapped)
|
||||
return r
|
||||
return dec
|
||||
|
||||
|
||||
def mergedicts(d1, d2):
|
||||
@ -19,7 +23,7 @@ def mergedicts(d1, d2):
|
||||
|
||||
def add_divider_highlight_group(highlight_group):
|
||||
def dec(func):
|
||||
@wraps(func)
|
||||
@wraps_saveargs(func)
|
||||
def f(**kwargs):
|
||||
r = func(**kwargs)
|
||||
if r:
|
||||
|
@ -1,7 +1,7 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
|
||||
from functools import wraps
|
||||
import time
|
||||
from powerline.lib.time import monotonic
|
||||
|
||||
|
||||
def default_cache_key(**kwargs):
|
||||
@ -28,10 +28,13 @@ class memoize(object):
|
||||
cached = self.cache.get(key, None)
|
||||
except TypeError:
|
||||
return func(**kwargs)
|
||||
if cached is None or time.time() - cached['time'] > self.timeout:
|
||||
# Handle case when time() appears to be less then cached['time'] due
|
||||
# to clock updates. Not applicable for monotonic clock, but this
|
||||
# case is currently rare.
|
||||
if cached is None or not (cached['time'] < monotonic() < cached['time'] + self.timeout):
|
||||
cached = self.cache[key] = {
|
||||
'result': func(**kwargs),
|
||||
'time': time.time(),
|
||||
'time': monotonic(),
|
||||
}
|
||||
return cached['result']
|
||||
return decorated_function
|
||||
|
131
powerline/lib/threaded.py
Normal file
131
powerline/lib/threaded.py
Normal file
@ -0,0 +1,131 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from powerline.lib.time import monotonic
|
||||
|
||||
from time import sleep
|
||||
from threading import Thread, Lock
|
||||
|
||||
|
||||
class ThreadedSegment(Thread):
|
||||
daemon = True
|
||||
min_sleep_time = 0.1
|
||||
|
||||
def __init__(self):
|
||||
super(ThreadedSegment, self).__init__()
|
||||
self.update_lock = Lock()
|
||||
self.write_lock = Lock()
|
||||
self.keep_going = True
|
||||
self.run_once = True
|
||||
self.did_set_interval = False
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
if self.run_once:
|
||||
self.set_state(**kwargs)
|
||||
self.update()
|
||||
elif not self.is_alive():
|
||||
self.startup(**kwargs)
|
||||
|
||||
with self.write_lock:
|
||||
return self.render(**kwargs)
|
||||
|
||||
def sleep(self, adjust_time):
|
||||
sleep(max(self.interval - adjust_time, self.min_sleep_time))
|
||||
|
||||
def run(self):
|
||||
while self.keep_going:
|
||||
start_time = monotonic()
|
||||
|
||||
with self.update_lock:
|
||||
self.update()
|
||||
|
||||
self.sleep(monotonic() - start_time)
|
||||
|
||||
def shutdown(self):
|
||||
self.keep_going = False
|
||||
self.update_lock.acquire()
|
||||
|
||||
def set_interval(self, interval=None, **kwargs):
|
||||
# Allowing “interval” keyword in configuration.
|
||||
# Note: Here **kwargs is needed to support foreign data, in subclasses
|
||||
# it can be seen in a number of places in order to support
|
||||
# .set_interval().
|
||||
interval = interval or getattr(self, 'interval', 1)
|
||||
self.interval = interval
|
||||
self.has_set_interval = True
|
||||
|
||||
def set_state(self, **kwargs):
|
||||
if not self.did_set_interval:
|
||||
self.set_interval(**kwargs)
|
||||
|
||||
def startup(self, **kwargs):
|
||||
# Normally .update() succeeds to run before value is requested, meaning
|
||||
# that user is getting values he needs directly at vim startup. Without
|
||||
# .startup() we will not have to wait long until receiving bug “I opened
|
||||
# vim, but branch information is only shown after I move cursor”.
|
||||
self.run_once = False
|
||||
|
||||
self.set_state(**kwargs)
|
||||
|
||||
if not self.is_alive():
|
||||
self.start()
|
||||
|
||||
|
||||
def printed(func):
|
||||
def f(*args, **kwargs):
|
||||
print(func.__name__)
|
||||
return func(*args, **kwargs)
|
||||
return f
|
||||
|
||||
|
||||
class KwThreadedSegment(ThreadedSegment):
|
||||
drop_interval = 10 * 60
|
||||
|
||||
def __init__(self):
|
||||
super(KwThreadedSegment, self).__init__()
|
||||
self.queries = {}
|
||||
self.update_missing = True
|
||||
|
||||
@staticmethod
|
||||
def key(**kwargs):
|
||||
return frozenset(kwargs.items())
|
||||
|
||||
def render(self, **kwargs):
|
||||
key = self.key(**kwargs)
|
||||
try:
|
||||
update_state = self.queries[key][1]
|
||||
except KeyError:
|
||||
update_state = self.compute_state(key) if self.update_missing else None
|
||||
# No locks: render method is already running with write_lock acquired.
|
||||
self.queries[key] = (monotonic(), update_state)
|
||||
return self.render_one(update_state, **kwargs)
|
||||
|
||||
def update(self):
|
||||
updates = {}
|
||||
removes = []
|
||||
for key, (last_query_time, state) in list(self.queries.items()):
|
||||
if last_query_time < monotonic() < last_query_time + self.drop_interval:
|
||||
updates[key] = (last_query_time, self.compute_state(key))
|
||||
else:
|
||||
removes.append(key)
|
||||
with self.write_lock:
|
||||
self.queries.update(updates)
|
||||
for key in removes:
|
||||
self.queries.pop(key)
|
||||
|
||||
def set_state(self, **kwargs):
|
||||
if not self.did_set_interval or ('interval' in kwargs and self.interval > kwargs['interval']):
|
||||
self.set_interval(**kwargs)
|
||||
|
||||
key = self.key(**kwargs)
|
||||
self.queries[key] = (monotonic(), None)
|
||||
|
||||
@staticmethod
|
||||
def render_one(update_state, **kwargs):
|
||||
return update_state
|
||||
|
||||
|
||||
def with_docstring(instance, doc):
|
||||
instance.__doc__ = doc
|
||||
return instance
|
103
powerline/lib/time.py
Normal file
103
powerline/lib/time.py
Normal file
@ -0,0 +1,103 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
try:
|
||||
try:
|
||||
# >=python-3.3, Unix
|
||||
from time import clock_gettime
|
||||
try:
|
||||
# >={kernel}-sources-2.6.28
|
||||
from time import CLOCK_MONOTONIC_RAW as CLOCK_ID
|
||||
except ImportError:
|
||||
from time import CLOCK_MONOTONIC as CLOCK_ID # NOQA
|
||||
|
||||
monotonic = lambda: clock_gettime(CLOCK_ID)
|
||||
|
||||
except ImportError:
|
||||
# >=python-3.3
|
||||
from time import monotonic # NOQA
|
||||
|
||||
except ImportError:
|
||||
import ctypes
|
||||
import sys
|
||||
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
# Windows only
|
||||
GetTickCount64 = ctypes.windll.kernel32.GetTickCount64
|
||||
GetTickCount64.restype = ctypes.c_ulonglong
|
||||
|
||||
def monotonic(): # NOQA
|
||||
return GetTickCount64() / 1000
|
||||
|
||||
elif sys.platform == 'darwin':
|
||||
# Mac OS X
|
||||
from ctypes.util import find_library
|
||||
|
||||
libc_name = find_library('c')
|
||||
if not libc_name:
|
||||
raise OSError
|
||||
|
||||
libc = ctypes.CDLL(libc_name, use_errno=True)
|
||||
|
||||
mach_absolute_time = libc.mach_absolute_time
|
||||
mach_absolute_time.argtypes = ()
|
||||
mach_absolute_time.restype = ctypes.c_uint64
|
||||
|
||||
class mach_timebase_info_data_t(ctypes.Structure):
|
||||
_fields_ = (
|
||||
('numer', ctypes.c_uint32),
|
||||
('denom', ctypes.c_uint32),
|
||||
)
|
||||
mach_timebase_info_data_p = ctypes.POINTER(mach_timebase_info_data_t)
|
||||
|
||||
_mach_timebase_info = libc.mach_timebase_info
|
||||
_mach_timebase_info.argtypes = (mach_timebase_info_data_p,)
|
||||
_mach_timebase_info.restype = ctypes.c_int
|
||||
|
||||
def mach_timebase_info():
|
||||
timebase = mach_timebase_info_data_t()
|
||||
_mach_timebase_info(ctypes.byref(timebase))
|
||||
return (timebase.numer, timebase.denom)
|
||||
|
||||
timebase = mach_timebase_info()
|
||||
factor = timebase[0] / timebase[1] * 1e-9
|
||||
|
||||
def monotonic(): # NOQA
|
||||
return mach_absolute_time() * factor
|
||||
else:
|
||||
# linux only (no librt on OS X)
|
||||
import os
|
||||
|
||||
# See <bits/time.h>
|
||||
CLOCK_MONOTONIC = 1
|
||||
CLOCK_MONOTONIC_RAW = 4
|
||||
|
||||
class timespec(ctypes.Structure):
|
||||
_fields_ = (
|
||||
('tv_sec', ctypes.c_long),
|
||||
('tv_nsec', ctypes.c_long)
|
||||
)
|
||||
tspec = timespec()
|
||||
|
||||
librt = ctypes.CDLL('librt.so.1', use_errno=True)
|
||||
clock_gettime = librt.clock_gettime
|
||||
clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]
|
||||
|
||||
if clock_gettime(CLOCK_MONOTONIC_RAW, ctypes.pointer(tspec)) == 0:
|
||||
# >={kernel}-sources-2.6.28
|
||||
clock_id = CLOCK_MONOTONIC_RAW
|
||||
elif clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(tspec)) == 0:
|
||||
clock_id = CLOCK_MONOTONIC
|
||||
else:
|
||||
raise OSError
|
||||
|
||||
def monotonic(): # NOQA
|
||||
if clock_gettime(CLOCK_MONOTONIC, ctypes.pointer(tspec)) != 0:
|
||||
errno_ = ctypes.get_errno()
|
||||
raise OSError(errno_, os.strerror(errno_))
|
||||
return tspec.tv_sec + tspec.tv_nsec / 1e9
|
||||
|
||||
except:
|
||||
from time import time as monotonic # NOQA
|
@ -1,26 +1,16 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
|
||||
try:
|
||||
from urllib.error import HTTPError
|
||||
from urllib.request import urlopen
|
||||
from urllib.parse import urlencode as urllib_urlencode # NOQA
|
||||
except ImportError:
|
||||
from urllib2 import urlopen, HTTPError # NOQA
|
||||
from urllib import urlencode as urllib_urlencode # NOQA
|
||||
|
||||
|
||||
def urllib_read(url):
|
||||
try:
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
try:
|
||||
return urllib.request.urlopen(url, timeout=5).read().decode('utf-8')
|
||||
except:
|
||||
return
|
||||
except ImportError:
|
||||
import urllib2
|
||||
try:
|
||||
return urllib2.urlopen(url, timeout=5).read()
|
||||
except:
|
||||
return
|
||||
|
||||
|
||||
def urllib_urlencode(string):
|
||||
try:
|
||||
import urllib.parse
|
||||
return urllib.parse.urlencode(string)
|
||||
except ImportError:
|
||||
import urllib
|
||||
return urllib.urlencode(string)
|
||||
return urlopen(url, timeout=10).read().decode('utf-8')
|
||||
except HTTPError:
|
||||
return
|
||||
|
@ -1,7 +1,6 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import absolute_import
|
||||
import os
|
||||
from powerline.lib.memoize import memoize
|
||||
|
||||
|
||||
vcs_props = (
|
||||
@ -16,12 +15,11 @@ def generate_directories(path):
|
||||
while True:
|
||||
old_path = path
|
||||
path = os.path.dirname(path)
|
||||
if path == old_path:
|
||||
if path == old_path or not path:
|
||||
break
|
||||
yield path
|
||||
|
||||
|
||||
@memoize(100)
|
||||
def guess(path):
|
||||
for directory in generate_directories(path):
|
||||
for vcs, vcs_dir, check in vcs_props:
|
||||
|
@ -97,8 +97,9 @@ except ImportError:
|
||||
def readlines(cmd, cwd):
|
||||
p = Popen(cmd, shell=False, stdout=PIPE, stderr=PIPE, cwd=cwd)
|
||||
p.stderr.close()
|
||||
for line in p.stdout:
|
||||
yield line[:-1].decode('utf-8')
|
||||
with p.stdout:
|
||||
for line in p.stdout:
|
||||
yield line[:-1].decode('utf-8')
|
||||
|
||||
class Repository(object):
|
||||
__slots__ = ('directory',)
|
||||
|
@ -26,12 +26,12 @@ class Renderer(object):
|
||||
self.theme_kwargs = theme_kwargs
|
||||
self.colorscheme = colorscheme
|
||||
self.width_data = {
|
||||
'N': 1, # Neutral
|
||||
'Na': 1, # Narrow
|
||||
'A': getattr(self, 'ambiwidth', 1), # Ambigious
|
||||
'H': 1, # Half-width
|
||||
'W': 2, # Wide
|
||||
'F': 2, # Fullwidth
|
||||
'N': 1, # Neutral
|
||||
'Na': 1, # Narrow
|
||||
'A': getattr(self, 'ambiwidth', 1), # Ambigious
|
||||
'H': 1, # Half-width
|
||||
'W': 2, # Wide
|
||||
'F': 2, # Fullwidth
|
||||
}
|
||||
|
||||
def strwidth(self, string):
|
||||
@ -40,6 +40,9 @@ class Renderer(object):
|
||||
def get_theme(self, matcher_info):
|
||||
return self.theme
|
||||
|
||||
def shutdown(self):
|
||||
self.theme.shutdown()
|
||||
|
||||
def get_highlighting(self, segment, mode):
|
||||
segment['highlight'] = self.colorscheme.get_highlighting(segment['highlight_group'], mode, segment.get('gradient_level'))
|
||||
if segment['divider_highlight_group']:
|
||||
|
@ -31,6 +31,12 @@ class VimRenderer(Renderer):
|
||||
super(VimRenderer, self).__init__(*args, **kwargs)
|
||||
self.hl_groups = {}
|
||||
|
||||
def shutdown(self):
|
||||
self.theme.shutdown()
|
||||
for match in self.local_themes.values():
|
||||
if 'theme' in match:
|
||||
match['theme'].shutdown()
|
||||
|
||||
def add_local_theme(self, matcher, theme):
|
||||
if matcher in self.local_themes:
|
||||
raise KeyError('There is already a local theme with given matcher')
|
||||
|
@ -79,6 +79,8 @@ def gen_segment_getter(ext, path, theme_configs, default_module=None):
|
||||
'include_modes': segment.get('include_modes', []),
|
||||
'width': segment.get('width'),
|
||||
'align': segment.get('align', 'l'),
|
||||
'shutdown': getattr(contents_func, 'shutdown', None),
|
||||
'startup': getattr(contents_func, 'startup', None),
|
||||
'_rendered_raw': '',
|
||||
'_rendered_hl': '',
|
||||
'_len': 0,
|
||||
|
@ -1,5 +1,7 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
@ -7,8 +9,13 @@ from datetime import datetime
|
||||
import socket
|
||||
from multiprocessing import cpu_count
|
||||
|
||||
from powerline.lib import memoize, urllib_read, urllib_urlencode, add_divider_highlight_group
|
||||
from powerline.lib import add_divider_highlight_group
|
||||
from powerline.lib.url import urllib_read, urllib_urlencode
|
||||
from powerline.lib.vcs import guess
|
||||
from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment, with_docstring
|
||||
from powerline.lib.time import monotonic
|
||||
from powerline.lib.humanize_bytes import humanize_bytes
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
def hostname(only_if_ssh=False):
|
||||
@ -22,27 +29,8 @@ def hostname(only_if_ssh=False):
|
||||
return socket.gethostname()
|
||||
|
||||
|
||||
def user():
|
||||
'''Return the current user.
|
||||
|
||||
Highlights the user with the ``superuser`` if the effective user ID is 0.
|
||||
|
||||
Highlight groups used: ``superuser`` or ``user``. It is recommended to define all highlight groups.
|
||||
'''
|
||||
user = os.environ.get('USER')
|
||||
try:
|
||||
euid = os.geteuid()
|
||||
except AttributeError:
|
||||
# os.geteuid is not available on windows
|
||||
euid = 1
|
||||
return [{
|
||||
'contents': user,
|
||||
'highlight_group': 'user' if euid != 0 else ['superuser', 'user'],
|
||||
}]
|
||||
|
||||
|
||||
def branch(status_colors=True):
|
||||
'''Return the current VCS branch.@
|
||||
'''Return the current VCS branch.
|
||||
|
||||
:param bool status_colors:
|
||||
determines whether repository status will be used to determine highlighting. Default: True.
|
||||
@ -180,53 +168,38 @@ def fuzzy_time():
|
||||
return ' '.join([minute, hour])
|
||||
|
||||
|
||||
@memoize(600)
|
||||
def _external_ip(query_url='http://ipv4.icanhazip.com/'):
|
||||
return urllib_read(query_url).strip()
|
||||
|
||||
|
||||
def external_ip(query_url='http://ipv4.icanhazip.com/'):
|
||||
'''Return external IP address.
|
||||
class ExternalIpSegment(ThreadedSegment):
|
||||
def set_state(self, query_url='http://ipv4.icanhazip.com/', **kwargs):
|
||||
super(ExternalIpSegment, self).set_state(**kwargs)
|
||||
self.query_url = query_url
|
||||
|
||||
Suggested URIs:
|
||||
def update(self):
|
||||
ip = _external_ip(query_url=self.query_url)
|
||||
with self.write_lock:
|
||||
self.ip = ip
|
||||
|
||||
* http://ipv4.icanhazip.com/
|
||||
* http://ipv6.icanhazip.com/
|
||||
* http://icanhazip.com/ (returns IPv6 address if available, else IPv4)
|
||||
|
||||
:param str query_url:
|
||||
URI to query for IP address, should return only the IP address as a text string
|
||||
|
||||
Divider highlight group used: ``background:divider``.
|
||||
'''
|
||||
return [{'contents': _external_ip(query_url=query_url), 'divider_highlight_group': 'background:divider'}]
|
||||
def render(self):
|
||||
return [{'contents': self.ip, 'divider_highlight_group': 'background:divider'}]
|
||||
|
||||
|
||||
@add_divider_highlight_group('background:divider')
|
||||
def uptime(format='{days:02d}d {hours:02d}h {minutes:02d}m'):
|
||||
'''Return system uptime.
|
||||
external_ip = with_docstring(ExternalIpSegment(),
|
||||
'''Return external IP address.
|
||||
|
||||
Uses the ``psutil`` module if available for multi-platform compatibility,
|
||||
falls back to reading :file:`/proc/uptime`.
|
||||
Suggested URIs:
|
||||
|
||||
:param str format:
|
||||
format string, will be passed ``days``, ``hours`` and ``minutes`` as arguments
|
||||
* http://ipv4.icanhazip.com/
|
||||
* http://ipv6.icanhazip.com/
|
||||
* http://icanhazip.com/ (returns IPv6 address if available, else IPv4)
|
||||
|
||||
Divider highlight group used: ``background:divider``.
|
||||
'''
|
||||
try:
|
||||
import psutil
|
||||
seconds = int((datetime.now() - datetime.fromtimestamp(psutil.BOOT_TIME)).total_seconds())
|
||||
except ImportError:
|
||||
try:
|
||||
with open('/proc/uptime', 'r') as f:
|
||||
seconds = int(float(f.readline().split()[0]))
|
||||
except IOError:
|
||||
return None
|
||||
minutes, seconds = divmod(seconds, 60)
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
days, hours = divmod(hours, 24)
|
||||
return format.format(days=int(days), hours=hours, minutes=minutes)
|
||||
:param str query_url:
|
||||
URI to query for IP address, should return only the IP address as a text string
|
||||
|
||||
Divider highlight group used: ``background:divider``.
|
||||
''')
|
||||
|
||||
|
||||
# Weather condition code descriptions available at
|
||||
@ -304,79 +277,123 @@ weather_conditions_icons = {
|
||||
'unknown': '⚠',
|
||||
}
|
||||
|
||||
temp_conversions = {
|
||||
'C': lambda temp: temp,
|
||||
'F': lambda temp: (temp * 9 / 5) + 32,
|
||||
'K': lambda temp: temp + 273.15,
|
||||
}
|
||||
|
||||
@memoize(1800)
|
||||
def weather(unit='c', location_query=None, icons=None):
|
||||
'''Return weather from Yahoo! Weather.
|
||||
# Note: there are also unicode characters for units: ℃, ℉ and K
|
||||
temp_units = {
|
||||
'C': '°C',
|
||||
'F': '°F',
|
||||
'K': 'K',
|
||||
}
|
||||
|
||||
Uses GeoIP lookup from http://freegeoip.net/ to automatically determine
|
||||
your current location. This should be changed if you're in a VPN or if your
|
||||
IP address is registered at another location.
|
||||
|
||||
Returns a list of colorized icon and temperature segments depending on
|
||||
weather conditions.
|
||||
class WeatherSegment(ThreadedSegment):
|
||||
interval = 600
|
||||
|
||||
:param str unit:
|
||||
temperature unit, can be one of ``F``, ``C`` or ``K``
|
||||
:param str location_query:
|
||||
location query for your current location, e.g. ``oslo, norway``
|
||||
:param dict icons:
|
||||
dict for overriding default icons, e.g. ``{'heavy_snow' : u'❆'}``
|
||||
def set_state(self, location_query=None, **kwargs):
|
||||
super(WeatherSegment, self).set_state(**kwargs)
|
||||
self.location = location_query
|
||||
self.url = None
|
||||
self.condition = {}
|
||||
|
||||
Divider highlight group used: ``background:divider``.
|
||||
def update(self):
|
||||
import json
|
||||
|
||||
Highlight groups used: ``weather_conditions`` or ``weather``, ``weather_temp_cold`` or ``weather_temp_hot`` or ``weather_temp`` or ``weather``.
|
||||
Also uses ``weather_conditions_{condition}`` for all weather conditions supported by Yahoo.
|
||||
'''
|
||||
import json
|
||||
if not self.url:
|
||||
# Do not lock attribute assignments in this branch: they are used
|
||||
# only in .update()
|
||||
if not self.location:
|
||||
try:
|
||||
location_data = json.loads(urllib_read('http://freegeoip.net/json/' + _external_ip()))
|
||||
self.location = ','.join([location_data['city'],
|
||||
location_data['region_name'],
|
||||
location_data['country_name']])
|
||||
except (TypeError, ValueError):
|
||||
return
|
||||
query_data = {
|
||||
'q':
|
||||
'use "http://github.com/yql/yql-tables/raw/master/weather/weather.bylocation.xml" as we;'
|
||||
'select * from we where location="{0}" and unit="c"'.format(self.location).encode('utf-8'),
|
||||
'format': 'json',
|
||||
}
|
||||
self.url = 'http://query.yahooapis.com/v1/public/yql?' + urllib_urlencode(query_data)
|
||||
|
||||
if not location_query:
|
||||
try:
|
||||
location = json.loads(urllib_read('http://freegeoip.net/json/' + _external_ip()))
|
||||
location_query = ','.join([location['city'], location['region_name'], location['country_name']])
|
||||
except (TypeError, ValueError):
|
||||
raw_response = urllib_read(self.url)
|
||||
response = json.loads(raw_response)
|
||||
condition = response['query']['results']['weather']['rss']['channel']['item']['condition']
|
||||
condition_code = int(condition['code'])
|
||||
temp = float(condition['temp'])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return
|
||||
|
||||
try:
|
||||
icon_names = weather_conditions_codes[condition_code]
|
||||
except IndexError:
|
||||
icon_names = (('not_available' if condition_code == 3200 else 'unknown'),)
|
||||
|
||||
with self.write_lock:
|
||||
self.temp = temp
|
||||
self.icon_names = icon_names
|
||||
|
||||
def render(self, icons=None, unit='C', temperature_format=None, **kwargs):
|
||||
if not hasattr(self, 'icon_names'):
|
||||
return None
|
||||
query_data = {
|
||||
'q':
|
||||
'use "http://github.com/yql/yql-tables/raw/master/weather/weather.bylocation.xml" as we;'
|
||||
'select * from we where location="{0}" and unit="{1}"'.format(location_query, unit).encode('utf-8'),
|
||||
'format': 'json'
|
||||
}
|
||||
try:
|
||||
url = 'http://query.yahooapis.com/v1/public/yql?' + urllib_urlencode(query_data)
|
||||
response = json.loads(urllib_read(url))
|
||||
condition = response['query']['results']['weather']['rss']['channel']['item']['condition']
|
||||
condition_code = int(condition['code'])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return None
|
||||
|
||||
try:
|
||||
icon_names = weather_conditions_codes[condition_code]
|
||||
except IndexError:
|
||||
icon_names = (('not_available' if condition_code == 3200 else 'unknown'),)
|
||||
for icon_name in self.icon_names:
|
||||
if icons:
|
||||
if icon_name in icons:
|
||||
icon = icons[icon_name]
|
||||
break
|
||||
else:
|
||||
icon = weather_conditions_icons[self.icon_names[-1]]
|
||||
|
||||
for icon_name in icon_names:
|
||||
if icons:
|
||||
if icon_name in icons:
|
||||
icon = icons[icon_name]
|
||||
break
|
||||
else:
|
||||
icon = weather_conditions_icons[icon_names[-1]]
|
||||
temperature_format = temperature_format or ('{temp:.0f}' + temp_units[unit])
|
||||
temp = temp_conversions[unit](self.temp)
|
||||
groups = ['weather_condition_' + icon_name for icon_name in self.icon_names] + ['weather_conditions', 'weather']
|
||||
return [
|
||||
{
|
||||
'contents': icon + ' ',
|
||||
'highlight_group': groups,
|
||||
'divider_highlight_group': 'background:divider',
|
||||
},
|
||||
{
|
||||
'contents': temperature_format.format(temp=temp),
|
||||
'highlight_group': ['weather_temp_cold' if int(self.temp) < 0 else 'weather_temp_hot', 'weather_temp', 'weather'],
|
||||
'draw_divider': False,
|
||||
'divider_highlight_group': 'background:divider',
|
||||
},
|
||||
]
|
||||
|
||||
groups = ['weather_condition_' + icon_name for icon_name in icon_names] + ['weather_conditions', 'weather']
|
||||
return [
|
||||
{
|
||||
'contents': icon + ' ',
|
||||
'highlight_group': groups,
|
||||
'divider_highlight_group': 'background:divider',
|
||||
},
|
||||
{
|
||||
'contents': '{0}°{1}'.format(condition['temp'], unit.upper()),
|
||||
'highlight_group': ['weather_temp_cold' if int(condition['temp']) < 0 else 'weather_temp_hot', 'weather_temp', 'weather'],
|
||||
'draw_divider': False,
|
||||
'divider_highlight_group': 'background:divider',
|
||||
},
|
||||
]
|
||||
|
||||
weather = with_docstring(WeatherSegment(),
|
||||
'''Return weather from Yahoo! Weather.
|
||||
|
||||
Uses GeoIP lookup from http://freegeoip.net/ to automatically determine
|
||||
your current location. This should be changed if you're in a VPN or if your
|
||||
IP address is registered at another location.
|
||||
|
||||
Returns a list of colorized icon and temperature segments depending on
|
||||
weather conditions.
|
||||
|
||||
:param str unit:
|
||||
temperature unit, can be one of ``F``, ``C`` or ``K``
|
||||
:param str location_query:
|
||||
location query for your current location, e.g. ``oslo, norway``
|
||||
:param dict icons:
|
||||
dict for overriding default icons, e.g. ``{'heavy_snow' : u'❆'}``
|
||||
:param str temperature_format:
|
||||
format string, receives ``temp`` as an argument. Should also hold unit.
|
||||
|
||||
Divider highlight group used: ``background:divider``.
|
||||
|
||||
Highlight groups used: ``weather_conditions`` or ``weather``, ``weather_temp_cold`` or ``weather_temp_hot`` or ``weather_temp`` or ``weather``.
|
||||
Also uses ``weather_conditions_{condition}`` for all weather conditions supported by Yahoo.
|
||||
''')
|
||||
|
||||
|
||||
def system_load(format='{avg:.1f}', threshold_good=1, threshold_bad=2):
|
||||
@ -397,7 +414,11 @@ def system_load(format='{avg:.1f}', threshold_good=1, threshold_bad=2):
|
||||
|
||||
Highlight groups used: ``system_load_good`` or ``system_load``, ``system_load_bad`` or ``system_load``, ``system_load_ugly`` or ``system_load``. It is recommended to define all highlight groups.
|
||||
'''
|
||||
cpu_num = cpu_count()
|
||||
global cpu_count
|
||||
try:
|
||||
cpu_num = cpu_count()
|
||||
except NotImplementedError:
|
||||
return None
|
||||
ret = []
|
||||
for avg in os.getloadavg():
|
||||
normalized = avg / cpu_num
|
||||
@ -419,69 +440,175 @@ def system_load(format='{avg:.1f}', threshold_good=1, threshold_bad=2):
|
||||
return ret
|
||||
|
||||
|
||||
def cpu_load_percent(measure_interval=.5):
|
||||
'''Return the average CPU load as a percentage.
|
||||
try:
|
||||
import psutil
|
||||
|
||||
Requires the ``psutil`` module.
|
||||
def _get_bytes(interface):
|
||||
io_counters = psutil.network_io_counters(pernic=True)
|
||||
if_io = io_counters.get(interface)
|
||||
if not if_io:
|
||||
return None
|
||||
return if_io.bytes_recv, if_io.bytes_sent
|
||||
|
||||
:param float measure_interval:
|
||||
interval used to measure CPU load (in seconds)
|
||||
'''
|
||||
try:
|
||||
import psutil
|
||||
except ImportError:
|
||||
def _get_user():
|
||||
return psutil.Process(os.getpid()).username
|
||||
|
||||
def cpu_load_percent(measure_interval=.5):
|
||||
'''Return the average CPU load as a percentage.
|
||||
|
||||
Requires the ``psutil`` module.
|
||||
|
||||
:param float measure_interval:
|
||||
interval used to measure CPU load (in seconds)
|
||||
'''
|
||||
cpu_percent = int(psutil.cpu_percent(interval=measure_interval))
|
||||
return '{0}%'.format(cpu_percent)
|
||||
except ImportError:
|
||||
def _get_bytes(interface): # NOQA
|
||||
try:
|
||||
with open('/sys/class/net/{interface}/statistics/rx_bytes'.format(interface=interface), 'rb') as file_obj:
|
||||
rx = int(file_obj.read())
|
||||
with open('/sys/class/net/{interface}/statistics/tx_bytes'.format(interface=interface), 'rb') as file_obj:
|
||||
tx = int(file_obj.read())
|
||||
return (rx, tx)
|
||||
except IOError:
|
||||
return None
|
||||
|
||||
def _get_user(): # NOQA
|
||||
return os.environ.get('USER', None)
|
||||
|
||||
def cpu_load_percent(measure_interval=.5): # NOQA
|
||||
'''Return the average CPU load as a percentage.
|
||||
|
||||
Requires the ``psutil`` module.
|
||||
|
||||
:param float measure_interval:
|
||||
interval used to measure CPU load (in seconds)
|
||||
'''
|
||||
return None
|
||||
cpu_percent = int(psutil.cpu_percent(interval=measure_interval))
|
||||
return '{0}%'.format(cpu_percent)
|
||||
|
||||
|
||||
username = False
|
||||
|
||||
|
||||
def user():
|
||||
'''Return the current user.
|
||||
|
||||
Highlights the user with the ``superuser`` if the effective user ID is 0.
|
||||
|
||||
Highlight groups used: ``superuser`` or ``user``. It is recommended to define all highlight groups.
|
||||
'''
|
||||
global username
|
||||
if username is False:
|
||||
username = _get_user()
|
||||
if username is None:
|
||||
return None
|
||||
try:
|
||||
euid = os.geteuid()
|
||||
except AttributeError:
|
||||
# os.geteuid is not available on windows
|
||||
euid = 1
|
||||
return [{
|
||||
'contents': username,
|
||||
'highlight_group': 'user' if euid != 0 else ['superuser', 'user'],
|
||||
}]
|
||||
|
||||
|
||||
if os.path.exists('/proc/uptime'):
|
||||
def _get_uptime():
|
||||
with open('/proc/uptime', 'r') as f:
|
||||
return int(float(f.readline().split()[0]))
|
||||
elif 'psutil' in globals():
|
||||
from time import time
|
||||
def _get_uptime(): # NOQA
|
||||
# psutil.BOOT_TIME is not subject to clock adjustments, but time() is.
|
||||
# Thus it is a fallback to /proc/uptime reading and not the reverse.
|
||||
return int(time() - psutil.BOOT_TIME)
|
||||
else:
|
||||
def _get_uptime(): # NOQA
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@add_divider_highlight_group('background:divider')
|
||||
def network_load(interface='eth0', measure_interval=1, suffix='B/s', si_prefix=False):
|
||||
'''Return the network load.
|
||||
def uptime(format='{days}d {hours:02d}h {minutes:02d}m'):
|
||||
'''Return system uptime.
|
||||
|
||||
Uses the ``psutil`` module if available for multi-platform compatibility,
|
||||
falls back to reading
|
||||
:file:`/sys/class/net/{interface}/statistics/{rx,tx}_bytes`.
|
||||
:param str format:
|
||||
format string, will be passed ``days``, ``hours``, ``minutes`` and
|
||||
seconds as arguments
|
||||
|
||||
:param str interface:
|
||||
network interface to measure
|
||||
:param float measure_interval:
|
||||
interval used to measure the network load (in seconds)
|
||||
:param str suffix:
|
||||
string appended to each load string
|
||||
:param bool si_prefix:
|
||||
use SI prefix, e.g. MB instead of MiB
|
||||
Divider highlight group used: ``background:divider``.
|
||||
'''
|
||||
import time
|
||||
from powerline.lib import humanize_bytes
|
||||
|
||||
def get_bytes():
|
||||
try:
|
||||
import psutil
|
||||
io_counters = psutil.network_io_counters(pernic=True)
|
||||
if_io = io_counters.get(interface)
|
||||
if not if_io:
|
||||
return None
|
||||
return (if_io.bytes_recv, if_io.bytes_sent)
|
||||
except ImportError:
|
||||
try:
|
||||
with open('/sys/class/net/{interface}/statistics/rx_bytes'.format(interface=interface), 'rb') as file_obj:
|
||||
rx = int(file_obj.read())
|
||||
with open('/sys/class/net/{interface}/statistics/tx_bytes'.format(interface=interface), 'rb') as file_obj:
|
||||
tx = int(file_obj.read())
|
||||
return (rx, tx)
|
||||
except IOError:
|
||||
return None
|
||||
|
||||
b1 = get_bytes()
|
||||
if b1 is None:
|
||||
try:
|
||||
seconds = _get_uptime()
|
||||
except (IOError, NotImplementedError):
|
||||
return None
|
||||
time.sleep(measure_interval)
|
||||
b2 = get_bytes()
|
||||
return '⬇ {rx_diff} ⬆ {tx_diff}'.format(
|
||||
rx_diff=humanize_bytes((b2[0] - b1[0]) / measure_interval, suffix, si_prefix).rjust(8),
|
||||
tx_diff=humanize_bytes((b2[1] - b1[1]) / measure_interval, suffix, si_prefix).rjust(8),
|
||||
)
|
||||
minutes, seconds = divmod(seconds, 60)
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
days, hours = divmod(hours, 24)
|
||||
return format.format(days=int(days), hours=hours, minutes=minutes, seconds=seconds)
|
||||
|
||||
|
||||
class NetworkLoadSegment(KwThreadedSegment):
|
||||
interfaces = {}
|
||||
|
||||
@staticmethod
|
||||
def key(interface='eth0', **kwargs):
|
||||
return interface
|
||||
|
||||
def compute_state(self, interface):
|
||||
if interface in self.interfaces:
|
||||
idata = self.interfaces[interface]
|
||||
try:
|
||||
idata['prev'] = idata['last']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
idata = {}
|
||||
if self.run_once:
|
||||
idata['prev'] = (monotonic(), _get_bytes(interface))
|
||||
self.sleep(0)
|
||||
self.interfaces[interface] = idata
|
||||
|
||||
idata['last'] = (monotonic(), _get_bytes(interface))
|
||||
return idata
|
||||
|
||||
def render_one(self, idata, format='⬇ {recv:>8} ⬆ {sent:>8}', suffix='B/s', si_prefix=False, **kwargs):
|
||||
if not idata or 'prev' not in idata:
|
||||
return None
|
||||
|
||||
t1, b1 = idata['prev']
|
||||
t2, b2 = idata['last']
|
||||
measure_interval = t2 - t1
|
||||
|
||||
if None in (b1, b2):
|
||||
return None
|
||||
|
||||
return [{
|
||||
'contents': format.format(
|
||||
recv=humanize_bytes((b2[0] - b1[0]) / measure_interval, suffix, si_prefix),
|
||||
sent=humanize_bytes((b2[1] - b1[1]) / measure_interval, suffix, si_prefix),
|
||||
),
|
||||
'divider_highlight_group': 'background:divider',
|
||||
}]
|
||||
|
||||
|
||||
network_load = with_docstring(NetworkLoadSegment(),
|
||||
'''Return the network load.
|
||||
|
||||
Uses the ``psutil`` module if available for multi-platform compatibility,
|
||||
falls back to reading
|
||||
:file:`/sys/class/net/{interface}/statistics/{rx,tx}_bytes`.
|
||||
|
||||
:param str interface:
|
||||
network interface to measure
|
||||
:param str suffix:
|
||||
string appended to each load string
|
||||
:param bool si_prefix:
|
||||
use SI prefix, e.g. MB instead of MiB
|
||||
:param str format:
|
||||
format string, receives ``recv`` and ``sent`` as arguments
|
||||
''')
|
||||
|
||||
|
||||
def virtualenv():
|
||||
@ -489,44 +616,56 @@ def virtualenv():
|
||||
return os.path.basename(os.environ.get('VIRTUAL_ENV', '')) or None
|
||||
|
||||
|
||||
@memoize(60)
|
||||
def email_imap_alert(username, password, server='imap.gmail.com', port=993, folder='INBOX'):
|
||||
'''Return unread e-mail count for IMAP servers.
|
||||
_IMAPKey = namedtuple('Key', 'username password server port folder')
|
||||
|
||||
:param str username:
|
||||
login username
|
||||
:param str password:
|
||||
login password
|
||||
:param str server:
|
||||
e-mail server
|
||||
:param int port:
|
||||
e-mail server port
|
||||
:param str folder:
|
||||
folder to check for e-mails
|
||||
|
||||
Highlight groups used: ``email_alert``.
|
||||
'''
|
||||
import imaplib
|
||||
import re
|
||||
class EmailIMAPSegment(KwThreadedSegment):
|
||||
interval = 60
|
||||
|
||||
if not username or not password:
|
||||
return None
|
||||
try:
|
||||
mail = imaplib.IMAP4_SSL(server, port)
|
||||
mail.login(username, password)
|
||||
rc, message = mail.status(folder, '(UNSEEN)')
|
||||
unread_str = message[0].decode('utf-8')
|
||||
unread_count = int(re.search('UNSEEN (\d+)', unread_str).group(1))
|
||||
except socket.gaierror:
|
||||
return None
|
||||
except imaplib.IMAP4.error as e:
|
||||
unread_count = str(e)
|
||||
if not unread_count:
|
||||
return None
|
||||
return [{
|
||||
'highlight_group': 'email_alert',
|
||||
'contents': str(unread_count),
|
||||
}]
|
||||
@staticmethod
|
||||
def key(username, password, server='imap.gmail.com', port=993, folder='INBOX'):
|
||||
return _IMAPKey(username, password, server, port, folder)
|
||||
|
||||
@staticmethod
|
||||
def compute_state(key):
|
||||
if not key.username or not key.password:
|
||||
return None
|
||||
try:
|
||||
import imaplib
|
||||
import re
|
||||
mail = imaplib.IMAP4_SSL(key.server, key.port)
|
||||
mail.login(key.username, key.password)
|
||||
rc, message = mail.status(key.folder, '(UNSEEN)')
|
||||
unread_str = message[0].decode('utf-8')
|
||||
unread_count = int(re.search('UNSEEN (\d+)', unread_str).group(1))
|
||||
except socket.gaierror:
|
||||
return None
|
||||
except imaplib.IMAP4.error as e:
|
||||
unread_count = str(e)
|
||||
if not unread_count:
|
||||
return None
|
||||
return [{
|
||||
'highlight_group': 'email_alert',
|
||||
'contents': str(unread_count),
|
||||
}]
|
||||
|
||||
|
||||
email_imap_alert = with_docstring(EmailIMAPSegment(),
|
||||
'''Return unread e-mail count for IMAP servers.
|
||||
|
||||
:param str username:
|
||||
login username
|
||||
:param str password:
|
||||
login password
|
||||
:param str server:
|
||||
e-mail server
|
||||
:param int port:
|
||||
e-mail server port
|
||||
:param str folder:
|
||||
folder to check for e-mails
|
||||
|
||||
Highlight groups used: ``email_alert``.
|
||||
''')
|
||||
|
||||
|
||||
class NowPlayingSegment(object):
|
||||
|
@ -10,9 +10,11 @@ except ImportError:
|
||||
|
||||
from powerline.bindings.vim import vim_get_func, getbufvar
|
||||
from powerline.theme import requires_segment_info
|
||||
from powerline.lib import memoize, humanize_bytes, add_divider_highlight_group
|
||||
from powerline.lib import add_divider_highlight_group
|
||||
from powerline.lib.vcs import guess
|
||||
from functools import wraps
|
||||
from powerline.lib.humanize_bytes import humanize_bytes
|
||||
from powerline.lib.threaded import KwThreadedSegment, with_docstring
|
||||
from powerline.lib import wraps_saveargs as wraps
|
||||
from collections import defaultdict
|
||||
|
||||
vim_funcs = {
|
||||
@ -20,6 +22,7 @@ vim_funcs = {
|
||||
'fnamemodify': vim_get_func('fnamemodify'),
|
||||
'expand': vim_get_func('expand'),
|
||||
'bufnr': vim_get_func('bufnr', rettype=int),
|
||||
'line2byte': vim_get_func('line2byte', rettype=int),
|
||||
}
|
||||
|
||||
vim_modes = {
|
||||
@ -74,28 +77,18 @@ def launchevent(event):
|
||||
pass
|
||||
|
||||
|
||||
def bufnr(segment_info, **kwargs):
|
||||
'''Used for cache key, returns current buffer number'''
|
||||
return segment_info['bufnr']
|
||||
|
||||
|
||||
def bufname(segment_info, **kwargs):
|
||||
'''Used for cache key, returns current buffer name'''
|
||||
return segment_info['buffer'].name
|
||||
|
||||
|
||||
# TODO Remove cache when needed
|
||||
def window_cached(func):
|
||||
cache = {}
|
||||
|
||||
@requires_segment_info
|
||||
@wraps(func)
|
||||
def ret(segment_info, *args, **kwargs):
|
||||
def ret(segment_info, **kwargs):
|
||||
window_id = segment_info['window_id']
|
||||
if segment_info['mode'] == 'nc':
|
||||
return cache.get(window_id)
|
||||
else:
|
||||
r = func(*args, **kwargs)
|
||||
r = func(**kwargs)
|
||||
cache[window_id] = r
|
||||
return r
|
||||
|
||||
@ -167,7 +160,7 @@ def file_directory(segment_info, shorten_user=True, shorten_cwd=True, shorten_ho
|
||||
if not name:
|
||||
return None
|
||||
file_directory = vim_funcs['fnamemodify'](name, (':~' if shorten_user else '')
|
||||
+ (':.' if shorten_home else '') + ':h')
|
||||
+ (':.' if shorten_cwd else '') + ':h')
|
||||
if shorten_home and file_directory.startswith('/home/'):
|
||||
file_directory = '~' + file_directory[6:]
|
||||
return file_directory + os.sep if file_directory else None
|
||||
@ -195,10 +188,9 @@ def file_name(segment_info, display_no_file=False, no_file_text='[No file]'):
|
||||
return file_name
|
||||
|
||||
|
||||
@requires_segment_info
|
||||
@memoize(2, cache_key=bufname, cache_reg_func=purgebuf_on_shell_and_write)
|
||||
def file_size(segment_info, suffix='B', si_prefix=False):
|
||||
'''Return file size.
|
||||
@window_cached
|
||||
def file_size(suffix='B', si_prefix=False):
|
||||
'''Return file size in &encoding.
|
||||
|
||||
:param str suffix:
|
||||
string appended to the file size
|
||||
@ -206,13 +198,9 @@ def file_size(segment_info, suffix='B', si_prefix=False):
|
||||
use SI prefix, e.g. MB instead of MiB
|
||||
:return: file size or None if the file isn't saved or if the size is too big to fit in a number
|
||||
'''
|
||||
file_name = segment_info['buffer'].name
|
||||
if not file_name:
|
||||
return None
|
||||
try:
|
||||
file_size = os.stat(file_name).st_size
|
||||
except:
|
||||
return None
|
||||
# Note: returns file size in &encoding, not in &fileencoding. But returned
|
||||
# size is updated immediately; and it is valid for any buffer
|
||||
file_size = vim_funcs['line2byte'](len(vim.current.buffer) + 1) - 1
|
||||
return humanize_bytes(file_size, suffix, si_prefix)
|
||||
|
||||
|
||||
@ -311,58 +299,135 @@ def modified_buffers(text='+ ', join_str=','):
|
||||
return None
|
||||
|
||||
|
||||
@requires_segment_info
|
||||
@memoize(2, cache_key=bufnr, cache_reg_func=purgeall_on_shell)
|
||||
def branch(segment_info, status_colors=True):
|
||||
'''Return the current working branch.
|
||||
|
||||
:param bool status_colors:
|
||||
determines whether repository status will be used to determine highlighting. Default: True.
|
||||
|
||||
Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``.
|
||||
|
||||
Divider highlight group used: ``branch:divider``.
|
||||
'''
|
||||
repo = guess(path=os.path.abspath(segment_info['buffer'].name or os.getcwd()))
|
||||
if repo:
|
||||
return [{
|
||||
'contents': repo.branch(),
|
||||
'highlight_group': (['branch_dirty' if repo.status() else 'branch_clean'] if status_colors else []) + ['branch'],
|
||||
'divider_highlight_group': 'branch:divider',
|
||||
}]
|
||||
return None
|
||||
class KwWindowThreadedSegment(KwThreadedSegment):
|
||||
def set_state(self, **kwargs):
|
||||
for window in vim.windows:
|
||||
buffer = window.buffer
|
||||
kwargs['segment_info'] = {'bufnr': buffer.number, 'buffer': buffer}
|
||||
super(KwWindowThreadedSegment, self).set_state(**kwargs)
|
||||
|
||||
|
||||
@requires_segment_info
|
||||
@memoize(2, cache_key=bufnr, cache_reg_func=purgebuf_on_shell_and_write)
|
||||
def file_vcs_status(segment_info):
|
||||
'''Return the VCS status for this buffer.
|
||||
class RepositorySegment(KwWindowThreadedSegment):
|
||||
def __init__(self):
|
||||
super(RepositorySegment, self).__init__()
|
||||
self.directories = {}
|
||||
|
||||
Highlight groups used: ``file_vcs_status``.
|
||||
'''
|
||||
name = segment_info['buffer'].name
|
||||
if name and not getbufvar(segment_info['bufnr'], '&buftype'):
|
||||
repo = guess(path=os.path.abspath(name))
|
||||
@staticmethod
|
||||
def key(segment_info, **kwargs):
|
||||
# FIXME os.getcwd() is not a proper variant for non-current buffers
|
||||
return segment_info['buffer'].name or os.getcwd()
|
||||
|
||||
def update(self):
|
||||
# .compute_state() is running only in this method, and only in one
|
||||
# thread, thus operations with .directories do not need write locks
|
||||
# (.render() method is not using .directories). If this is changed
|
||||
# .directories needs redesigning
|
||||
self.directories.clear()
|
||||
super(RepositorySegment, self).update()
|
||||
|
||||
def compute_state(self, path):
|
||||
repo = guess(path=path)
|
||||
if repo:
|
||||
status = repo.status(os.path.relpath(name, repo.directory))
|
||||
if not status:
|
||||
return None
|
||||
status = status.strip()
|
||||
ret = []
|
||||
for status in status:
|
||||
ret.append({
|
||||
'contents': status,
|
||||
'highlight_group': ['file_vcs_status_' + status, 'file_vcs_status'],
|
||||
})
|
||||
return ret
|
||||
return None
|
||||
if repo.directory in self.directories:
|
||||
return self.directories[repo.directory]
|
||||
else:
|
||||
r = self.process_repo(repo)
|
||||
self.directories[repo.directory] = r
|
||||
return r
|
||||
|
||||
|
||||
@requires_segment_info
|
||||
@memoize(2, cache_key=bufnr, cache_reg_func=purgeall_on_shell)
|
||||
def repository_status(segment_info):
|
||||
'''Return the status for the current repo.'''
|
||||
repo = guess(path=os.path.abspath(segment_info['buffer'].name or os.getcwd()))
|
||||
if repo:
|
||||
return repo.status().strip() or None
|
||||
return None
|
||||
class RepositoryStatusSegment(RepositorySegment):
|
||||
interval = 2
|
||||
|
||||
@staticmethod
|
||||
def process_repo(repo):
|
||||
return repo.status()
|
||||
|
||||
|
||||
repository_status = with_docstring(RepositoryStatusSegment(),
|
||||
'''Return the status for the current repo.''')
|
||||
|
||||
|
||||
@requires_segment_info
|
||||
class BranchSegment(RepositorySegment):
|
||||
interval = 0.2
|
||||
started_repository_status = False
|
||||
|
||||
@staticmethod
|
||||
def process_repo(repo):
|
||||
return repo.branch()
|
||||
|
||||
def render_one(self, update_state, segment_info, status_colors=False, **kwargs):
|
||||
if not update_state:
|
||||
return None
|
||||
|
||||
if status_colors:
|
||||
self.started_repository_status = True
|
||||
|
||||
return [{
|
||||
'contents': update_state,
|
||||
'highlight_group': (['branch_dirty' if repository_status(segment_info=segment_info) else 'branch_clean']
|
||||
if status_colors else []) + ['branch'],
|
||||
'divider_highlight_group': 'branch:divider',
|
||||
}]
|
||||
|
||||
def startup(self, **kwargs):
|
||||
super(BranchSegment, self).startup()
|
||||
if kwargs.get('status_colors', False):
|
||||
self.started_repository_status = True
|
||||
repository_status.startup()
|
||||
|
||||
def shutdown(self):
|
||||
if self.started_repository_status:
|
||||
repository_status.shutdown()
|
||||
super(BranchSegment, self).shutdown()
|
||||
|
||||
|
||||
branch = with_docstring(BranchSegment(),
|
||||
'''Return the current working branch.
|
||||
|
||||
:param bool status_colors:
|
||||
determines whether repository status will be used to determine highlighting. Default: False.
|
||||
|
||||
Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``.
|
||||
|
||||
Divider highlight group used: ``branch:divider``.
|
||||
''')
|
||||
|
||||
|
||||
@requires_segment_info
|
||||
class FileVCSStatusSegment(KwWindowThreadedSegment):
|
||||
interval = 0.2
|
||||
|
||||
@staticmethod
|
||||
def key(segment_info, **kwargs):
|
||||
name = segment_info['buffer'].name
|
||||
skip = not (name and (not getbufvar(segment_info['bufnr'], '&buftype')))
|
||||
return name, skip
|
||||
|
||||
@staticmethod
|
||||
def compute_state(key):
|
||||
name, skip = key
|
||||
if not skip:
|
||||
repo = guess(path=name)
|
||||
if repo:
|
||||
status = repo.status(os.path.relpath(name, repo.directory))
|
||||
if not status:
|
||||
return None
|
||||
status = status.strip()
|
||||
ret = []
|
||||
for status in status:
|
||||
ret.append({
|
||||
'contents': status,
|
||||
'highlight_group': ['file_vcs_status_' + status, 'file_vcs_status'],
|
||||
})
|
||||
return ret
|
||||
return None
|
||||
|
||||
|
||||
file_vcs_status = with_docstring(FileVCSStatusSegment(),
|
||||
'''Return the VCS status for this buffer.
|
||||
|
||||
Highlight groups used: ``file_vcs_status``.
|
||||
''')
|
||||
|
@ -14,10 +14,10 @@ def mergeargs(argvalue):
|
||||
|
||||
|
||||
class ShellPowerline(Powerline):
|
||||
def __init__(self, args):
|
||||
def __init__(self, args, run_once=False):
|
||||
self.args = args
|
||||
self.theme_option = mergeargs(args.theme_option) or {}
|
||||
super(ShellPowerline, self).__init__(args.ext[0], args.renderer_module)
|
||||
super(ShellPowerline, self).__init__(args.ext[0], args.renderer_module, run_once=run_once)
|
||||
|
||||
def get_segment_info(self):
|
||||
return self.args
|
||||
|
@ -19,12 +19,12 @@ def u(s):
|
||||
|
||||
|
||||
def requires_segment_info(func):
|
||||
func.requires_powerline_segment_info = True
|
||||
func.powerline_requires_segment_info = True
|
||||
return func
|
||||
|
||||
|
||||
class Theme(object):
|
||||
def __init__(self, ext, theme_config, common_config, top_theme_config=None, segment_info=None):
|
||||
def __init__(self, ext, theme_config, common_config, top_theme_config=None, segment_info=None, run_once=False):
|
||||
self.dividers = theme_config.get('dividers', common_config['dividers'])
|
||||
self.spaces = theme_config.get('spaces', common_config['spaces'])
|
||||
self.segments = {
|
||||
@ -42,6 +42,18 @@ class Theme(object):
|
||||
get_segment = gen_segment_getter(ext, common_config['paths'], theme_configs, theme_config.get('default_module'))
|
||||
for side in ['left', 'right']:
|
||||
self.segments[side].extend((get_segment(segment, side) for segment in theme_config['segments'].get(side, [])))
|
||||
if not run_once:
|
||||
for segment in self.segments[side]:
|
||||
if segment['startup']:
|
||||
segment['startup'](**segment['args'])
|
||||
|
||||
def shutdown(self):
|
||||
for segments in self.segments.values():
|
||||
for segment in segments:
|
||||
try:
|
||||
segment['shutdown']()
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
def get_divider(self, side='left', type='soft'):
|
||||
'''Return segment divider.'''
|
||||
@ -60,8 +72,8 @@ class Theme(object):
|
||||
parsed_segments = []
|
||||
for segment in self.segments[side]:
|
||||
if segment['type'] == 'function':
|
||||
if (hasattr(segment['contents_func'], 'requires_powerline_segment_info')
|
||||
and segment['contents_func'].requires_powerline_segment_info):
|
||||
if (hasattr(segment['contents_func'], 'powerline_requires_segment_info')
|
||||
and segment['contents_func'].powerline_requires_segment_info):
|
||||
contents = segment['contents_func'](segment_info=self.segment_info, **segment['args'])
|
||||
else:
|
||||
contents = segment['contents_func'](**segment['args'])
|
||||
|
@ -12,7 +12,7 @@ except ImportError:
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = get_argparser(description=__doc__).parse_args()
|
||||
powerline = ShellPowerline(args)
|
||||
powerline = ShellPowerline(args, run_once=True)
|
||||
rendered = powerline.renderer.render(width=args.width, side=args.side)
|
||||
try:
|
||||
sys.stdout.write(rendered)
|
||||
|
@ -1,5 +1,6 @@
|
||||
#!/bin/sh
|
||||
pip install .
|
||||
pip install psutil
|
||||
if python -c 'import sys; sys.exit(1 * (sys.version_info[0] != 2))' ; then
|
||||
# Python 2
|
||||
pip install mercurial bzr
|
||||
|
@ -24,7 +24,7 @@ def urllib_read(query_url):
|
||||
elif query_url.startswith('http://freegeoip.net/json/'):
|
||||
return '{"city": "Meppen", "region_code": "06", "region_name": "Niedersachsen", "areacode": "", "ip": "82.145.55.16", "zipcode": "49716", "longitude": 7.3167, "country_name": "Germany", "country_code": "DE", "metrocode": "", "latitude": 52.6833}'
|
||||
elif query_url.startswith('http://query.yahooapis.com/v1/public/'):
|
||||
return '{"query":{"count":1,"created":"2013-03-02T13:20:22Z","lang":"en-US","results":{"weather":{"rss":{"version":"2.0","geo":"http://www.w3.org/2003/01/geo/wgs84_pos#","yweather":"http://xml.weather.yahoo.com/ns/rss/1.0","channel":{"title":"Yahoo! Weather - Russia, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","description":"Yahoo! Weather for Russia, RU","language":"en-us","lastBuildDate":"Sat, 02 Mar 2013 4:58 pm MSK","ttl":"60","location":{"city":"Russia","country":"Russia","region":""},"units":{"distance":"km","pressure":"mb","speed":"km/h","temperature":"C"},"wind":{"chill":"-11","direction":"0","speed":""},"atmosphere":{"humidity":"94","pressure":"1006.1","rising":"0","visibility":""},"astronomy":{"sunrise":"10:04 am","sunset":"7:57 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Russia, RU at 4:58 pm MSK","lat":"59.45","long":"108.83","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","pubDate":"Sat, 02 Mar 2013 4:58 pm MSK","condition":{"code":"30","date":"Sat, 02 Mar 2013 4:58 pm MSK","temp":"-11","text":"Partly Cloudy"},"description":"<img src=\"http://l.yimg.com/a/i/us/we/52/30.gif\"/><br />\n<b>Current Conditions:</b><br />\nPartly Cloudy, -11 C<BR />\n<BR /><b>Forecast:</b><BR />\nSat - Partly Cloudy. High: -9 Low: -19<br />\nSun - Partly Cloudy. High: -12 Low: -18<br />\n<br />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html\">Full Forecast at Yahoo! Weather</a><BR/><BR/>\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)<br/>","forecast":[{"code":"29","date":"2 Mar 2013","day":"Sat","high":"-9","low":"-19","text":"Partly Cloudy"},{"code":"30","date":"3 Mar 2013","day":"Sun","high":"-12","low":"-18","text":"Partly Cloudy"}],"guid":{"isPermaLink":"false","content":"RSXX1511_2013_03_03_7_00_MSK"}}}}}}}}'
|
||||
return r'{"query":{"count":1,"created":"2013-03-02T13:20:22Z","lang":"en-US","results":{"weather":{"rss":{"version":"2.0","geo":"http://www.w3.org/2003/01/geo/wgs84_pos#","yweather":"http://xml.weather.yahoo.com/ns/rss/1.0","channel":{"title":"Yahoo! Weather - Russia, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","description":"Yahoo! Weather for Russia, RU","language":"en-us","lastBuildDate":"Sat, 02 Mar 2013 4:58 pm MSK","ttl":"60","location":{"city":"Russia","country":"Russia","region":""},"units":{"distance":"km","pressure":"mb","speed":"km/h","temperature":"C"},"wind":{"chill":"-11","direction":"0","speed":""},"atmosphere":{"humidity":"94","pressure":"1006.1","rising":"0","visibility":""},"astronomy":{"sunrise":"10:04 am","sunset":"7:57 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Russia, RU at 4:58 pm MSK","lat":"59.45","long":"108.83","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","pubDate":"Sat, 02 Mar 2013 4:58 pm MSK","condition":{"code":"30","date":"Sat, 02 Mar 2013 4:58 pm MSK","temp":"-11","text":"Partly Cloudy"},"description":"<img src=\"http://l.yimg.com/a/i/us/we/52/30.gif\"/><br />\n<b>Current Conditions:</b><br />\nPartly Cloudy, -11 C<BR />\n<BR /><b>Forecast:</b><BR />\nSat - Partly Cloudy. High: -9 Low: -19<br />\nSun - Partly Cloudy. High: -12 Low: -18<br />\n<br />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html\">Full Forecast at Yahoo! Weather</a><BR/><BR/>\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)<br/>","forecast":[{"code":"29","date":"2 Mar 2013","day":"Sat","high":"-9","low":"-19","text":"Partly Cloudy"},{"code":"30","date":"3 Mar 2013","day":"Sun","high":"-12","low":"-18","text":"Partly Cloudy"}],"guid":{"isPermaLink":"false","content":"RSXX1511_2013_03_03_7_00_MSK"}}}}}}}}'
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -1,19 +1,12 @@
|
||||
#!/bin/sh
|
||||
: ${PYTHON:=python}
|
||||
FAILED=0
|
||||
if ${PYTHON} -c 'import sys; sys.exit(1 * (sys.version_info >= (2, 7)))' ; then
|
||||
# Python 2.6
|
||||
export PYTHONPATH="${PYTHONPATH}:`realpath .`"
|
||||
for file in tests/test_*.py ; do
|
||||
if ! ${PYTHON} $file ; then
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
else
|
||||
if ! ${PYTHON} setup.py test ; then
|
||||
export PYTHONPATH="${PYTHONPATH}:`realpath .`"
|
||||
for file in tests/test_*.py ; do
|
||||
if ! ${PYTHON} $file ; then
|
||||
FAILED=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if ! ${PYTHON} scripts/powerline-lint ; then
|
||||
FAILED=1
|
||||
fi
|
||||
|
@ -49,6 +49,7 @@ class TestConfig(TestCase):
|
||||
check_output(1, 0)
|
||||
finally:
|
||||
vim_module._start_mode('n')
|
||||
powerline.renderer.shutdown()
|
||||
|
||||
def test_tmux(self):
|
||||
from powerline.segments import common
|
||||
@ -56,16 +57,16 @@ class TestConfig(TestCase):
|
||||
reload(common)
|
||||
from powerline.shell import ShellPowerline
|
||||
with replace_module_attr(common, 'urllib_read', urllib_read):
|
||||
ShellPowerline(Args(ext=['tmux'])).renderer.render()
|
||||
ShellPowerline(Args(ext=['tmux']), run_once=True).renderer.render()
|
||||
reload(common)
|
||||
|
||||
def test_zsh(self):
|
||||
from powerline.shell import ShellPowerline
|
||||
ShellPowerline(Args(last_pipe_status=[1, 0], ext=['shell'], renderer_module='zsh_prompt')).renderer.render()
|
||||
ShellPowerline(Args(last_pipe_status=[1, 0], ext=['shell'], renderer_module='zsh_prompt'), run_once=True).renderer.render()
|
||||
|
||||
def test_bash(self):
|
||||
from powerline.shell import ShellPowerline
|
||||
ShellPowerline(Args(last_exit_code=1, ext=['shell'], renderer_module='bash_prompt', config=[('ext', {'shell': {'theme': 'default_leftonly'}})])).renderer.render()
|
||||
ShellPowerline(Args(last_exit_code=1, ext=['shell'], renderer_module='bash_prompt', config=[('ext', {'shell': {'theme': 'default_leftonly'}})]), run_once=True).renderer.render()
|
||||
|
||||
def test_ipython(self):
|
||||
from powerline.ipython import IpythonPowerline
|
||||
@ -75,7 +76,9 @@ class TestConfig(TestCase):
|
||||
config_overrides = None
|
||||
theme_overrides = {}
|
||||
|
||||
IpyPowerline().renderer.render()
|
||||
powerline = IpyPowerline()
|
||||
powerline.renderer.render()
|
||||
powerline.renderer.shutdown()
|
||||
|
||||
def test_wm(self):
|
||||
from powerline.segments import common
|
||||
@ -83,7 +86,7 @@ class TestConfig(TestCase):
|
||||
reload(common)
|
||||
from powerline import Powerline
|
||||
with replace_module_attr(common, 'urllib_read', urllib_read):
|
||||
Powerline(ext='wm', renderer_module='pango_markup').renderer.render()
|
||||
Powerline(ext='wm', renderer_module='pango_markup', run_once=True).renderer.render()
|
||||
reload(common)
|
||||
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from powerline.lib import mergedicts, add_divider_highlight_group, humanize_bytes
|
||||
from powerline.lib import mergedicts, add_divider_highlight_group
|
||||
from powerline.lib.humanize_bytes import humanize_bytes
|
||||
from powerline.lib.vcs import guess
|
||||
from subprocess import call, PIPE
|
||||
import os
|
||||
|
@ -4,7 +4,7 @@ from powerline.segments import shell, common
|
||||
import tests.vim as vim_module
|
||||
import sys
|
||||
import os
|
||||
from tests.lib import Args, urllib_read, replace_module, replace_module_attr, new_module, replace_module_module, replace_env
|
||||
from tests.lib import Args, urllib_read, replace_module_attr, new_module, replace_module_module, replace_env
|
||||
from tests import TestCase
|
||||
|
||||
|
||||
@ -37,13 +37,15 @@ class TestCommon(TestCase):
|
||||
self.assertEqual(common.hostname(only_if_ssh=True), None)
|
||||
|
||||
def test_user(self):
|
||||
new_os = new_module('os', environ={'USER': 'def'})
|
||||
new_os = new_module('os', environ={'USER': 'def'}, getpid=lambda: 1)
|
||||
new_psutil = new_module('psutil', Process=lambda pid: Args(username='def'))
|
||||
with replace_module_attr(common, 'os', new_os):
|
||||
self.assertEqual(common.user(), [{'contents': 'def', 'highlight_group': 'user'}])
|
||||
new_os.geteuid = lambda: 1
|
||||
self.assertEqual(common.user(), [{'contents': 'def', 'highlight_group': 'user'}])
|
||||
new_os.geteuid = lambda: 0
|
||||
self.assertEqual(common.user(), [{'contents': 'def', 'highlight_group': ['superuser', 'user']}])
|
||||
with replace_module_attr(common, 'psutil', new_psutil):
|
||||
self.assertEqual(common.user(), [{'contents': 'def', 'highlight_group': 'user'}])
|
||||
new_os.geteuid = lambda: 1
|
||||
self.assertEqual(common.user(), [{'contents': 'def', 'highlight_group': 'user'}])
|
||||
new_os.geteuid = lambda: 0
|
||||
self.assertEqual(common.user(), [{'contents': 'def', 'highlight_group': ['superuser', 'user']}])
|
||||
|
||||
def test_branch(self):
|
||||
with replace_module_attr(common, 'guess', lambda path: Args(branch=lambda: os.path.basename(path), status=lambda: None)):
|
||||
@ -128,12 +130,41 @@ class TestCommon(TestCase):
|
||||
self.assertEqual(common.external_ip(), [{'contents': '127.0.0.1', 'divider_highlight_group': 'background:divider'}])
|
||||
|
||||
def test_uptime(self):
|
||||
# TODO
|
||||
pass
|
||||
with replace_module_attr(common, '_get_uptime', lambda: 65536):
|
||||
self.assertEqual(common.uptime(), [{'contents': '0d 18h 12m', 'divider_highlight_group': 'background:divider'}])
|
||||
|
||||
def _get_uptime():
|
||||
raise NotImplementedError
|
||||
|
||||
with replace_module_attr(common, '_get_uptime', _get_uptime):
|
||||
self.assertEqual(common.uptime(), None)
|
||||
|
||||
def test_weather(self):
|
||||
# TODO
|
||||
pass
|
||||
with replace_module_attr(common, 'urllib_read', urllib_read):
|
||||
self.assertEqual(common.weather(), [
|
||||
{'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': '☁ '},
|
||||
{'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_cold', 'weather_temp', 'weather'], 'contents': '-11°C'}
|
||||
])
|
||||
self.assertEqual(common.weather(icons={'cloudy': 'o'}), [
|
||||
{'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'o '},
|
||||
{'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_cold', 'weather_temp', 'weather'], 'contents': '-11°C'}
|
||||
])
|
||||
self.assertEqual(common.weather(icons={'partly_cloudy_day': 'x'}), [
|
||||
{'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': 'x '},
|
||||
{'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_cold', 'weather_temp', 'weather'], 'contents': '-11°C'}
|
||||
])
|
||||
self.assertEqual(common.weather(unit='F'), [
|
||||
{'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': '☁ '},
|
||||
{'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_cold', 'weather_temp', 'weather'], 'contents': '12°F'}
|
||||
])
|
||||
self.assertEqual(common.weather(unit='K'), [
|
||||
{'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': '☁ '},
|
||||
{'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_cold', 'weather_temp', 'weather'], 'contents': '262K'}
|
||||
])
|
||||
self.assertEqual(common.weather(temperature_format='{temp:.1e}C'), [
|
||||
{'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_condition_partly_cloudy_day', 'weather_condition_cloudy', 'weather_conditions', 'weather'], 'contents': '☁ '},
|
||||
{'draw_divider': False, 'divider_highlight_group': 'background:divider', 'highlight_group': ['weather_temp_cold', 'weather_temp', 'weather'], 'contents': '-1.1e+01C'}
|
||||
])
|
||||
|
||||
def test_system_load(self):
|
||||
with replace_module_module(common, 'os', getloadavg=lambda: (7.5, 3.5, 1.5)):
|
||||
@ -148,12 +179,39 @@ class TestCommon(TestCase):
|
||||
{'contents': '2', 'highlight_group': ['system_load_bad', 'system_load'], 'draw_divider': False, 'divider_highlight_group': 'background:divider'}])
|
||||
|
||||
def test_cpu_load_percent(self):
|
||||
with replace_module('psutil', cpu_percent=lambda **kwargs: 52.3):
|
||||
with replace_module_module(common, 'psutil', cpu_percent=lambda **kwargs: 52.3):
|
||||
self.assertEqual(common.cpu_load_percent(), '52%')
|
||||
|
||||
def test_network_load(self):
|
||||
# TODO
|
||||
pass
|
||||
def _get_bytes(interface):
|
||||
return None
|
||||
with replace_module_attr(common, '_get_bytes', _get_bytes):
|
||||
self.assertEqual(common.network_load(), None)
|
||||
l = [0, 0]
|
||||
|
||||
def _get_bytes2(interface):
|
||||
l[0] += 1200
|
||||
l[1] += 2400
|
||||
return tuple(l)
|
||||
|
||||
from imp import reload
|
||||
reload(common)
|
||||
with replace_module_attr(common, '_get_bytes', _get_bytes2):
|
||||
common.network_load.startup()
|
||||
common.network_load.sleep(0)
|
||||
common.network_load.sleep(0)
|
||||
self.assertEqual(common.network_load(), [
|
||||
{'divider_highlight_group': 'background:divider', 'contents': '⬇ 1 KiB/s ⬆ 2 KiB/s'}
|
||||
])
|
||||
self.assertEqual(common.network_load(format='r {recv} s {sent}'), [
|
||||
{'divider_highlight_group': 'background:divider', 'contents': 'r 1 KiB/s s 2 KiB/s'}
|
||||
])
|
||||
self.assertEqual(common.network_load(format='r {recv} s {sent}', suffix='bps'), [
|
||||
{'divider_highlight_group': 'background:divider', 'contents': 'r 1 Kibps s 2 Kibps'}
|
||||
])
|
||||
self.assertEqual(common.network_load(format='r {recv} s {sent}', si_prefix=True), [
|
||||
{'divider_highlight_group': 'background:divider', 'contents': 'r 1 kB/s s 2 kB/s'}
|
||||
])
|
||||
|
||||
def test_virtualenv(self):
|
||||
with replace_env('VIRTUAL_ENV', '/abc/def/ghi'):
|
||||
@ -229,7 +287,7 @@ class TestVim(TestCase):
|
||||
|
||||
def test_file_size(self):
|
||||
segment_info = vim_module._get_segment_info()
|
||||
self.assertEqual(vim.file_size(segment_info=segment_info), None)
|
||||
self.assertEqual(vim.file_size(segment_info=segment_info), '0 B')
|
||||
with vim_module._with('buffer', os.path.join(os.path.dirname(__file__), 'empty')) as segment_info:
|
||||
self.assertEqual(vim.file_size(segment_info=segment_info), '0 B')
|
||||
|
||||
@ -267,16 +325,36 @@ class TestVim(TestCase):
|
||||
self.assertEqual(vim.modified_buffers(), None)
|
||||
|
||||
def test_branch(self):
|
||||
# TODO
|
||||
pass
|
||||
with vim_module._with('buffer', '/foo') as segment_info:
|
||||
with replace_module_attr(vim, 'guess', lambda path: Args(branch=lambda: os.path.basename(path), status=lambda: None, directory=path)):
|
||||
self.assertEqual(vim.branch(segment_info=segment_info),
|
||||
[{'divider_highlight_group': 'branch:divider', 'highlight_group': ['branch'], 'contents': 'foo'}])
|
||||
self.assertEqual(vim.branch(segment_info=segment_info, status_colors=True),
|
||||
[{'divider_highlight_group': 'branch:divider', 'highlight_group': ['branch_clean', 'branch'], 'contents': 'foo'}])
|
||||
with replace_module_attr(vim, 'guess', lambda path: Args(branch=lambda: os.path.basename(path), status=lambda: 'DU', directory=path)):
|
||||
self.assertEqual(vim.branch(segment_info=segment_info),
|
||||
[{'divider_highlight_group': 'branch:divider', 'highlight_group': ['branch'], 'contents': 'foo'}])
|
||||
self.assertEqual(vim.branch(segment_info=segment_info, status_colors=True),
|
||||
[{'divider_highlight_group': 'branch:divider', 'highlight_group': ['branch_dirty', 'branch'], 'contents': 'foo'}])
|
||||
|
||||
def test_file_vcs_status(self):
|
||||
# TODO
|
||||
pass
|
||||
with vim_module._with('buffer', '/foo') as segment_info:
|
||||
with replace_module_attr(vim, 'guess', lambda path: Args(branch=lambda: os.path.basename(path), status=lambda file: 'M', directory=path)):
|
||||
self.assertEqual(vim.file_vcs_status(segment_info=segment_info),
|
||||
[{'highlight_group': ['file_vcs_status_M', 'file_vcs_status'], 'contents': 'M'}])
|
||||
with replace_module_attr(vim, 'guess', lambda path: Args(branch=lambda: os.path.basename(path), status=lambda file: None, directory=path)):
|
||||
self.assertEqual(vim.file_vcs_status(segment_info=segment_info), None)
|
||||
with vim_module._with('buffer', '/bar') as segment_info:
|
||||
with vim_module._with('bufoptions', buftype='nofile'):
|
||||
with replace_module_attr(vim, 'guess', lambda path: Args(branch=lambda: os.path.basename(path), status=lambda file: 'M', directory=path)):
|
||||
self.assertEqual(vim.file_vcs_status(segment_info=segment_info), None)
|
||||
|
||||
def test_repository_status(self):
|
||||
# TODO
|
||||
pass
|
||||
segment_info = vim_module._get_segment_info()
|
||||
with replace_module_attr(vim, 'guess', lambda path: Args(branch=lambda: os.path.basename(path), status=lambda: None, directory=path)):
|
||||
self.assertEqual(vim.repository_status(segment_info=segment_info), None)
|
||||
with replace_module_attr(vim, 'guess', lambda path: Args(branch=lambda: os.path.basename(path), status=lambda: 'DU', directory=path)):
|
||||
self.assertEqual(vim.repository_status(segment_info=segment_info), 'DU')
|
||||
|
||||
|
||||
old_cwd = None
|
||||
|
16
tests/vim.py
16
tests/vim.py
@ -150,6 +150,13 @@ def _emul_exists(varname):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@_logged
|
||||
def _emul_line2byte(line):
|
||||
buflines = _buf_lines[_buffer()]
|
||||
if line == len(buflines) + 1:
|
||||
return sum((len(s) for s in buflines)) + 1
|
||||
raise NotImplementedError
|
||||
|
||||
_window_ids = [None]
|
||||
_window_id = 0
|
||||
_win_scopes = [None]
|
||||
@ -242,6 +249,15 @@ class _Buffer(object):
|
||||
_buf_scopes.pop(bufnr)
|
||||
|
||||
|
||||
class _Current(object):
|
||||
@property
|
||||
def buffer(self):
|
||||
return buffers[_buffer()]
|
||||
|
||||
|
||||
current = _Current()
|
||||
|
||||
|
||||
_dict = None
|
||||
|
||||
|
||||
|
646
tools/colors.map
Normal file
646
tools/colors.map
Normal file
@ -0,0 +1,646 @@
|
||||
Grey 545454
|
||||
Grey, Silver C0C0C0
|
||||
grey BEBEBE
|
||||
LightGray D3D3D3
|
||||
LightSlateGrey 778899
|
||||
SlateGray 708090
|
||||
SlateGray1 C6E2FF
|
||||
SlateGray2 B9D3EE
|
||||
SlateGray3 9FB6CD
|
||||
SlateGray4 6C7B8B
|
||||
black 000000
|
||||
grey0 000000
|
||||
grey1 030303
|
||||
grey2 050505
|
||||
grey3 080808
|
||||
grey4 0A0A0A
|
||||
grey5 0D0D0D
|
||||
grey6 0F0F0F
|
||||
grey7 121212
|
||||
grey8 141414
|
||||
grey9 171717
|
||||
grey10 1A1A1A
|
||||
grey11 1C1C1C
|
||||
grey12 1F1F1F
|
||||
grey13 212121
|
||||
grey14 242424
|
||||
grey15 262626
|
||||
grey16 292929
|
||||
grey17 2B2B2B
|
||||
grey18 2E2E2E
|
||||
grey19 303030
|
||||
grey20 333333
|
||||
grey21 363636
|
||||
grey22 383838
|
||||
grey23 3B3B3B
|
||||
grey24 3D3D3D
|
||||
grey25 404040
|
||||
grey26 424242
|
||||
grey27 454545
|
||||
grey28 474747
|
||||
grey29 4A4A4A
|
||||
grey30 4D4D4D
|
||||
grey31 4F4F4F
|
||||
grey32 525252
|
||||
grey33 545454
|
||||
grey34 575757
|
||||
grey35 595959
|
||||
grey36 5C5C5C
|
||||
grey37 5E5E5E
|
||||
grey38 616161
|
||||
grey39 636363
|
||||
grey40 666666
|
||||
grey41, DimGrey 696969
|
||||
grey42 6B6B6B
|
||||
grey43 6E6E6E
|
||||
grey44 707070
|
||||
grey45 737373
|
||||
grey46 757575
|
||||
grey47 787878
|
||||
grey48 7A7A7A
|
||||
grey49 7D7D7D
|
||||
grey50 7F7F7F
|
||||
grey51 828282
|
||||
grey52 858585
|
||||
grey53 878787
|
||||
grey54 8A8A8A
|
||||
grey55 8C8C8C
|
||||
grey56 8F8F8F
|
||||
grey57 919191
|
||||
grey58 949494
|
||||
grey59 969696
|
||||
grey60 999999
|
||||
grey61 9C9C9C
|
||||
grey62 9E9E9E
|
||||
grey63 A1A1A1
|
||||
grey64 A3A3A3
|
||||
grey65 A6A6A6
|
||||
grey66 A8A8A8
|
||||
grey67 ABABAB
|
||||
grey68 ADADAD
|
||||
grey69 B0B0B0
|
||||
grey70 B3B3B3
|
||||
grey71 B5B5B5
|
||||
grey72 B8B8B8
|
||||
grey73 BABABA
|
||||
grey74 BDBDBD
|
||||
grey75 BFBFBF
|
||||
grey76 C2C2C2
|
||||
grey77 C4C4C4
|
||||
grey78 C7C7C7
|
||||
grey79 C9C9C9
|
||||
grey80 CCCCCC
|
||||
grey81 CFCFCF
|
||||
grey82 D1D1D1
|
||||
grey83 D4D4D4
|
||||
grey84 D6D6D6
|
||||
grey85 D9D9D9
|
||||
grey86 DBDBDB
|
||||
grey87 DEDEDE
|
||||
grey88 E0E0E0
|
||||
grey89 E3E3E3
|
||||
grey90 E5E5E5
|
||||
grey91 E8E8E8
|
||||
grey92 EBEBEB
|
||||
grey93 EDEDED
|
||||
grey94 F0F0F0
|
||||
grey95 F2F2F2
|
||||
grey96 F5F5F5
|
||||
grey97 F7F7F7
|
||||
grey98 FAFAFA
|
||||
grey99 FCFCFC
|
||||
grey100, White FFFFFF
|
||||
Dark Slate Grey 2F4F4F
|
||||
Dim Grey 545454
|
||||
Very Light Grey CDCDCD
|
||||
Free Speech Grey 635688
|
||||
AliceBlue F0F8FF
|
||||
BlueViolet 8A2BE2
|
||||
Cadet Blue 5F9F9F
|
||||
CadetBlue 5F9EA0
|
||||
CadetBlue 5F9EA0
|
||||
CadetBlue1 98F5FF
|
||||
CadetBlue2 8EE5EE
|
||||
CadetBlue3 7AC5CD
|
||||
CadetBlue4 53868B
|
||||
Corn Flower Blue 42426F
|
||||
CornflowerBlue 6495ED
|
||||
DarkSlateBlue 483D8B
|
||||
DarkTurquoise 00CED1
|
||||
DeepSkyBlue 00BFFF
|
||||
DeepSkyBlue1 00BFFF
|
||||
DeepSkyBlue2 00B2EE
|
||||
DeepSkyBlue3 009ACD
|
||||
DeepSkyBlue4 00688B
|
||||
DodgerBlue 1E90FF
|
||||
DodgerBlue1 1E90FF
|
||||
DodgerBlue2 1C86EE
|
||||
DodgerBlue3 1874CD
|
||||
DodgerBlue4 104E8B
|
||||
LightBlue ADD8E6
|
||||
LightBlue1 BFEFFF
|
||||
LightBlue2 B2DFEE
|
||||
LightBlue3 9AC0CD
|
||||
LightBlue4 68838B
|
||||
LightCyan E0FFFF
|
||||
LightCyan1 E0FFFF
|
||||
LightCyan2 D1EEEE
|
||||
LightCyan3 B4CDCD
|
||||
LightCyan4 7A8B8B
|
||||
LightSkyBlue 87CEFA
|
||||
LightSkyBlue1 B0E2FF
|
||||
LightSkyBlue2 A4D3EE
|
||||
LightSkyBlue3 8DB6CD
|
||||
LightSkyBlue4 607B8B
|
||||
LightSlateBlue 8470FF
|
||||
LightSteelBlue B0C4DE
|
||||
LightSteelBlue1 CAE1FF
|
||||
LightSteelBlue2 BCD2EE
|
||||
LightSteelBlue3 A2B5CD
|
||||
LightSteelBlue4 6E7B8B
|
||||
Aquamarine 70DB93
|
||||
MediumBlue 0000CD
|
||||
MediumSlateBlue 7B68EE
|
||||
MediumTurquoise 48D1CC
|
||||
MidnightBlue 191970
|
||||
NavyBlue 000080
|
||||
PaleTurquoise AFEEEE
|
||||
PaleTurquoise1 BBFFFF
|
||||
PaleTurquoise2 AEEEEE
|
||||
PaleTurquoise3 96CDCD
|
||||
PaleTurquoise4 668B8B
|
||||
PowderBlue B0E0E6
|
||||
RoyalBlue 4169E1
|
||||
RoyalBlue1 4876FF
|
||||
RoyalBlue2 436EEE
|
||||
RoyalBlue3 3A5FCD
|
||||
RoyalBlue4 27408B
|
||||
RoyalBlue5 002266
|
||||
SkyBlue 87CEEB
|
||||
SkyBlue1 87CEFF
|
||||
SkyBlue2 7EC0EE
|
||||
SkyBlue3 6CA6CD
|
||||
SkyBlue4 4A708B
|
||||
SlateBlue 6A5ACD
|
||||
SlateBlue1 836FFF
|
||||
SlateBlue2 7A67EE
|
||||
SlateBlue3 6959CD
|
||||
SlateBlue4 473C8B
|
||||
SteelBlue 4682B4
|
||||
SteelBlue1 63B8FF
|
||||
SteelBlue2 5CACEE
|
||||
SteelBlue3 4F94CD
|
||||
SteelBlue4 36648B
|
||||
aquamarine 7FFFD4
|
||||
aquamarine1 7FFFD4
|
||||
aquamarine2 76EEC6
|
||||
aquamarine3, MediumAquamarine 66CDAA
|
||||
aquamarine4 458B74
|
||||
azure F0FFFF
|
||||
azure1 F0FFFF
|
||||
azure2 E0EEEE
|
||||
azure3 C1CDCD
|
||||
azure4 838B8B
|
||||
blue 0000FF
|
||||
blue1 0000FF
|
||||
blue2 0000EE
|
||||
blue3 0000CD
|
||||
blue4 00008B
|
||||
aqua 00FFFF
|
||||
True Iris Blue 03B4CC
|
||||
cyan 00FFFF
|
||||
cyan1 00FFFF
|
||||
cyan2 00EEEE
|
||||
cyan3 00CDCD
|
||||
cyan4 008B8B
|
||||
navy 000080
|
||||
teal 008080
|
||||
turquoise 40E0D0
|
||||
turquoise1 00F5FF
|
||||
turquoise2 00E5EE
|
||||
turquoise3 00C5CD
|
||||
turquoise4 00868B
|
||||
DarkSlateGray 2F4F4F
|
||||
DarkSlateGray1 97FFFF
|
||||
DarkSlateGray2 8DEEEE
|
||||
DarkSlateGray3 79CDCD
|
||||
DarkSlateGray4 528B8B
|
||||
Dark Slate Blue 241882
|
||||
Dark Turquoise 7093DB
|
||||
Medium Slate Blue 7F00FF
|
||||
Medium Turquoise 70DBDB
|
||||
Midnight Blue 2F2F4F
|
||||
Navy Blue 23238E
|
||||
Neon Blue 4D4DFF
|
||||
New Midnight Blue 00009C
|
||||
Rich Blue 5959AB
|
||||
Sky Blue 3299CC
|
||||
Slate Blue 007FFF
|
||||
Summer Sky 38B0DE
|
||||
Iris Blue 03B4C8
|
||||
Free Speech Blue 4156C5
|
||||
RosyBrown BC8F8F
|
||||
RosyBrown1 FFC1C1
|
||||
RosyBrown2 EEB4B4
|
||||
RosyBrown3 CD9B9B
|
||||
RosyBrown4 8B6969
|
||||
SaddleBrown 8B4513
|
||||
SandyBrown F4A460
|
||||
beige F5F5DC
|
||||
brown A52A2A
|
||||
brown A62A2A
|
||||
brown1 FF4040
|
||||
brown2 EE3B3B
|
||||
brown3 CD3333
|
||||
brown4 8B2323
|
||||
dark brown 5C4033
|
||||
burlywood DEB887
|
||||
burlywood1 FFD39B
|
||||
burlywood2 EEC591
|
||||
burlywood3 CDAA7D
|
||||
burlywood4 8B7355
|
||||
baker's chocolate 5C3317
|
||||
chocolate D2691E
|
||||
chocolate1 FF7F24
|
||||
chocolate2 EE7621
|
||||
chocolate3 CD661D
|
||||
chocolate4 8B4513
|
||||
peru CD853F
|
||||
tan D2B48C
|
||||
tan1 FFA54F
|
||||
tan2 EE9A49
|
||||
tan3 CD853F
|
||||
tan4 8B5A2B
|
||||
Dark Tan 97694F
|
||||
Dark Wood 855E42
|
||||
Light Wood 856363
|
||||
Medium Wood A68064
|
||||
New Tan EBC79E
|
||||
Semi-Sweet Chocolate 6B4226
|
||||
Sienna 8E6B23
|
||||
Tan DB9370
|
||||
Very Dark Brown 5C4033
|
||||
Dark Green 2F4F2F
|
||||
DarkGreen 006400
|
||||
dark green copper 4A766E
|
||||
DarkKhaki BDB76B
|
||||
DarkOliveGreen 556B2F
|
||||
DarkOliveGreen1 CAFF70
|
||||
DarkOliveGreen2 BCEE68
|
||||
DarkOliveGreen3 A2CD5A
|
||||
DarkOliveGreen4 6E8B3D
|
||||
olive 808000
|
||||
DarkSeaGreen 8FBC8F
|
||||
DarkSeaGreen1 C1FFC1
|
||||
DarkSeaGreen2 B4EEB4
|
||||
DarkSeaGreen3 9BCD9B
|
||||
DarkSeaGreen4 698B69
|
||||
ForestGreen 228B22
|
||||
GreenYellow ADFF2F
|
||||
LawnGreen 7CFC00
|
||||
LightSeaGreen 20B2AA
|
||||
LimeGreen 32CD32
|
||||
MediumSeaGreen 3CB371
|
||||
MediumSpringGreen 00FA9A
|
||||
MintCream F5FFFA
|
||||
OliveDrab 6B8E23
|
||||
OliveDrab1 C0FF3E
|
||||
OliveDrab2 B3EE3A
|
||||
OliveDrab3 9ACD32
|
||||
OliveDrab4 698B22
|
||||
PaleGreen 98FB98
|
||||
PaleGreen1 9AFF9A
|
||||
PaleGreen2 90EE90
|
||||
PaleGreen3 7CCD7C
|
||||
PaleGreen4 548B54
|
||||
SeaGreen, SeaGreen4 2E8B57
|
||||
SeaGreen1 54FF9F
|
||||
SeaGreen2 4EEE94
|
||||
SeaGreen3 43CD80
|
||||
SpringGreen 00FF7F
|
||||
SpringGreen1 00FF7F
|
||||
SpringGreen2 00EE76
|
||||
SpringGreen3 00CD66
|
||||
SpringGreen4 008B45
|
||||
YellowGreen 9ACD32
|
||||
chartreuse 7FFF00
|
||||
chartreuse1 7FFF00
|
||||
chartreuse2 76EE00
|
||||
chartreuse3 66CD00
|
||||
chartreuse4 458B00
|
||||
green 00FF00
|
||||
green 008000
|
||||
lime 00FF00
|
||||
green1 00FF00
|
||||
green2 00EE00
|
||||
green3 00CD00
|
||||
green4 008B00
|
||||
khaki F0E68C
|
||||
khaki1 FFF68F
|
||||
khaki2 EEE685
|
||||
khaki3 CDC673
|
||||
khaki4 8B864E
|
||||
Dark Olive Green 4F4F2F
|
||||
Green Yellow <a href=#sic>[sic]</a> D19275
|
||||
Hunter Green <a href=#sic>[sic]</a> 8E2323
|
||||
Forest Green, Khaki, Medium Aquamarine 238E23
|
||||
Medium Forest Green DBDB70
|
||||
Medium Sea Green 426F42
|
||||
Medium Spring Green 7FFF00
|
||||
Pale Green 8FBC8F
|
||||
Sea Green 238E68
|
||||
Spring Green 00FF7F
|
||||
Free Speech Green 09F911
|
||||
Free Speech Aquamarine 029D74
|
||||
DarkOrange FF8C00
|
||||
DarkOrange1 FF7F00
|
||||
DarkOrange2 EE7600
|
||||
DarkOrange3 CD6600
|
||||
DarkOrange4 8B4500
|
||||
DarkSalmon E9967A
|
||||
LightCoral F08080
|
||||
LightSalmon FFA07A
|
||||
LightSalmon1 FFA07A
|
||||
LightSalmon2 EE9572
|
||||
LightSalmon3 CD8162
|
||||
LightSalmon4 8B5742
|
||||
PeachPuff FFDAB9
|
||||
PeachPuff1 FFDAB9
|
||||
PeachPuff2 EECBAD
|
||||
PeachPuff3 CDAF95
|
||||
PeachPuff4 8B7765
|
||||
bisque FFE4C4
|
||||
bisque1 FFE4C4
|
||||
bisque2 EED5B7
|
||||
bisque3 CDB79E
|
||||
bisque4 8B7D6B
|
||||
coral FF7F00
|
||||
coral FF7F50
|
||||
coral1 FF7256
|
||||
coral2 EE6A50
|
||||
coral3 CD5B45
|
||||
coral4 8B3E2F
|
||||
honeydew F0FFF0
|
||||
honeydew1 F0FFF0
|
||||
honeydew2 E0EEE0
|
||||
honeydew3 C1CDC1
|
||||
honeydew4 838B83
|
||||
orange FFA500
|
||||
orange1 FFA500
|
||||
orange2 EE9A00
|
||||
orange3 CD8500
|
||||
orange4 8B5A00
|
||||
salmon FA8072
|
||||
salmon1 FF8C69
|
||||
salmon2 EE8262
|
||||
salmon3 CD7054
|
||||
salmon4 8B4C39
|
||||
sienna A0522D
|
||||
sienna1 FF8247
|
||||
sienna2 EE7942
|
||||
sienna3 CD6839
|
||||
sienna4 8B4726
|
||||
Mandarian Orange 8E2323
|
||||
Orange FF7F00
|
||||
Orange Red FF2400
|
||||
DeepPink FF1493
|
||||
DeepPink1 FF1493
|
||||
DeepPink2 EE1289
|
||||
DeepPink3 CD1076
|
||||
DeepPink4 8B0A50
|
||||
HotPink FF69B4
|
||||
HotPink1 FF6EB4
|
||||
HotPink2 EE6AA7
|
||||
HotPink3 CD6090
|
||||
HotPink4 8B3A62
|
||||
IndianRed CD5C5C
|
||||
IndianRed1 FF6A6A
|
||||
IndianRed2 EE6363
|
||||
IndianRed3 CD5555
|
||||
IndianRed4 8B3A3A
|
||||
LightPink FFB6C1
|
||||
LightPink1 FFAEB9
|
||||
LightPink2 EEA2AD
|
||||
LightPink3 CD8C95
|
||||
LightPink4 8B5F65
|
||||
MediumVioletRed C71585
|
||||
MistyRose FFE4E1
|
||||
MistyRose1 FFE4E1
|
||||
MistyRose2 EED5D2
|
||||
MistyRose3 CDB7B5
|
||||
MistyRose4 8B7D7B
|
||||
OrangeRed FF4500
|
||||
OrangeRed1 FF4500
|
||||
OrangeRed2 EE4000
|
||||
OrangeRed3 CD3700
|
||||
OrangeRed4 8B2500
|
||||
PaleVioletRed DB7093
|
||||
PaleVioletRed1 FF82AB
|
||||
PaleVioletRed2 EE799F
|
||||
PaleVioletRed3 CD6889
|
||||
PaleVioletRed4 8B475D
|
||||
VioletRed D02090
|
||||
VioletRed1 FF3E96
|
||||
VioletRed2 EE3A8C
|
||||
VioletRed3 CD3278
|
||||
VioletRed4 8B2252
|
||||
firebrick B22222
|
||||
firebrick1 FF3030
|
||||
firebrick2 EE2C2C
|
||||
firebrick3 CD2626
|
||||
firebrick4 8B1A1A
|
||||
pink FFC0CB
|
||||
pink1 FFB5C5
|
||||
pink2 EEA9B8
|
||||
pink3 CD919E
|
||||
pink4 8B636C
|
||||
Flesh F5CCB0
|
||||
Feldspar D19275
|
||||
red FF0000
|
||||
red1 FF0000
|
||||
red2 EE0000
|
||||
red3 CD0000
|
||||
red4 8B0000
|
||||
tomato FF6347
|
||||
tomato1 FF6347
|
||||
tomato2 EE5C42
|
||||
tomato3 CD4F39
|
||||
tomato4 8B3626
|
||||
Dusty Rose 856363
|
||||
Firebrick 8E2323
|
||||
Indian Red F5CCB0
|
||||
Pink BC8F8F
|
||||
Salmon 6F4242
|
||||
Scarlet 8C1717
|
||||
Spicy Pink FF1CAE
|
||||
Free Speech Magenta E35BD8
|
||||
Free Speech Red C00000
|
||||
DarkOrchid 9932CC
|
||||
DarkOrchid1 BF3EFF
|
||||
DarkOrchid2 B23AEE
|
||||
DarkOrchid3 9A32CD
|
||||
DarkOrchid4 68228B
|
||||
DarkViolet 9400D3
|
||||
LavenderBlush FFF0F5
|
||||
LavenderBlush1 FFF0F5
|
||||
LavenderBlush2 EEE0E5
|
||||
LavenderBlush3 CDC1C5
|
||||
LavenderBlush4 8B8386
|
||||
MediumOrchid BA55D3
|
||||
MediumOrchid1 E066FF
|
||||
MediumOrchid2 D15FEE
|
||||
MediumOrchid3 B452CD
|
||||
MediumOrchid4 7A378B
|
||||
MediumPurple 9370DB
|
||||
Medium Orchid 9370DB
|
||||
MediumPurple1 AB82FF
|
||||
Dark Orchid 9932CD
|
||||
MediumPurple2 9F79EE
|
||||
MediumPurple3 8968CD
|
||||
MediumPurple4 5D478B
|
||||
lavender E6E6FA
|
||||
magenta FF00FF
|
||||
fuchsia FF00FF
|
||||
magenta1 FF00FF
|
||||
magenta2 EE00EE
|
||||
magenta3 CD00CD
|
||||
magenta4 8B008B
|
||||
maroon B03060
|
||||
maroon1 FF34B3
|
||||
maroon2 EE30A7
|
||||
maroon3 CD2990
|
||||
maroon4 8B1C62
|
||||
orchid DA70D6
|
||||
Orchid DB70DB
|
||||
orchid1 FF83FA
|
||||
orchid2 EE7AE9
|
||||
orchid3 CD69C9
|
||||
orchid4 8B4789
|
||||
plum DDA0DD
|
||||
plum1 FFBBFF
|
||||
plum2 EEAEEE
|
||||
plum3 CD96CD
|
||||
plum4 8B668B
|
||||
purple A020F0
|
||||
purple 800080
|
||||
purple1 9B30FF
|
||||
purple2 912CEE
|
||||
purple3 7D26CD
|
||||
purple4 551A8B
|
||||
thistle D8BFD8
|
||||
thistle1 FFE1FF
|
||||
thistle2 EED2EE
|
||||
thistle3 CDB5CD
|
||||
thistle4 8B7B8B
|
||||
violet EE82EE
|
||||
violet blue 9F5F9F
|
||||
Dark Purple 871F78
|
||||
Maroon 800000
|
||||
Medium Violet Red DB7093
|
||||
Neon Pink FF6EC7
|
||||
Plum EAADEA
|
||||
Thistle D8BFD8
|
||||
Turquoise ADEAEA
|
||||
Violet 4F2F4F
|
||||
Violet Red CC3299
|
||||
AntiqueWhite FAEBD7
|
||||
AntiqueWhite1 FFEFDB
|
||||
AntiqueWhite2 EEDFCC
|
||||
AntiqueWhite3 CDC0B0
|
||||
AntiqueWhite4 8B8378
|
||||
FloralWhite FFFAF0
|
||||
GhostWhite F8F8FF
|
||||
NavajoWhite FFDEAD
|
||||
NavajoWhite1 FFDEAD
|
||||
NavajoWhite2 EECFA1
|
||||
NavajoWhite3 CDB38B
|
||||
NavajoWhite4 8B795E
|
||||
OldLace FDF5E6
|
||||
WhiteSmoke F5F5F5
|
||||
gainsboro DCDCDC
|
||||
ivory FFFFF0
|
||||
ivory1 FFFFF0
|
||||
ivory2 EEEEE0
|
||||
ivory3 CDCDC1
|
||||
ivory4 8B8B83
|
||||
linen FAF0E6
|
||||
seashell FFF5EE
|
||||
seashell1 FFF5EE
|
||||
seashell2 EEE5DE
|
||||
seashell3 CDC5BF
|
||||
seashell4 8B8682
|
||||
snow FFFAFA
|
||||
snow1 FFFAFA
|
||||
snow2 EEE9E9
|
||||
snow3 CDC9C9
|
||||
snow4 8B8989
|
||||
wheat F5DEB3
|
||||
wheat1 FFE7BA
|
||||
wheat2 EED8AE
|
||||
wheat3 CDBA96
|
||||
wheat4 8B7E66
|
||||
white FFFFFF
|
||||
Quartz D9D9F3
|
||||
Wheat D8D8BF
|
||||
BlanchedAlmond FFEBCD
|
||||
DarkGoldenrod B8860B
|
||||
DarkGoldenrod1 FFB90F
|
||||
DarkGoldenrod2 EEAD0E
|
||||
DarkGoldenrod3 CD950C
|
||||
DarkGoldenrod4 8B6508
|
||||
LemonChiffon FFFACD
|
||||
LemonChiffon1 FFFACD
|
||||
LemonChiffon2 EEE9BF
|
||||
LemonChiffon3 CDC9A5
|
||||
LemonChiffon4 8B8970
|
||||
LightGoldenrod EEDD82
|
||||
LightGoldenrod1 FFEC8B
|
||||
LightGoldenrod2 EEDC82
|
||||
LightGoldenrod3 CDBE70
|
||||
LightGoldenrod4 8B814C
|
||||
LightGoldenrodYellow FAFAD2
|
||||
LightYellow FFFFE0
|
||||
LightYellow1 FFFFE0
|
||||
LightYellow2 EEEED1
|
||||
LightYellow3 CDCDB4
|
||||
LightYellow4 8B8B7A
|
||||
PaleGoldenrod EEE8AA
|
||||
PapayaWhip FFEFD5
|
||||
cornsilk FFF8DC
|
||||
cornsilk1 FFF8DC
|
||||
cornsilk2 EEE8CD
|
||||
cornsilk3 CDC8B1
|
||||
cornsilk4 8B8878
|
||||
goldenrod DAA520
|
||||
goldenrod1 FFC125
|
||||
goldenrod2 EEB422
|
||||
goldenrod3 CD9B1D
|
||||
goldenrod4 8B6914
|
||||
moccasin FFE4B5
|
||||
yellow FFFF00
|
||||
yellow1 FFFF00
|
||||
yellow2 EEEE00
|
||||
yellow3 CDCD00
|
||||
yellow4 8B8B00
|
||||
gold FFD700
|
||||
gold1 FFD700
|
||||
gold2 EEC900
|
||||
gold3 CDAD00
|
||||
gold4 8B7500
|
||||
Goldenrod DBDB70
|
||||
Medium Goldenrod EAEAAE
|
||||
Yellow Green 99CC32
|
||||
copper B87333
|
||||
cool copper D98719
|
||||
Green Copper 856363
|
||||
brass B5A642
|
||||
bronze 8C7853
|
||||
bronze II A67D3D
|
||||
bright gold D9D919
|
||||
Old Gold CFB53B
|
||||
CSS Gold CC9900
|
||||
gold CD7F32
|
||||
silver E6E8FA
|
||||
Silver, Grey C0C0C0
|
||||
Light Steel Blue 545454
|
||||
Steel Blue 236B8E
|
38
tools/colors_find.py
Normal file
38
tools/colors_find.py
Normal file
@ -0,0 +1,38 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
def get_color(name, rgb):
|
||||
return name, (int(rgb[:2], 16), int(rgb[2:4], 16), int(rgb[4:6], 16))
|
||||
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), 'colors.map'), 'r') as f:
|
||||
colors = [get_color(*line.split('\t')) for line in f]
|
||||
|
||||
|
||||
urgb = get_color(None, sys.argv[1])[1]
|
||||
|
||||
|
||||
def col_distance(rgb1, rgb2):
|
||||
return sum(((rgb1[i] - rgb2[i]) ** 2 for i in range(3)))
|
||||
|
||||
|
||||
def find_color(urgb, colors):
|
||||
cur_distance = 3 * (255 ** 2 + 1)
|
||||
cur_color = None
|
||||
for color, crgb in colors:
|
||||
dist = col_distance(urgb, crgb)
|
||||
if dist < cur_distance:
|
||||
cur_distance = dist
|
||||
cur_color = (color, crgb)
|
||||
return cur_color
|
||||
|
||||
|
||||
cur_color = find_color(urgb, colors)
|
||||
|
||||
print urgb, ':', cur_color
|
||||
|
||||
col_1 = ';2;' + ';'.join((str(i) for i in urgb)) + 'm'
|
||||
col_2 = ';2;' + ';'.join((str(i) for i in cur_color[1])) + 'm'
|
||||
sys.stdout.write('\033[48' + col_1 + '\033[38' + col_2 + 'abc\033[0m <-- bg:urgb, fg:crgb\n')
|
||||
sys.stdout.write('\033[48' + col_2 + '\033[38' + col_1 + 'abc\033[0m <-- bg:crgb, fg:urgb\n')
|
105
tools/generate_gradients.py
Normal file
105
tools/generate_gradients.py
Normal file
@ -0,0 +1,105 @@
|
||||
import sys
|
||||
import json
|
||||
from powerline.colorscheme import cterm_to_hex
|
||||
from itertools import groupby
|
||||
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
sys.stderr.write('''
|
||||
Usage: generate_gradients.py colors itemnum[ "show"]
|
||||
|
||||
colors: JSON list with either cterm ([200, 42, 6]) or RGB (["abcdef",
|
||||
"feffef"]) colors.
|
||||
|
||||
itemnum: number of items in generated gradient.
|
||||
|
||||
"show": static string, determines whether gradient sample should be
|
||||
printed to stdout as well.
|
||||
''')
|
||||
|
||||
|
||||
def linear_gradient(start_value, stop_value, start_offset, stop_offset, offset):
|
||||
return start_value + ((offset - start_offset) * (stop_value - start_value) / (stop_offset - start_offset))
|
||||
|
||||
|
||||
def gradient(DATA):
|
||||
def gradient_function(y):
|
||||
initial_offset = 0
|
||||
for offset, start, end in DATA:
|
||||
if y <= offset:
|
||||
return [linear_gradient(start[i], end[i], initial_offset, offset, y) for i in range(3)]
|
||||
initial_offset = offset
|
||||
return gradient_function
|
||||
|
||||
|
||||
def get_color(rgb):
|
||||
if type(rgb) is unicode:
|
||||
return int(rgb[:2], 16), int(rgb[2:4], 16), int(rgb[4:6], 16)
|
||||
else:
|
||||
return rgbint_to_rgb(cterm_to_hex[rgb])
|
||||
|
||||
|
||||
def get_rgb(*args):
|
||||
return "%02x%02x%02x" % args
|
||||
|
||||
|
||||
def col_distance(rgb1, rgb2):
|
||||
return sum(((rgb1[i] - rgb2[i]) ** 2 for i in range(3)))
|
||||
|
||||
|
||||
def rgbint_to_rgb(rgbint):
|
||||
return ((rgbint >> 16) & 0xFF, (rgbint >> 8) & 0xFF, rgbint & 0xFF)
|
||||
|
||||
|
||||
def find_color(urgb, colors):
|
||||
cur_distance = 3 * (255 ** 2 + 1)
|
||||
cur_color = None
|
||||
i = 0
|
||||
for crgbint in colors:
|
||||
crgb = rgbint_to_rgb(crgbint)
|
||||
dist = col_distance(urgb, crgb)
|
||||
if dist < cur_distance:
|
||||
cur_distance = dist
|
||||
cur_color = (i, crgb)
|
||||
i += 1
|
||||
return cur_color
|
||||
|
||||
|
||||
def print_color(color):
|
||||
if type(color) is int:
|
||||
colstr = '5;' + str(color)
|
||||
else:
|
||||
colstr = '2;' + ';'.join((str(i) for i in color))
|
||||
sys.stdout.write('\033[48;' + colstr + 'm ')
|
||||
|
||||
|
||||
def print_colors(colors):
|
||||
for i in range(101):
|
||||
color = colors[int(round(i * (len(colors) - 1) / 100))]
|
||||
print_color(color)
|
||||
sys.stdout.write('\033[0m\n')
|
||||
|
||||
|
||||
c = [get_color(color) for color in json.loads(sys.argv[1])]
|
||||
m = int(sys.argv[2]) if len(sys.argv) > 2 else 100
|
||||
m += m % (len(c) - 1)
|
||||
step = m / (len(c) - 1)
|
||||
data = [(i * step, c[i - 1], c[i]) for i in range(1, len(c))]
|
||||
gr_func = gradient(data)
|
||||
gradient = [gr_func(y) for y in range(0, m - 1)]
|
||||
r = [get_rgb(*color) for color in gradient]
|
||||
r2 = [find_color(color, cterm_to_hex)[0] for color in gradient]
|
||||
r3 = [i[0] for i in groupby(r2)]
|
||||
print json.dumps(r)
|
||||
print json.dumps(r2)
|
||||
print json.dumps(r3)
|
||||
if len(sys.argv) > 3 and sys.argv[3] == 'show':
|
||||
print_colors(gradient)
|
||||
print_colors(r2)
|
||||
print_colors(r3)
|
||||
sys.stdout.write('0')
|
||||
sys.stdout.write(''.join(('%10u' % (i * 10) for i in range(1, 11))))
|
||||
sys.stdout.write('\n')
|
||||
nums = (''.join((str(i) for i in range(10))))
|
||||
sys.stdout.write(''.join(((('\033[1m' if j % 2 else '\033[0m') + nums) for j in range(10))))
|
||||
sys.stdout.write('\033[0m0\n')
|
Loading…
x
Reference in New Issue
Block a user