Release 2.4

- Added `short` parameter for `system_load` segment that leaves only one load
  average number out of three.
- Added `powerline.segments.i3wm.scratchpad` segment used to list windows that
  are currently on the scratchpad.
- Added support for multiple batteries in battery segment.
- Added `….i3wm.workspace` segment which describes single i3wm workspace and
  workspaces lister. Old `….i3wm.workspaces` segment was deprecated.
- Added support for multiple monitors in lemonbar bindings.
- Added support for most recent tmux version (2.2).
- Fixed battery status support on some linux systems.
- Fixed MPD bindings: they sometimes were not able to handle names if they did
  not fit ASCII.
- Fixed MPD bindings: they did not correctly get elapsed time.
- Fixed AttributeError on some systems: LC_MESSAGES is not always available.
- Fixed Mac OS-specific variant of spotify player support when Python-3 is
  used.
- Fixed performance of the tabline.
This commit is contained in:
Foo 2016-04-19 02:02:47 +03:00
commit d77cc30a3b
65 changed files with 2155 additions and 316 deletions

View File

@ -24,12 +24,9 @@ matrix:
- python: "3.2"
- python: "3.3"
- python: "3.4"
- python: "3.5"
- python: "pypy"
- python: "pypy3"
- python: "2.6"
env: >-
USE_UCS2_PYTHON=1
UCS2_PYTHON_VARIANT="2.6"
- python: "2.7"
env: >-
USE_UCS2_PYTHON=1

View File

@ -1,6 +1,7 @@
recursive-include powerline *.json *.vim
recursive-include powerline/bindings *.*
recursive-exclude powerline/bindings *.pyc *.pyo
recursive-include powerline/dist *.*
recursive-include client *.*
recursive-include docs/source *.rst *.py
include docs/Makefile

View File

@ -16,8 +16,6 @@ their type and ``segments`` key with a list of segments (a bit more details in
More information in :ref:`Writing listers <dev-listers>` section.
Currently only Vim listers are available.
Vim listers
-----------
@ -29,3 +27,9 @@ Pdb listers
.. automodule:: powerline.listers.pdb
:members:
i3wm listers
----------
.. automodule:: powerline.listers.i3wm
:members:

View File

@ -12,6 +12,8 @@ Vim overrides
Vim configuration can be overridden using the following options:
.. _local-configuration-overrides-vim-config:
``g:powerline_config_overrides``
Dictionary, recursively merged with contents of
:file:`powerline/config.json`.
@ -37,6 +39,17 @@ Vim configuration can be overridden using the following options:
was configured in :ref:`log_* options <config-common-log>`. Level is always
:ref:`log_level <config-common-log_level>`, same for format.
.. warning::
This variable is deprecated. Use :ref:`log_file option
<config-common-log>` in conjunction with
:py:class:`powerline.vim.VimVarHandler` class and :ref:`Vim config
overrides variable <local-configuration-overrides-vim-config>`. Using
this is also the only variant to make saving into the environment
variable the *only* place where log is saved or save into different
variable.
.. autoclass:: powerline.vim.VimVarHandler
.. _local-configuration-overrides-script:
Powerline script overrides

View File

@ -94,14 +94,38 @@ Common configuration is a subdictionary that is a value of ``common`` key in
.. _config-common-log:
``log_file``
Defines path which will hold powerline logs. If not present, logging will be
done to stderr.
Defines how logs will be handled. There are three variants here:
#. Absent. In this case logging will be done to stderr: equivalent to
``[["logging.StreamHandler", []]]`` or ``[null]``.
#. Plain string. In this case logging will be done to the given file:
``"/file/name"`` is equivalent to ``[["logging.FileHandler",
[["/file/name"]]]]`` or ``["/file/name"]``. Leading ``~/`` is expanded in
the file name, so using ``"~/.log/foo"`` is permitted. If directory
pointed by the option is absent, it will be created, but not its parent.
#. List of handler definitions. Handler definition may either be ``null``,
a string or a list with two or three elements:
#. Logging class name and module. If module name is absent, it is
equivalent to ``logging.handlers``.
#. Class constructor arguments in a form ``[[args[, kwargs]]]``: accepted
variants are ``[]`` (no arguments), ``[args]`` (e.g.
``[["/file/name"]]``: only positional arguments) or ``[args, kwargs]``
(e.g. ``[[], {"host": "localhost", "port": 6666}]``: positional and
keyword arguments, but no positional arguments in the example).
#. Optional logging level. Overrides :ref:`log_level key
<config-common-log_level>` and has the same format.
#. Optional format string. Partially overrides :ref:`log_format key
<config-common-log_format>` and has the same format. “Partially” here
means that it may only specify more critical level.
.. _config-common-log_level:
``log_level``
String, determines logging level. Defaults to ``WARNING``.
.. _config-common-log_format:
``log_format``
String, determines format of the log messages. Defaults to
``'%(asctime)s:%(level)s:%(message)s'``.
@ -163,6 +187,10 @@ Common configuration is a subdictionary that is a value of ``ext`` key in
``out`` and ``rewrite`` prompts (refer to IPython documentation for more
details) while ``in`` prompt is the default.
For wm (:ref:`lemonbar <lemonbar-usage>` only) it is a dictionary
``{output : theme_name}`` that maps the ``xrandr`` output names to the
local themes to use on that output.
``components``
Determines which extension components should be enabled. This key is highly
extension-specific, here is the table of extensions and corresponding

View File

@ -305,7 +305,7 @@ Segment dictionary contains the following keys:
``side``
Segment side: ``right`` or ``left``.
``display_condition```
``display_condition``
Contains function that takes three position parameters:
:py:class:`powerline.PowerlineLogger` instance, :ref:`segment_info
<dev-segments-info>` dictionary and current mode and returns either ``True``
@ -517,6 +517,22 @@ Pdb
Equal to the length of :py:attr:`pdb.Pdb.stack` at the first invocation of
the prompt decremented by one.
i3wm
----
``mode``
Currently active i3 mode (as a string).
``output``
``xrandr`` output name currently drawing to. Currently only available
in lemonbar bindings.
``workspace``
dictionary containing the workspace name under the key ``"name"`` and
boolean values for the ``"visible"``, ``"urgent"`` and ``"focused"``
keys, indicating the state of the workspace. Currently only provided by
the :py:func:`powerline.listers.i3wm.workspace_lister` lister.
Segment class
=============

View File

@ -30,8 +30,9 @@ Generic requirements
with bazaar repositories.
* ``pyuv`` python package. Required for :ref:`libuv-based watcher
<config-common-watcher>` to work.
* ``i3-py``, `available on github <https://github.com/ziberna/i3-py>`_. Required
for i3wm bindings and segments.
* ``i3-ipc`` python package. Required for i3wm bindings and segments.
* ``xrandr`` program. Required for the multi-monitor lemonbar binding and the
:py:func:`powerline.listers.i3wm.output_lister`.
.. note::
Until mercurial and bazaar support Python-3 or PyPy powerline will not

View File

@ -11,12 +11,10 @@ Python package
sudo port select python python27-apple
. Homebrew may be used here::
Homebrew may be used here::
brew install python
.
.. note::
In case :file:`powerline.sh` as a client ``socat`` and ``coreutils`` need
to be installed. ``coreutils`` may be installed using ``brew install
@ -56,13 +54,13 @@ Vim installation
Any terminal vim version with Python 3.2+ or Python 2.6+ support should work,
but MacVim users need to install it using the following command::
brew install macvim --env-std --override-system-vim
brew install macvim --env-std --with-override-system-vim
Fonts installation
==================
Install downloaded patched font by double-clicking the font file in Finder, then
clicking :guilabel:`Install this font` in the preview window.
To install patched font double-click the font file in Finder, then click
:guilabel:`Install this font` in the preview window.
After installing the patched font MacVim or terminal emulator (whatever
application powerline should work with) need to be configured to use the patched

View File

@ -6,6 +6,7 @@ import re
import codecs
from collections import namedtuple
from argparse import REMAINDER
from functools import reduce
@ -61,9 +62,9 @@ def parse_argument(*args, **kwargs):
is_option = args[0].startswith('-')
is_long_option = args[0].startswith('--')
is_short_option = is_option and not is_long_option
action = kwargs.get('action', 'store_true')
multi = kwargs.get('action') in ('append',)
nargs = kwargs.get('nargs') or (1 if kwargs.get('metavar') or action in ('append',) else 0)
action = kwargs.get('action', 'store')
multi = kwargs.get('action') in ('append',) or kwargs.get('nargs') is REMAINDER
nargs = kwargs.get('nargs', (1 if action in ('append', 'store') else 0))
return Argument(
names=args,
help=u(kwargs.get('help', '')),
@ -165,9 +166,20 @@ def format_usage_arguments(arguments, base_length=None):
# `--` is automatically transformed into &#8211; (EN DASH)
# when parsing into HTML. We do not need this.
line[-1] += [nodes.Text(char) for char in name]
elif argument.nargs is REMAINDER:
line.append(nodes.Text('['))
line.append(nodes.strong())
line[-1] += [nodes.Text(char) for char in '--']
line.append(nodes.Text('] '))
if argument.nargs:
assert(argument.nargs in (1, '?'))
with SurroundWith(line, argument.nargs == '?' and argument.is_option):
assert(argument.nargs in (1, '?', REMAINDER))
with SurroundWith(
line, (
True
if argument.nargs is REMAINDER
else (argument.nargs == '?' and argument.is_option)
)
):
if argument.is_long_option:
line.append(nodes.Text('='))
line.append(nodes.emphasis(text=argument.metavar))
@ -337,15 +349,21 @@ class AutoManParser(object):
class AutoMan(Directive):
required_arguments = 1
optional_arguments = 0
option_spec = dict(prog=unchanged_required)
option_spec = dict(prog=unchanged_required, minimal=bool)
has_content = False
def run(self):
minimal = self.options.get('minimal')
module = self.arguments[0]
template_args = {}
template_args.update(get_authors())
get_argparser = __import__(str(module), fromlist=[str('get_argparser')]).get_argparser
parser = get_argparser(AutoManParser)
if minimal:
container = nodes.container()
container += parser.automan_usage(self.options['prog'])
container += parser.automan_description()
return [container]
synopsis_section = nodes.section(
'',
nodes.title(text='Synopsis'),

View File

@ -7,7 +7,8 @@ I cant see any fancy symbols, whats wrong?
* If youre using iTerm2, please update to `this revision
<https://github.com/gnachman/iTerm2/commit/8e3ad6dabf83c60b8cf4a3e3327c596401744af6>`_
or newer.
or newer. Also make sure that Preferences>Profiles>Text>Non-ASCII Font is the same as
your main Font.
* You need to set your ``LANG`` and ``LC_*`` environment variables to
a UTF-8 locale (e.g. ``LANG=en_US.utf8``). Consult your Linux distros
documentation for information about setting these variables correctly.

View File

@ -89,8 +89,8 @@ root <repository-root>`)::
.. note::
The availability of the ``powerline-config`` command is required for
powerline support. DLlocation of this script may be specified via
``$POWERLINE_CONFIG_COMMAND`` environment variable.
powerline support. The location of this script may be specified via
the ``$POWERLINE_CONFIG_COMMAND`` environment variable.
.. note::
It is advised to run ``powerline-daemon`` before adding the above line to
@ -114,11 +114,12 @@ For IPython<0.11 add the following lines to :file:`.ipython/ipy_user_conf.py`:
# create skeleton ipy_user_conf.py file):
powerline_setup()
For IPython>=0.11 add the following line to :file:`ipython_config.py` file in
the used profile:
For IPython>=0.11 add the following line to
:file:`~/.ipython/profile_default/ipython_config.py` file in the used profile:
.. code-block:: Python
c = get_config()
c.InteractiveShellApp.extensions = [
'powerline.bindings.ipython.post_0_11'
]

View File

@ -50,26 +50,35 @@ Add the following to :file:`~/.config/qtile/config.py`:
),
]
.. _bar-usage:
.. _lemonbar-usage:
bar-aint-recursive
==================
lemonbar (formerly bar-aint-recursive)
======================================
To run the bar simply pipe the output of the binding script into ``bar`` and
specify appropriate options, for example like this::
To run the bar simply start the binding script:
python /path/to/powerline/bindings/bar/powerline-bar.py | bar
python /path/to/powerline/bindings/lemonbar/powerline-lemonbar.py
to run with i3, simply ``exec`` this in i3 config file::
You can specify options to be passed to ``lemonbar`` after ``--``, like so:
exec python /path/to/powerline/bindings/bar/powerline-bar.py --i3 | bar
python /path/to/powerline/bindings/lemonbar/powerline-lemonbar.py --height 16 -- -f "Source Code Pro for Powerline-9"
to run with i3, simply ``exec`` this in the i3 config file and set the ``--i3`` switch:
exec python /path/to/powerline/bindings/lemonbar/powerline-lemonbar.py --i3
Running the binding in i3-mode will require `i3ipc <https://github.com/acrisci/i3ipc-python>`_
(or the outdated `i3-py <https://github.com/ziberna/i3-py>`_).
See the `bar documentation <https://github.com/LemonBoy/bar>`_ for more
See the `lemonbar documentation <https://github.com/LemonBoy/bar>`_ for more
information and options.
All ``powerline-lemonbar.py`` arguments:
.. automan:: powerline.commands.lemonbar
:prog: powerline-lemonbar.py
:minimal: true
I3 bar
======

View File

@ -9,7 +9,7 @@ from threading import Lock, Event
from powerline.colorscheme import Colorscheme
from powerline.lib.config import ConfigLoader
from powerline.lib.unicode import safe_unicode, FailedUnicode
from powerline.lib.unicode import unicode, safe_unicode, FailedUnicode
from powerline.config import DEFAULT_SYSTEM_CONFIG_DIR
from powerline.lib.dict import mergedicts
from powerline.lib.encoding import get_preferred_output_encoding
@ -121,7 +121,7 @@ def get_fallback_logger(stream=None):
handler.setLevel(level)
handler.setFormatter(formatter)
logger = logging.getLogger('powerline')
logger = logging.Logger('powerline')
logger.setLevel(level)
logger.addHandler(handler)
_fallback_logger = PowerlineLogger(None, logger, '_fallback_')
@ -200,40 +200,102 @@ def load_config(cfg_path, find_config_files, config_loader, loader_callback=None
return ret
def _get_log_handler(common_config, stream=None):
'''Get log handler.
def _set_log_handlers(common_config, logger, get_module_attr, stream=None):
'''Set log handlers
:param dict common_config:
Configuration dictionary used to create handler.
:return: logging.Handler subclass.
:param logging.Logger logger:
Logger to which handlers will be attached.
:param func get_module_attr:
:py:func:`gen_module_attr_getter` output.
:param file stream:
Stream to use by default for :py:class:`logging.StreamHandler` in place
of :py:attr:`sys.stderr`. May be ``None``.
'''
log_file = common_config['log_file']
if log_file:
log_file = os.path.expanduser(log_file)
log_dir = os.path.dirname(log_file)
if not os.path.isdir(log_dir):
log_targets = common_config['log_file']
num_handlers = 0
for log_target in log_targets:
if log_target is None:
log_target = ['logging.StreamHandler', []]
elif isinstance(log_target, unicode):
log_target = os.path.expanduser(log_target)
log_dir = os.path.dirname(log_target)
if log_dir and not os.path.isdir(log_dir):
os.mkdir(log_dir)
return logging.FileHandler(log_file)
log_target = ['logging.FileHandler', [[log_target]]]
module, handler_class_name = log_target[0].rpartition('.')[::2]
module = module or 'logging.handlers'
try:
handler_class_args = log_target[1][0]
except IndexError:
if module == 'logging' and handler_class_name == 'StreamHandler':
handler_class_args = [stream]
else:
return logging.StreamHandler(stream)
def create_logger(common_config, stream=None):
'''Create logger according to provided configuration
'''
log_format = common_config['log_format']
formatter = logging.Formatter(log_format)
level = getattr(logging, common_config['log_level'])
handler = _get_log_handler(common_config, stream)
handler.setLevel(level)
handler.setFormatter(formatter)
logger = logging.getLogger('powerline')
logger.setLevel(level)
handler_class_args = ()
try:
handler_class_kwargs = log_target[1][1]
except IndexError:
handler_class_kwargs = {}
module = str(module)
handler_class_name = str(handler_class_name)
handler_class = get_module_attr(module, handler_class_name)
if not handler_class:
continue
handler = handler_class(*handler_class_args, **handler_class_kwargs)
try:
handler_level_name = log_target[2]
except IndexError:
handler_level_name = common_config['log_level']
try:
handler_format = log_target[3]
except IndexError:
handler_format = common_config['log_format']
handler.setLevel(getattr(logging, handler_level_name))
handler.setFormatter(logging.Formatter(handler_format))
logger.addHandler(handler)
return logger
num_handlers += 1
if num_handlers == 0 and log_targets:
raise ValueError('Failed to set up any handlers')
def create_logger(common_config, use_daemon_threads=True, ext='__unknown__',
import_paths=None, imported_modules=None, stream=None):
'''Create logger according to provided configuration
:param dict common_config:
Common configuration, from :py:func:`finish_common_config`.
:param bool use_daemon_threads:
Whether daemon threads should be used. Argument to
:py:class:`PowerlineLogger` constructor.
:param str ext:
Used extension. Argument to :py:class:`PowerlineLogger` constructor.
:param set imported_modules:
Set where imported modules are saved. Argument to
:py:func:`gen_module_attr_getter`. May be ``None``, in this case new
empty set is used.
:param file stream:
Stream to use by default for :py:class:`logging.StreamHandler` in place
of :py:attr:`sys.stderr`. May be ``None``.
:return: Three objects:
#. :py:class:`logging.Logger` instance.
#. :py:class:`PowerlineLogger` instance.
#. Function, output of :py:func:`gen_module_attr_getter`.
'''
logger = logging.Logger('powerline')
level = getattr(logging, common_config['log_level'])
logger.setLevel(level)
pl = PowerlineLogger(use_daemon_threads, logger, ext)
get_module_attr = gen_module_attr_getter(
pl, common_config['paths'],
set() if imported_modules is None else imported_modules)
_set_log_handlers(common_config, logger, get_module_attr, stream)
return logger, pl, get_module_attr
def finish_common_config(encoding, common_config):
@ -264,7 +326,10 @@ def finish_common_config(encoding, common_config):
common_config.setdefault('additional_escapes', None)
common_config.setdefault('reload_config', True)
common_config.setdefault('interval', None)
common_config.setdefault('log_file', None)
common_config.setdefault('log_file', [None])
if not isinstance(common_config['log_file'], list):
common_config['log_file'] = [common_config['log_file']]
common_config['paths'] = [
os.path.expanduser(path) for path in common_config['paths']
@ -324,6 +389,26 @@ def gen_module_attr_getter(pl, import_paths, imported_modules):
return get_module_attr
LOG_KEYS = set(('log_format', 'log_level', 'log_file', 'paths'))
'''List of keys related to logging
'''
def _get_log_keys(common_config):
'''Return a common configuration copy with only log-related config left
:param dict common_config:
Common configuration.
:return:
:py:class:`dict` instance which has only keys from
:py:attr:`powerline.LOG_KEYS` left.
'''
return dict((
(k, v) for k, v in common_config.items() if k in LOG_KEYS
))
class Powerline(object):
'''Main powerline class, entrance point for all powerline uses. Sets
powerline up and loads the configuration.
@ -380,6 +465,7 @@ class Powerline(object):
self.ext = ext
self.run_once = run_once
self.logger = logger
self.had_logger = bool(self.logger)
self.use_daemon_threads = use_daemon_threads
if not renderer_module:
@ -430,8 +516,20 @@ class Powerline(object):
This function is used to create logger unless it was already specified
at initialization.
:return: Three objects:
#. :py:class:`logging.Logger` instance.
#. :py:class:`PowerlineLogger` instance.
#. Function, output of :py:func:`gen_module_attr_getter`.
'''
return create_logger(self.common_config, self.default_log_stream)
return create_logger(
common_config=self.common_config,
use_daemon_threads=self.use_daemon_threads,
ext=self.ext,
imported_modules=self.imported_modules,
stream=self.default_log_stream,
)
def create_renderer(self, load_main=False, load_colors=False, load_colorscheme=False, load_theme=False):
'''(Re)create renderer object. Can be used after Powerline object was
@ -465,22 +563,24 @@ class Powerline(object):
or not self.prev_common_config
or self.prev_common_config['default_top_theme'] != self.common_config['default_top_theme'])
log_keys_differ = (not self.prev_common_config or (
_get_log_keys(self.prev_common_config) != _get_log_keys(self.common_config)
))
self.prev_common_config = self.common_config
self.import_paths = self.common_config['paths']
if not self.logger:
self.logger = self.create_logger()
if not self.pl:
if log_keys_differ:
if self.had_logger:
self.pl = PowerlineLogger(self.use_daemon_threads, self.logger, self.ext)
self.get_module_attr = gen_module_attr_getter(
self.pl, self.common_config['paths'], self.imported_modules)
else:
self.logger, self.pl, self.get_module_attr = self.create_logger()
self.config_loader.pl = self.pl
if not self.run_once:
self.config_loader.set_watcher(self.common_config['watcher'])
self.get_module_attr = gen_module_attr_getter(self.pl, self.import_paths, self.imported_modules)
mergedicts(self.renderer_options, dict(
pl=self.pl,
term_truecolor=self.common_config['term_truecolor'],

View File

@ -2,31 +2,27 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
import os
import sys
import time
from threading import Lock, Timer
from argparse import ArgumentParser
from powerline import Powerline
from powerline.lemonbar import LemonbarPowerline
from powerline.lib.encoding import get_unicode_writer
class BarPowerline(Powerline):
get_encoding = staticmethod(lambda: 'utf-8')
def init(self):
super(BarPowerline, self).init(ext='wm', renderer_module='bar')
if __name__ == '__main__':
parser = ArgumentParser(description='Powerline BAR bindings.')
parser = ArgumentParser(description='Powerline lemonbar bindings.')
parser.add_argument(
'--i3', action='store_true',
help='Subscribe for i3 events.'
)
args = parser.parse_args()
powerline = BarPowerline()
powerline = LemonbarPowerline()
powerline.update_renderer()
powerline.pl.warn("The 'bar' bindings are deprecated, please switch to 'lemonbar'")
lock = Lock()
modes = ['default']
write = get_unicode_writer(encoding='utf-8')
@ -60,4 +56,4 @@ if __name__ == '__main__':
conn.main()
while True:
time.sleep(1e10)
time.sleep(1e8)

View File

@ -9,7 +9,7 @@ import shlex
from powerline.config import POWERLINE_ROOT, TMUX_CONFIG_DIRECTORY
from powerline.lib.config import ConfigLoader
from powerline import generate_config_finder, load_config, create_logger, PowerlineLogger, finish_common_config
from powerline import generate_config_finder, load_config, create_logger, finish_common_config
from powerline.shell import ShellPowerline
from powerline.lib.shell import which
from powerline.bindings.tmux import (TmuxVersionInfo, run_tmux_command, set_tmux_environment, get_tmux_version,
@ -221,8 +221,8 @@ def get_main_config(args):
def create_powerline_logger(args):
config = get_main_config(args)
common_config = finish_common_config(get_preferred_output_encoding(), config['common'])
logger = create_logger(common_config)
return PowerlineLogger(use_daemon_threads=True, logger=logger, ext='config')
logger, pl, get_module_attr = create_logger(common_config)
return pl
def check_command(cmd):

View File

@ -0,0 +1,61 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
import time
import re
import subprocess
from threading import Lock, Timer
from powerline.lemonbar import LemonbarPowerline
from powerline.commands.lemonbar import get_argparser
from powerline.bindings.wm import get_connected_xrandr_outputs
if __name__ == '__main__':
parser = get_argparser()
args = parser.parse_args()
powerline = LemonbarPowerline()
powerline.update_renderer()
bars = []
for screen in get_connected_xrandr_outputs(powerline.pl):
command = [args.bar_command, '-g', '{0}x{1}+{2}'.format(screen['width'], args.height, screen['x'])] + args.args[1:]
process = subprocess.Popen(command, stdin=subprocess.PIPE)
bars.append((screen['name'], process, int(screen['width']) / 5))
lock = Lock()
modes = ['default']
def render(reschedule=False):
if reschedule:
Timer(args.interval, render, kwargs={'reschedule': True}).start()
global lock
with lock:
for output, process, width in bars:
process.stdin.write(powerline.render(mode=modes[0], width=width, matcher_info=output).encode('utf-8') + b'\n')
process.stdin.flush()
def update(evt):
modes[0] = evt.change
render()
render(reschedule=True)
if args.i3:
try:
import i3ipc
except ImportError:
import i3
i3.Subscription(lambda evt, data, sub: render(), 'workspace')
else:
conn = i3ipc.Connection()
conn.on('workspace::focus', lambda conn, evt: render())
conn.on('mode', lambda conn, evt: update(evt))
conn.main()
while True:
time.sleep(1e8)

View File

@ -1,5 +1,4 @@
set -g status on
set -g status-utf8 on
set -g status-interval 2
set -g status-left-length 20
set -g status-right '#(env "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS tmux right -R pane_id=\"`tmux display -p "#D"`\")'

View File

@ -273,7 +273,7 @@ def _vim_to_python(value):
if hasattr(vim, 'options'):
def vim_getbufoption(info, option):
return info['buffer'].options[str(option)]
return _vim_to_python(info['buffer'].options[str(option)])
def vim_getoption(option):
return vim.options[str(option)]

View File

@ -0,0 +1,38 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
import re
from powerline.theme import requires_segment_info
from powerline.lib.shell import run_cmd
conn = None
def get_i3_connection():
'''Return a valid, cached i3 Connection instance
'''
global conn
if not conn:
try:
import i3ipc
except ImportError:
import i3 as conn
else:
conn = i3ipc.Connection()
return conn
XRANDR_OUTPUT_RE = re.compile(r'^(?P<name>[0-9A-Za-z-]+) connected(?P<primary> primary)? (?P<width>\d+)x(?P<height>\d+)\+(?P<x>\d+)\+(?P<y>\d+)', re.MULTILINE)
def get_connected_xrandr_outputs(pl):
'''Iterate over xrandr outputs
Outputs are represented by a dictionary with ``name``, ``width``,
``height``, ``primary``, ``x`` and ``y`` keys.
'''
return (match.groupdict() for match in XRANDR_OUTPUT_RE.finditer(
run_cmd(pl, ['xrandr', '-q'])
))

View File

@ -0,0 +1,35 @@
# vim:fileencoding=utf-8:noet
# WARNING: using unicode_literals causes errors in argparse
from __future__ import (division, absolute_import, print_function)
import argparse
def get_argparser(ArgumentParser=argparse.ArgumentParser):
parser = ArgumentParser(
description='Powerline BAR bindings.'
)
parser.add_argument(
'--i3', action='store_true',
help='Subscribe for i3 events.'
)
parser.add_argument(
'--height', default='',
metavar='PIXELS', help='Bar height.'
)
parser.add_argument(
'--interval', '-i',
type=float, default=0.5,
metavar='SECONDS', help='Refresh interval.'
)
parser.add_argument(
'--bar-command', '-C',
default='lemonbar',
metavar='CMD', help='Name of the lemonbar executable to use.'
)
parser.add_argument(
'args', nargs=argparse.REMAINDER,
help='Extra arguments for lemonbar. Should be preceded with ``--`` '
'argument in order not to be confused with script own arguments.'
)
return parser

View File

@ -23,16 +23,25 @@
"csv:column_number": "line_current",
"csv:column_name": "line_current_symbol",
"tab:background": "background",
"tab:divider": "background:divider",
"tab_nc:modified_indicator": "modified_indicator",
"tab_nc:file_directory": "information:unimportant",
"tab_nc:file_name": "tab_nc:file_directory",
"tab_nc:tabnr": "tab_nc:file_directory",
"buf_nc:file_directory": "tab_nc:file_directory",
"buf_nc:file_name": "tab_nc:file_name",
"buf_nc:bufnr": "tab_nc:tabnr",
"buf_nc:file_name": "buf_nc:file_directory",
"buf_nc:bufnr": "buf_nc:file_directory",
"buf_nc:modified_indicator": "tab_nc:modified_indicator",
"buf_nc_mod:file_directory": "tab_nc:file_directory",
"buf_nc_mod:file_name": "buf_nc_mod:file_directory",
"buf_nc_mod:bufnr": "buf_nc_mod:file_directory",
"buf_nc_mod:modified_indicator": "tab_nc:modified_indicator",
"commandt:label": "file_name",
"commandt:background": "background",
"commandt:finder": "file_name",

View File

@ -131,6 +131,15 @@
"args": {
"text": "+"
}
},
"powerline.segments.i3wm.scratchpad": {
"args": {
"icons": {
"fresh": "O",
"changed": "X"
}
}
}
}
}

View File

@ -129,6 +129,15 @@
"args": {
"text": "+"
}
},
"powerline.segments.i3wm.scratchpad": {
"args": {
"icons": {
"fresh": "●",
"changed": "○"
}
}
}
}
}

View File

@ -143,6 +143,15 @@
"args": {
"text": "🖫⃥"
}
},
"powerline.segments.i3wm.scratchpad": {
"args": {
"icons": {
"fresh": "●",
"changed": "○"
}
}
}
}
}

View File

@ -129,6 +129,15 @@
"args": {
"text": "+"
}
},
"powerline.segments.i3wm.scratchpad": {
"args": {
"icons": {
"fresh": "●",
"changed": "○"
}
}
}
}
}

View File

@ -129,6 +129,15 @@
"args": {
"text": "+"
}
},
"powerline.segments.i3wm.scratchpad": {
"args": {
"icons": {
"fresh": "●",
"changed": "○"
}
}
}
}
}

View File

@ -130,6 +130,15 @@
"args": {
"text": "+"
}
},
"powerline.segments.i3wm.scratchpad": {
"args": {
"icons": {
"fresh": "●",
"changed": "○"
}
}
}
}
}

View File

@ -67,7 +67,7 @@
},
{
"type": "string",
"highlight_groups": ["background"],
"highlight_groups": ["tab:background"],
"draw_soft_divider": false,
"draw_hard_divider": false,
"width": "auto"

21
powerline/lemonbar.py Normal file
View File

@ -0,0 +1,21 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline import Powerline
from powerline.lib.dict import mergedicts
class LemonbarPowerline(Powerline):
def init(self):
super(LemonbarPowerline, self).init(ext='wm', renderer_module='lemonbar')
get_encoding = staticmethod(lambda: 'utf-8')
def get_local_themes(self, local_themes):
if not local_themes:
return {}
return dict((
(key, {'config': self.load_theme_config(val)})
for key, val in local_themes.items()
))

View File

@ -78,3 +78,11 @@ def mergedicts_copy(d1, d2):
else:
ret[k] = d2[k]
return ret
def updated(d, *args, **kwargs):
'''Copy dictionary and update it with provided arguments
'''
d = d.copy()
d.update(*args, **kwargs)
return d

View File

@ -43,12 +43,18 @@ def get_preferred_output_encoding():
Falls back to ASCII, so that output is most likely to be displayed
correctly.
'''
if hasattr(locale, 'LC_MESSAGES'):
return (
locale.getlocale(locale.LC_MESSAGES)[1]
or locale.getdefaultlocale()[1]
or 'ascii'
)
return (
locale.getdefaultlocale()[1]
or 'ascii'
)
def get_preferred_input_encoding():
'''Get encoding that should be used for reading shell command output
@ -57,12 +63,18 @@ def get_preferred_input_encoding():
Falls back to latin1 so that function is less likely to throw as decoded
output is primary searched for ASCII values.
'''
if hasattr(locale, 'LC_MESSAGES'):
return (
locale.getlocale(locale.LC_MESSAGES)[1]
or locale.getdefaultlocale()[1]
or 'latin1'
)
return (
locale.getdefaultlocale()[1]
or 'latin1'
)
def get_preferred_arguments_encoding():
'''Get encoding that should be used for command-line arguments

View File

@ -7,7 +7,7 @@ import os
from subprocess import Popen, PIPE
from functools import partial
from powerline.lib.encoding import get_preferred_input_encoding
from powerline.lib.encoding import get_preferred_input_encoding, get_preferred_output_encoding
if sys.platform.startswith('win32'):
@ -36,7 +36,8 @@ def run_cmd(pl, cmd, stdin=None, strip=True):
pl.exception('Could not execute command ({0}): {1}', e, cmd)
return None
else:
stdout, err = p.communicate(stdin)
stdout, err = p.communicate(
stdin if stdin is None else stdin.encode(get_preferred_output_encoding()))
stdout = stdout.decode(get_preferred_input_encoding())
return stdout.strip() if strip else stdout

View File

@ -22,7 +22,7 @@ from powerline.lint.checks import (check_matcher_func, check_ext, check_config,
check_segment_function, check_args, get_one_segment_function,
check_highlight_groups, check_highlight_group, check_full_segment_data,
get_all_possible_functions, check_segment_data_key, register_common_name,
highlight_group_spec)
highlight_group_spec, check_log_file_level, check_logging_handler)
from powerline.lint.spec import Spec
from powerline.lint.context import Context
@ -56,6 +56,11 @@ ext_spec = Spec(
top_theme=top_theme_spec().optional(),
).copy
gen_components_spec = (lambda *components: Spec().list(Spec().type(unicode).oneof(set(components))))
log_level_spec = Spec().re('^[A-Z]+$').func(
(lambda value, *args: (True, True, not hasattr(logging, value))),
(lambda value: 'unknown debugging level {0}'.format(value))
).copy
log_format_spec = Spec().type(unicode).copy
main_spec = (Spec(
common=Spec(
default_top_theme=top_theme_spec().optional(),
@ -67,7 +72,8 @@ main_spec = (Spec(
(lambda value, *args: (True, True, not os.path.exists(os.path.expanduser(value.value)))),
(lambda value: 'path does not exist: {0}'.format(value))
).optional(),
log_file=Spec().type(unicode).func(
log_file=Spec().either(
Spec().type(unicode).func(
(
lambda value, *args: (
True,
@ -76,12 +82,22 @@ main_spec = (Spec(
)
),
(lambda value: 'directory does not exist: {0}'.format(os.path.dirname(value)))
),
Spec().list(Spec().either(
Spec().type(unicode, type(None)),
Spec().tuple(
Spec().re(function_name_re).func(check_logging_handler),
Spec().tuple(
Spec().type(list).optional(),
Spec().type(dict).optional(),
),
log_level_spec().func(check_log_file_level).optional(),
log_format_spec().optional(),
),
))
).optional(),
log_level=Spec().re('^[A-Z]+$').func(
(lambda value, *args: (True, True, not hasattr(logging, value))),
(lambda value: 'unknown debugging level {0}'.format(value))
).optional(),
log_format=Spec().type(unicode).optional(),
log_level=log_level_spec().optional(),
log_format=log_format_spec().optional(),
interval=Spec().either(Spec().cmp('gt', 0.0), Spec().type(type(None))).optional(),
reload_config=Spec().type(bool).optional(),
watcher=Spec().type(unicode).oneof(set(('auto', 'inotify', 'stat'))).optional(),
@ -110,6 +126,12 @@ main_spec = (Spec(
select=ext_theme_spec(),
),
).optional(),
wm=ext_spec().update(
local_themes=Spec().unknown_spec(
Spec().re('^[0-9A-Za-z-]+$'),
ext_theme_spec()
).optional()
).optional(),
).unknown_spec(
check_ext,
ext_spec(),

View File

@ -800,3 +800,67 @@ def check_exinclude_function(name, data, context, echoerr):
if not func:
return True, False, True
return True, False, False
def check_log_file_level(this_level, data, context, echoerr):
'''Check handler level specified in :ref:`log_file key <config-common-log>`
This level must be greater or equal to the level in :ref:`log_level key
<config-common-log_level>`.
'''
havemarks(this_level)
hadproblem = False
top_level = context[0][1].get('common', {}).get('log_level', 'WARNING')
top_level_str = top_level
top_level_mark = getattr(top_level, 'mark', None)
if (
not isinstance(top_level, unicode) or not hasattr(logging, top_level)
or not isinstance(this_level, unicode) or not hasattr(logging, this_level)
):
return True, False, hadproblem
top_level = getattr(logging, top_level)
this_level_str = this_level
this_level_mark = this_level.mark
this_level = getattr(logging, this_level)
if this_level < top_level:
echoerr(
context='Error while checking log level index (key {key})'.format(
key=context.key),
context_mark=this_level_mark,
problem='found level that is less critical then top level ({0} < {0})'.format(
this_level_str, top_level_str),
problem_mark=top_level_mark,
)
hadproblem = True
return True, False, hadproblem
def check_logging_handler(handler_name, data, context, echoerr):
havemarks(handler_name)
import_paths = [os.path.expanduser(path) for path in context[0][1].get('common', {}).get('paths', [])]
handler_module, separator, handler_class = handler_name.rpartition('.')
if not separator:
handler_module = 'logging.handlers'
handler_class = handler_name
with WithPath(import_paths):
try:
handler = getattr(__import__(str(handler_module), fromlist=[str(handler_class)]), str(handler_class))
except ImportError:
echoerr(context='Error while loading logger class (key {key})'.format(key=context.key),
problem='failed to load module {0}'.format(handler_module),
problem_mark=handler_name.mark)
return True, False, True
except AttributeError:
echoerr(context='Error while loading logger class (key {key})'.format(key=context.key),
problem='failed to load handler class {0}'.format(handler_class),
problem_mark=handler_name.mark)
return True, False, True
if not issubclass(handler, logging.Handler):
echoerr(context='Error while loading logger class (key {key})'.format(key=context.key),
problem='loaded class {0} is not a logging.Handler subclass'.format(handler_class),
problem_mark=handler_name.mark)
return True, False, True
return True, False, False

View File

@ -21,6 +21,12 @@ class WithPath(object):
def import_function(function_type, name, data, context, echoerr, module):
havemarks(name, module)
if module == 'powerline.segments.i3wm' and name == 'workspaces':
echoerr(context='Warning while checking segments (key {key})'.format(key=context.key),
context_mark=name.mark,
problem='segment {0} from {1} is deprecated'.format(name, module),
problem_mark=module.mark)
with WithPath(data['import_paths']):
try:
func = getattr(__import__(str(module), fromlist=[str(name)]), str(name))

View File

@ -531,6 +531,7 @@ class Spec(object):
if max_len == min_len:
self.len('eq', len(specs))
else:
if min_len > 0:
self.len('ge', min_len)
self.len('le', max_len)

68
powerline/listers/i3wm.py Normal file
View File

@ -0,0 +1,68 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline.theme import requires_segment_info
from powerline.lib.dict import updated
from powerline.bindings.wm import get_i3_connection, get_connected_xrandr_outputs
@requires_segment_info
def output_lister(pl, segment_info):
'''List all outputs in segment_info format
'''
return (
(
updated(segment_info, output=output['name']),
{
'draw_inner_divider': None
}
)
for output in get_connected_xrandr_outputs(pl)
)
@requires_segment_info
def workspace_lister(pl, segment_info, only_show=None, output=None):
'''List all workspaces in segment_info format
Sets the segment info values of ``workspace`` and ``output`` to the name of
the i3 workspace and the ``xrandr`` output respectively and the keys
``"visible"``, ``"urgent"`` and ``"focused"`` to a boolean indicating these
states.
:param list only_show:
Specifies which workspaces to list. Valid entries are ``"visible"``,
``"urgent"`` and ``"focused"``. If omitted or ``null`` all workspaces
are listed.
:param str output:
May be set to the name of an X output. If specified, only workspaces
on that output are listed. Overrides automatic output detection by
the lemonbar renderer and bindings. Set to ``false`` to force
all workspaces to be shown.
'''
if output == None:
output = output or segment_info.get('output')
return (
(
updated(
segment_info,
output=w['output'],
workspace={
'name': w['name'],
'visible': w['visible'],
'urgent': w['urgent'],
'focused': w['focused'],
},
),
{
'draw_inner_divider': None
}
)
for w in get_i3_connection().get_workspaces()
if (((not only_show or any(w[typ] for typ in only_show))
and (not output or w['output'] == output)))
)

View File

@ -2,12 +2,12 @@
from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline.theme import requires_segment_info
from powerline.bindings.vim import (current_tabpage, list_tabpages, vim_getbufoption)
from powerline.bindings.vim import (current_tabpage, list_tabpages)
try:
import vim
except ImportError:
vim = {}
vim = object()
def tabpage_updated_segment_info(segment_info, tabpage):
@ -49,7 +49,10 @@ def tablister(pl, segment_info, **kwargs):
return (
(lambda tabpage, prefix: (
tabpage_updated_segment_info(segment_info, tabpage),
add_multiplier(tabpage, {'highlight_group_prefix': prefix})
add_multiplier(tabpage, {
'highlight_group_prefix': prefix,
'divider_highlight_group': 'tab:divider'
})
))(tabpage, 'tab' if tabpage == cur_tabpage else 'tab_nc')
for tabpage in list_tabpages()
)
@ -75,7 +78,8 @@ def bufferlister(pl, segment_info, show_unlisted=False, **kwargs):
and ``bufnr`` keys set to buffer-specific ones, ``window``, ``winnr`` and
``window_id`` keys set to None.
Adds either ``buf:`` or ``buf_nc:`` prefix to all segment highlight groups.
Adds one of ``buf:``, ``buf_nc:``, ``buf_mod:``, or ``buf_nc_mod``
prefix to all segment highlight groups.
:param bool show_unlisted:
True if unlisted buffers should be shown as well. Current buffer is
@ -89,22 +93,31 @@ def bufferlister(pl, segment_info, show_unlisted=False, **kwargs):
return dct
return (
(
buf_segment_info,
add_multiplier(buf_segment_info['buffer'], {'highlight_group_prefix': prefix})
(lambda buffer, current, modified: (
buffer_updated_segment_info(segment_info, buffer),
add_multiplier(buffer, {
'highlight_group_prefix': '{0}{1}'.format(current, modified),
'divider_highlight_group': 'tab:divider'
})
))(
buffer,
'buf' if buffer is cur_buffer else 'buf_nc',
'_mod' if int(vim.eval('getbufvar({0}, \'&modified\')'.format(buffer.number))) > 0 else ''
)
for buf_segment_info, prefix in (
(
buffer_updated_segment_info(
segment_info,
buffer
),
('buf' if buffer is cur_buffer else 'buf_nc')
)
for buffer in vim.buffers
) if (
buf_segment_info['buffer'] is cur_buffer
for buffer in vim.buffers if (
buffer is cur_buffer
or show_unlisted
or int(vim_getbufoption(buf_segment_info, 'buflisted'))
# We can't use vim_getbufoption(segment_info, 'buflisted')
# here for performance reasons. Querying the buffer options
# through the vim python module's option attribute caused
# vim to think it needed to update the tabline for every
# keystroke after any event that changed the buffer's
# options.
#
# Using the vim module's eval method to directly use the
# buflisted(nr) vim method instead does not cause vim to
# update the tabline after every keystroke, but rather after
# events that would change that status. Fixes #1281
or int(vim.eval('buflisted(%s)' % buffer.number)) > 0
)
)

View File

@ -7,7 +7,7 @@ from powerline.bindings.vim import vim_getbufoption, buffer_name
def help(matcher_info):
return str(vim_getbufoption(matcher_info, 'buftype')) == 'help'
return vim_getbufoption(matcher_info, 'buftype') == 'help'
def cmdwin(matcher_info):
@ -16,4 +16,4 @@ def cmdwin(matcher_info):
def quickfix(matcher_info):
return str(vim_getbufoption(matcher_info, 'buftype')) == 'quickfix'
return vim_getbufoption(matcher_info, 'buftype') == 'quickfix'

View File

@ -3,9 +3,12 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
import os
from powerline.bindings.vim import buffer_name
from powerline.bindings.vim import vim_getbufoption, buffer_name
def commandt(matcher_info):
name = buffer_name(matcher_info)
return name and os.path.basename(name) == b'GoToFile'
return (
vim_getbufoption(matcher_info, 'filetype') == 'command-t'
or (name and os.path.basename(name) == b'GoToFile')
)

View File

@ -1,45 +0,0 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline.renderer import Renderer
from powerline.colorscheme import ATTR_UNDERLINE
class BarRenderer(Renderer):
'''bar (bar ain't recursive) renderer
See documentation of `bar <https://github.com/LemonBoy/bar>`_ and :ref:`the usage instructions <bar-usage>`
'''
character_translations = Renderer.character_translations.copy()
character_translations[ord('%')] = '%%'
@staticmethod
def hlstyle(*args, **kwargs):
# We dont need to explicitly reset attributes, so skip those calls
return ''
def hl(self, contents, fg=None, bg=None, attrs=None):
text = ''
if fg is not None:
if fg is not False and fg[1] is not False:
text += '%{{F#ff{0:06x}}}'.format(fg[1])
if bg is not None:
if bg is not False and bg[1] is not False:
text += '%{{B#ff{0:06x}}}'.format(bg[1])
if attrs & ATTR_UNDERLINE:
text += '%{+u}'
return text + contents + '%{F-B--u}'
def render(self, *args, **kwargs):
return '%{{l}}{0}%{{r}}{1}'.format(
super(BarRenderer, self).render(side='left', *args, **kwargs),
super(BarRenderer, self).render(side='right', *args, **kwargs),
)
renderer = BarRenderer

View File

@ -0,0 +1,61 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
from powerline.renderer import Renderer
from powerline.theme import Theme
from powerline.colorscheme import ATTR_UNDERLINE
class LemonbarRenderer(Renderer):
'''lemonbar (formerly bar/bar ain't recursive) renderer
See documentation of `lemonbar <https://github.com/LemonBoy/bar>`_ and :ref:`the usage instructions <lemonbar-usage>`
'''
character_translations = Renderer.character_translations.copy()
character_translations[ord('%')] = '%%{}'
@staticmethod
def hlstyle(*args, **kwargs):
# We dont need to explicitly reset attributes, so skip those calls
return ''
def hl(self, contents, fg=None, bg=None, attrs=None):
text = ''
if fg is not None:
if fg is not False and fg[1] is not False:
text += '%{{F#ff{0:06x}}}'.format(fg[1])
if bg is not None:
if bg is not False and bg[1] is not False:
text += '%{{B#ff{0:06x}}}'.format(bg[1])
if attrs & ATTR_UNDERLINE:
text += '%{+u}'
return text + contents + '%{F-B--u}'
def render(self, *args, **kwargs):
return '%{{l}}{0}%{{r}}{1}'.format(
super(LemonbarRenderer, self).render(side='left', segment_info={'output': kwargs.get('matcher_info')}, *args, **kwargs),
super(LemonbarRenderer, self).render(side='right', segment_info={'output': kwargs.get('matcher_info')}, *args, **kwargs),
)
def get_theme(self, matcher_info):
if not matcher_info or matcher_info not in self.local_themes:
return self.theme
match = self.local_themes[matcher_info]
try:
return match['theme']
except KeyError:
match['theme'] = Theme(
theme_config=match['config'],
main_theme_config=self.theme_config,
**self.theme_kwargs
)
return match['theme']
renderer = LemonbarRenderer

View File

@ -30,6 +30,7 @@ def _fetch_battery_info(pl):
else:
devinterface = 'org.freedesktop.DBus.Properties'
devtype_name = interface + '.Device'
devices = []
for devpath in up.EnumerateDevices(dbus_interface=interface):
dev = bus.get_object(interface, devpath)
devget = lambda what: dev.Get(
@ -46,36 +47,62 @@ def _fetch_battery_info(pl):
if not bool(devget('PowerSupply')):
pl.debug('Not using DBUS+UPower with {0}: not a power supply', devpath)
continue
devices.append(devpath)
pl.debug('Using DBUS+UPower with {0}', devpath)
return lambda pl: (
float(
if devices:
def _flatten_battery(pl):
energy = 0.0
energy_full = 0.0
state = True
for devpath in devices:
dev = bus.get_object(interface, devpath)
energy_full += float(
dbus.Interface(dev, dbus_interface=devinterface).Get(
devtype_name,
'Percentage'
),
'EnergyFull'
),
)
energy += float(
dbus.Interface(dev, dbus_interface=devinterface).Get(
devtype_name,
'Energy'
),
)
state &= dbus.Interface(dev, dbus_interface=devinterface).Get(
devtype_name,
'State'
) == 1
)
) != 2
return (energy * 100.0 / energy_full), state
return _flatten_battery
pl.debug('Not using DBUS+UPower as no batteries were found')
if os.path.isdir('/sys/class/power_supply'):
linux_bat_fmt = '/sys/class/power_supply/{0}/capacity'
linux_ac_fmt = '/sys/class/power_supply/{0}/online'
for linux_bat in os.listdir('/sys/class/power_supply'):
cap_path = linux_bat_fmt.format(linux_bat)
online_path = linux_ac_fmt.format(linux_bat)
if linux_bat.startswith('BAT') and os.path.exists(cap_path):
pl.debug('Using /sys/class/power_supply with battery {0}', linux_bat)
linux_energy_full_fmt = '/sys/class/power_supply/{0}/energy_full'
linux_energy_fmt = '/sys/class/power_supply/{0}/energy_now'
linux_status_fmt = '/sys/class/power_supply/{0}/status'
devices = []
for linux_supplier in os.listdir('/sys/class/power_supply'):
energy_path = linux_energy_fmt.format(linux_supplier)
if not os.path.exists(energy_path):
continue
pl.debug('Using /sys/class/power_supply with battery {0}', linux_supplier)
devices.append(linux_supplier)
if devices:
def _get_battery_status(pl):
with open(cap_path, 'r') as f:
_capacity = int(float(f.readline().split()[0]))
with open(online_path, 'r') as f:
_ac_powered = f.readline() == 1
return _capacity, _ac_powered
energy = 0.0
energy_full = 0.0
state = True
for device in devices:
with open(linux_energy_full_fmt.format(device), 'r') as f:
energy_full += int(float(f.readline().split()[0]))
with open(linux_energy_fmt.format(device), 'r') as f:
energy += int(float(f.readline().split()[0]))
try:
with open(linux_status_fmt.format(device), 'r') as f:
state &= (f.readline().strip() != 'Discharging')
except IOError:
state = None
return (energy * 100.0 / energy_full), state
return _get_battery_status
pl.debug('Not using /sys/class/power_supply as no batteries were found')
else:

View File

@ -74,6 +74,7 @@ else:
_interface_starts = {
'eth': 10, # Regular ethernet adapters : eth1
'enp': 10, # Regular ethernet adapters, Gentoo : enp2s0
'en': 10, # OS X : en0
'ath': 9, # Atheros WiFi adapters : ath0
'wlan': 9, # Other WiFi adapters : wlan1
'wlp': 9, # Other WiFi adapters, Gentoo : wlp5s0

View File

@ -189,6 +189,10 @@ class MpdPlayerSegment(PlayerSegment):
'total': now_playing[3],
}
else:
try:
client = mpd.MPDClient(use_unicode=True)
except TypeError:
# python-mpd 1.x does not support use_unicode
client = mpd.MPDClient()
client.connect(host, port)
if password:
@ -204,7 +208,7 @@ class MpdPlayerSegment(PlayerSegment):
'album': now_playing.get('album'),
'artist': now_playing.get('artist'),
'title': now_playing.get('title'),
'elapsed': _convert_seconds(now_playing.get('elapsed', 0)),
'elapsed': _convert_seconds(status.get('elapsed', 0)),
'total': _convert_seconds(now_playing.get('time', 0)),
}

View File

@ -13,7 +13,8 @@ from powerline.segments import with_docstring
cpu_count = None
def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2, track_cpu_count=False):
def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2,
track_cpu_count=False, short=False):
'''Return system load average.
Highlights using ``system_load_good``, ``system_load_bad`` and
@ -35,6 +36,8 @@ def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2, track
:param bool track_cpu_count:
if True powerline will continuously poll the system to detect changes
in the number of CPUs.
:param bool short:
if True only the sys load over last 1 minute will be displayed.
Divider highlight group used: ``background:divider``.
@ -61,6 +64,10 @@ def system_load(pl, format='{avg:.1f}', threshold_good=1, threshold_bad=2, track
'divider_highlight_group': 'background:divider',
'gradient_level': gradient_level,
})
if short:
return ret
ret[0]['contents'] += ' '
ret[1]['contents'] += ' '
return ret

View File

@ -1,13 +1,16 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
import re
from powerline.theme import requires_segment_info
from powerline.bindings.wm import get_i3_connection
conn = None
WORKSPACE_REGEX = re.compile(r'^[0-9]+: ?')
def calcgrp(w):
def workspace_groups(w):
group = []
if w['focused']:
group.append('w_focused')
@ -19,7 +22,14 @@ def calcgrp(w):
return group
def workspaces(pl, only_show=None, output=None, strip=0):
def format_name(name, strip=False):
if strip:
return WORKSPACE_REGEX.sub('', name, count=1)
return name
@requires_segment_info
def workspaces(pl, segment_info, only_show=None, output=None, strip=0):
'''Return list of used workspaces
:param list only_show:
@ -28,7 +38,9 @@ def workspaces(pl, only_show=None, output=None, strip=0):
are shown.
:param str output:
If specified, only workspaces on this output are shown.
May be set to the name of an X output. If specified, only workspaces
on that output are shown. Overrides automatic output detection by
the lemonbar renderer and bindings.
:param int strip:
Specifies how many characters from the front of each workspace name
@ -36,22 +48,57 @@ def workspaces(pl, only_show=None, output=None, strip=0):
Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``.
'''
global conn
if not conn:
output = output or segment_info.get('output')
return [
{
'contents': w['name'][strip:],
'highlight_groups': workspace_groups(w)
}
for w in get_i3_connection().get_workspaces()
if ((not only_show or any(w[typ] for typ in only_show))
and (not output or w['output'] == output))
]
@requires_segment_info
def workspace(pl, segment_info, workspace=None, strip=False):
'''Return the specified workspace name
:param str workspace:
Specifies which workspace to show. If unspecified, may be set by the
``list_workspaces`` lister if used, otherwise falls back to
currently focused workspace.
:param bool strip:
Specifies whether workspace numbers (in the ``1: name`` format) should
be stripped from workspace names before being displayed. Defaults to false.
Highlight groups used: ``workspace`` or ``w_visible``, ``workspace`` or ``w_focused``, ``workspace`` or ``w_urgent``.
'''
if workspace:
try:
import i3ipc
except ImportError:
import i3 as conn
w = next((
w for w in get_i3_connection().get_workspaces()
if w['name'] == workspace
))
except StopIteration:
return None
elif segment_info.get('workspace'):
w = segment_info['workspace']
else:
conn = i3ipc.Connection()
try:
w = next((
w for w in get_i3_connection().get_workspaces()
if w['focused']
))
except StopIteration:
return None
return [{
'contents': w['name'][min(len(w['name']), strip):],
'highlight_groups': calcgrp(w)
} for w in conn.get_workspaces()
if (not only_show or any(w[typ] for typ in only_show))
and (not output or w['output'] == output)
]
'contents': format_name(w['name'], strip=strip),
'highlight_groups': workspace_groups(w)
}]
@requires_segment_info
@ -68,3 +115,41 @@ def mode(pl, segment_info, names={'default': None}):
if mode in names:
return names[mode]
return mode
def scratchpad_groups(w):
group = []
if w.urgent:
group.append('scratchpad:urgent')
if w.nodes[0].focused:
group.append('scratchpad:focused')
if w.workspace().name != '__i3_scratch':
group.append('scratchpad:visible')
group.append('scratchpad')
return group
SCRATCHPAD_ICONS = {
'fresh': 'O',
'changed': 'X',
}
def scratchpad(pl, icons=SCRATCHPAD_ICONS):
'''Returns the windows currently on the scratchpad
:param dict icons:
Specifies the strings to show for the different scratchpad window states. Must
contain the keys ``fresh`` and ``changed``.
Highlight groups used: ``scratchpad`` or ``scratchpad:visible``, ``scratchpad`` or ``scratchpad:focused``, ``scratchpad`` or ``scratchpad:urgent``.
'''
return [
{
'contents': icons.get(w.scratchpad_state, icons['changed']),
'highlight_groups': scratchpad_groups(w)
}
for w in get_i3_connection().get_tree().descendents()
if w.scratchpad_state != 'none'
]

View File

@ -11,7 +11,7 @@ from collections import defaultdict
try:
import vim
except ImportError:
vim = {}
vim = object()
from powerline.bindings.vim import (vim_get_func, getbufvar, vim_getbufoption,
buffer_name, vim_getwinvar,

View File

@ -7,10 +7,13 @@ import logging
from itertools import count
try:
import vim
except ImportError:
vim = object()
from powerline.bindings.vim import vim_get_func, vim_getvar, get_vim_encoding, python_to_vim
from powerline import Powerline, FailedUnicode
from powerline import Powerline, FailedUnicode, finish_common_config
from powerline.lib.dict import mergedicts
from powerline.lib.unicode import u
@ -32,19 +35,21 @@ def _override_from(config, override_varname, key=None):
class VimVarHandler(logging.Handler, object):
'''Vim-specific handler which emits messages to Vim global variables
Used variable: ``g:powerline_log_messages``.
:param str varname:
Variable where
'''
def __init__(self, *args, **kwargs):
super(VimVarHandler, self).__init__(*args, **kwargs)
vim.command('unlet! g:powerline_log_messages')
vim.command('let g:powerline_log_messages = []')
def __init__(self, varname):
super(VimVarHandler, self).__init__()
utf_varname = u(varname)
self.vim_varname = utf_varname.encode('ascii')
vim.command('unlet! g:' + utf_varname)
vim.command('let g:' + utf_varname + ' = []')
@staticmethod
def emit(record):
def emit(self, record):
message = u(record.message)
if record.exc_text:
message += '\n' + u(record.exc_text)
vim.eval(b'add(g:powerline_log_messages, ' + python_to_vim(message) + b')')
vim.eval(b'add(g:' + self.vim_varname + b', ' + python_to_vim(message) + b')')
class VimPowerline(Powerline):
@ -53,6 +58,12 @@ class VimPowerline(Powerline):
self.last_window_id = 1
self.pyeval = pyeval
self.construct_window_statusline = self.create_window_statusline_constructor()
if all((hasattr(vim.current.window, attr) for attr in ('options', 'vars', 'number'))):
self.win_idx = self.new_win_idx
else:
self.win_idx = self.old_win_idx
self._vim_getwinvar = vim_get_func('getwinvar', 'bytes')
self._vim_setwinvar = vim_get_func('setwinvar')
if sys.version_info < (3,):
def create_window_statusline_constructor(self):
@ -80,18 +91,6 @@ class VimPowerline(Powerline):
default_log_stream = sys.stdout
def create_logger(self):
logger = super(VimPowerline, self).create_logger()
try:
if int(vim_getvar('powerline_use_var_handler')):
formatter = logging.Formatter(self.common_config['log_format'])
handler = VimVarHandler(getattr(logging, self.common_config['log_level']))
handler.setFormatter(formatter)
logger.addHandler(handler)
except KeyError:
pass
return logger
def add_local_theme(self, key, config):
'''Add local themes at runtime (during vim session).
@ -137,7 +136,16 @@ class VimPowerline(Powerline):
get_encoding = staticmethod(get_vim_encoding)
def load_main_config(self):
return _override_from(super(VimPowerline, self).load_main_config(), 'powerline_config_overrides')
main_config = _override_from(super(VimPowerline, self).load_main_config(), 'powerline_config_overrides')
try:
use_var_handler = bool(int(vim_getvar('powerline_use_var_handler')))
except KeyError:
use_var_handler = False
if use_var_handler:
main_config.setdefault('common', {})
main_config['common'] = finish_common_config(self.get_encoding(), main_config['common'])
main_config['common']['log_file'].append(['powerline.vim.VimVarHandler', [['powerline_log_messages']]])
return main_config
def load_theme_config(self, name):
return _override_from(
@ -252,8 +260,7 @@ class VimPowerline(Powerline):
# do anything.
pass
if all((hasattr(vim.current.window, attr) for attr in ('options', 'vars', 'number'))):
def win_idx(self, window_id):
def new_win_idx(self, window_id):
r = None
for window in vim.windows:
try:
@ -270,11 +277,8 @@ class VimPowerline(Powerline):
if curwindow_id == window_id if window_id else window is vim.current.window:
r = (window, curwindow_id, window.number)
return r
else:
_vim_getwinvar = staticmethod(vim_get_func('getwinvar', 'bytes'))
_vim_setwinvar = staticmethod(vim_get_func('setwinvar'))
def win_idx(self, window_id):
def old_win_idx(self, window_id):
r = None
for winnr, window in zip(count(1), vim.windows):
curwindow_id = self._vim_getwinvar(winnr, 'powerline_window_id')

View File

@ -59,7 +59,7 @@ else:
def get_version():
base_version = '2.3'
base_version = '2.4'
base_version += '.dev9999'
try:
return base_version + '+git.' + str(subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip())
@ -70,7 +70,7 @@ def get_version():
setup(
name='powerline-status',
version='2.3',
version='2.4',
description='The ultimate statusline/prompt utility.',
long_description=README,
classifiers=[

View File

@ -18,7 +18,9 @@ exit_suite() {
echo "${FAIL_SUMMARY}"
fi
export POWERLINE_CURRENT_SUITE="${POWERLINE_CURRENT_SUITE%/*}"
if test "x$1" != "x--continue" ; then
exit $FAILED
fi
}
fail() {

View File

@ -21,6 +21,11 @@ class Pl(object):
' self.{0}s.append((kwargs.get("prefix") or self.prefix, msg, args, kwargs))\n'
).format(meth))
def __nonzero__(self):
return bool(self.exceptions or self.errors or self.warns)
__bool__ = __nonzero__
class Args(object):
theme_override = {}

201
tests/run_bar_tests.sh Executable file
View File

@ -0,0 +1,201 @@
#!/bin/sh
. tests/common.sh
enter_suite bar
TEST_ROOT="$ROOT/tests/bar"
TEST_PATH="$TEST_ROOT/path"
TEST_STATIC_ROOT="$ROOT/tests/test_bar"
test -d "$TEST_ROOT" && rm -r "$TEST_ROOT"
mkdir "$TEST_ROOT"
cp -r "$TEST_STATIC_ROOT/path" "$TEST_ROOT"
cp -r "$TEST_STATIC_ROOT/powerline" "$TEST_ROOT"
export PYTHONPATH="$ROOT${PYTHONPATH:+:}$PYTHONPATH"
ln -s "$(which "${PYTHON}")" "$TEST_PATH"/python
ln -s "$(which sed)" "$TEST_PATH"
ln -s "$(which cat)" "$TEST_PATH"
ln -s "$(which mkdir)" "$TEST_PATH"
ln -s "$(which basename)" "$TEST_PATH"
ln -s "$TEST_PATH/lemonbar" "$TEST_PATH/bar-aint-recursive"
DEPRECATED_SCRIPT="$ROOT/powerline/bindings/bar/powerline-bar.py"
run() {
env -i \
LANG=C \
PATH="$TEST_PATH" \
XDG_CONFIG_HOME="$TEST_ROOT" \
XDG_CONFIG_DIRS="$TEST_ROOT/dummy" \
PYTHONPATH="$PYTHONPATH" \
TEST_ROOT="$TEST_ROOT" \
LD_LIBRARY_PATH="$LD_LIBRARY_PATH" \
"$@" || true
}
display_log() {
local log_file="$1"
echo "$log_file:"
echo '============================================================'
cat -v "$log_file"
echo
echo '____________________________________________________________'
}
check_log() {
local log_file="$1"
local text="$2"
local warns="$3"
if test "$warns" = "warns" ; then
local warning="$(head -n1 "$log_file" | sed 's/.*://')"
local expwarning="The 'bar' bindings are deprecated, please switch to 'lemonbar'"
if test "x$warning" != "x$expwarning" ; then
echo "Got: $warning"
echo "Exp: $expwarning"
fail "warn" F "Expected warning"
fi
sed -r -i -e '1d' "$log_file"
fi
local line="$(head -n1 "$log_file")"
local linenum="$(cat "$log_file" | wc -l)"
if test $linenum -lt 5 ; then
fail "log:lt" F "Script was run not enough times"
return 1
elif test $linenum -gt 15 ; then
fail "log:gt" E "Script was run too many times"
return 1
fi
local expline="%{l}%{F#ffd0d0d0}%{B#ff303030} $text-left %{F-B--u}%{F#ff303030} %{F-B--u}%{r}%{F#ff303030} %{F-B--u}%{F#ffd0d0d0}%{B#ff303030} $text-right %{F-B--u}"
if test "x$expline" != "x$line" ; then
echo "Line: '$line'"
echo "Expected: '$expline'"
fail "log:line" F "Unexpected line"
return 1
fi
local ret=0
while test $linenum -gt 0 ; do
echo "$line" >> "$TEST_ROOT/ok"
linenum=$(( linenum - 1 ))
done
if ! diff "$TEST_ROOT/ok" "$log_file" ; then
fail "log:diff" F "Unexpected output"
ret=1
fi
rm "$TEST_ROOT/ok"
return $ret
}
killscript() {
kill -KILL $1 || true
}
if ! test -e "$DEPRECATED_SCRIPT" ; then
# TODO: uncomment when skip is available
# skip "deprecated" "Missing deprecated bar bindings script"
:
else
enter_suite "deprecated"
run python "$DEPRECATED_SCRIPT" $args > "$TEST_ROOT/deprecated.log" 2>&1 &
SPID=$!
sleep 5
killscript $SPID
if ! check_log "$TEST_ROOT/deprecated.log" "default" warns ; then
display_log "$TEST_ROOT/deprecated.log"
fail "log" F "Checking log failed"
fi
rm "$TEST_ROOT/deprecated.log"
exit_suite --continue
fi
LEMONBAR_SCRIPT="$ROOT/powerline/bindings/lemonbar/powerline-lemonbar.py"
if ! test -e "$LEMONBAR_SCRIPT" ; then
# TODO: uncomment when skip is available
# skip "lemonbar" "Missing lemonbar bindings script"
:
else
enter_suite "lemonbar"
for args in "" "-i0.5" "--interval=0.5" "-- test args" "--bar-command bar-aint-recursive" "--height=10"; do
rm -rf "$TEST_ROOT/results"
run python "$LEMONBAR_SCRIPT" $args > "$TEST_ROOT/lemonbar.log" 2>&1 &
SPID=$!
sleep 5
killscript $SPID
sleep 0.5
enter_suite "args($args)"
fnum=0
for file in "$TEST_ROOT/results"/*.log ; do
if ! test -e "$file" ; then
fail "log" E "Log file is missing"
break
fi
fnum=$(( fnum + 1 ))
args_file="${file%.log}.args"
if ! test -e "$args_file" ; then
fail "args" E "$args_file is missing"
else
cat "$args_file" >> "$TEST_ROOT/args.log"
fi
text="dvi"
if cat "$args_file" | grep -q +1 ; then
text="default"
fi
if ! check_log "$file" "$text" ; then
display_log "$file"
fail "log" F "Checking log failed"
fi
rm "$file"
done
if test "$fnum" -ne 2 ; then
fail "fnum" F "Expected two output files"
fi
if test "x${args#--height}" != "x$args" ; then
height="${args#--height}"
height="${height# }"
height="${height#=}"
height="${height%% *}"
fi
command="lemonbar"
if test "x${args#--bar-command}" != "x$args" ; then
command="${args#--bar-command}"
command="${command# }"
command="${command#=}"
command="${command%% *}"
fi
received_args="$(cat "$TEST_ROOT/args.log" | sort)"
rm "$TEST_ROOT/args.log"
script_args="${args#*-- }"
script_args="${script_args# }"
if test "x${script_args}" '=' "x$args" ; then
script_args=
fi
expected_args="$command -g 1920x$height+0${script_args:+ }$script_args${NL}$command -g 1920x$height+1${script_args:+ }$script_args"
if test "x$expected_args" != "x$received_args" ; then
echo "args:${NL}<$received_args>"
echo "expected:${NL}<$expected_args>"
fail "args" F "Expected different args"
fi
if ! test -z "$(cat "$TEST_ROOT/lemonbar.log")" ; then
display_log "$TEST_ROOT/lemonbar.log"
fail "stderr" E "Unexpected script output"
fi
rm "$TEST_ROOT/lemonbar.log"
exit_suite --continue
done
exit_suite --continue
fi
if ! powerline-lint \
-p "$ROOT/powerline/config_files" \
-p "$TEST_STATIC_ROOT/powerline"
then
fail "lint" F "Checking test config failed"
fi
if test $FAILED -eq 0 ; then
rm -r "$TEST_ROOT"
fi
exit_suite

11
tests/test_bar/path/lemonbar Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
RES_DIR="$TEST_ROOT/results"
mkdir -p "$RES_DIR"
RES_FILE="$RES_DIR/$$"
while test -e "$RES_FILE.log" ; do
RES_FILE="$RES_FILE.${RANDOM:-`date +%N | sed s/^0*//`}"
done
echo $(basename $0) "$@" > "$RES_FILE.args"
cat > "$RES_FILE.log"

31
tests/test_bar/path/xrandr Executable file
View File

@ -0,0 +1,31 @@
#!/bin/sh
cat << EOF
Screen 0: minimum 8 x 8, current 1920 x 1200, maximum 16384 x 16384
DVI-I-0 disconnected (normal left inverted right x axis y axis)
VGA-0 connected 1920x1200+1+0 (normal left inverted right x axis y axis) 520mm x 330mm
1920x1200 59.95*+
1920x1080 60.00
1680x1050 59.95
1600x1200 60.00
1440x900 59.89
1280x1024 75.02 60.02
1280x800 59.81
1152x864 75.00
1024x768 75.03 60.00
800x600 75.00 60.32
640x480 75.00 59.94
DVI-I-1 connected 1920x1200+0+0 (normal left inverted right x axis y axis) 520mm x 330mm
1920x1200 59.95*+
1920x1080 60.00
1680x1050 59.95
1600x1200 60.00
1440x900 59.89
1280x1024 75.02 60.02
1280x800 59.81
1152x864 75.00
1024x768 75.03 60.00
800x600 75.00 60.32
640x480 75.00 59.94
HDMI-0 disconnected (normal left inverted right x axis y axis)
EOF

View File

@ -0,0 +1,9 @@
{
"ext": {
"wm": {
"local_themes": {
"DVI-I-1": "dvi"
}
}
}
}

View File

@ -0,0 +1,18 @@
{
"segments": {
"left": [
{
"type": "string",
"highlight_groups": ["time"],
"contents": "default-left"
}
],
"right": [
{
"type": "string",
"highlight_groups": ["time"],
"contents": "default-right"
}
]
}
}

View File

@ -0,0 +1,18 @@
{
"segments": {
"left": [
{
"type": "string",
"highlight_groups": ["time"],
"contents": "dvi-left"
}
],
"right": [
{
"type": "string",
"highlight_groups": ["time"],
"contents": "dvi-right"
}
]
}
}

View File

@ -825,28 +825,28 @@ class TestVim(TestCase):
sys.path.pop(0)
class TestBar(TestRender):
def test_bar(self):
class TestLemonbar(TestRender):
def test_lemonbar(self):
import powerline as powerline_module
with swap_attributes(config, powerline_module):
with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='bar') as powerline:
with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='lemonbar') as powerline:
self.assertRenderEqual(
powerline,
'%{l}%{F#ffc00000}%{B#ff008000}%{+u} A%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}B%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}'
)
@with_new_config
def test_bar_escape(self, config):
def test_lemonbar_escape(self, config):
import powerline as powerline_module
config['themes/wm/default']['segments']['left'] = (
highlighted_string('%{asd}', 'hl1'),
highlighted_string('10% %', 'hl2'),
)
with swap_attributes(config, powerline_module):
with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='bar') as powerline:
with get_powerline_raw(config, powerline_module.Powerline, replace_gcp=True, ext='wm', renderer_module='lemonbar') as powerline:
self.assertRenderEqual(
powerline,
'%{l}%{F#ffc00000}%{B#ff008000}%{+u} %%{asd}%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}10%% %%%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}'
'%{l}%{F#ffc00000}%{B#ff008000}%{+u} %%{}{asd}%{F-B--u}%{F#ff008000}%{B#ffc00000}>>%{F-B--u}%{F#ff008000}%{B#ffc00000}10%%{} %%{}%{F-B--u}%{F#ffc00000}>>%{F-B--u}%{r}%{F#ffc00000}<<%{F-B--u}%{F#ff804000}%{B#ffc00000}%{+u}C%{F-B--u}%{F#ff0000c0}%{B#ffc00000}<<%{F-B--u}%{F#ff008000}%{B#ff0000c0}D %{F-B--u}'
)

View File

@ -17,6 +17,7 @@ from powerline.lib.vcs import guess, get_fallback_create_watcher
from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment
from powerline.lib.monotonic import monotonic
from powerline.lib.vcs.git import git_directory
from powerline.lib.shell import run_cmd
import powerline.lib.unicode as plu
@ -48,6 +49,24 @@ def thread_number():
return len(threading.enumerate())
class TestShell(TestCase):
def test_run_cmd(self):
pl = Pl()
self.assertEqual(run_cmd(pl, ['xxx_nonexistent_command_xxx']), None)
self.assertEqual(len(pl.exceptions), 1)
pl = Pl()
self.assertEqual(run_cmd(pl, ['echo', ' test ']), 'test')
self.assertFalse(pl)
self.assertEqual(run_cmd(pl, ['echo', ' test '], strip=True), 'test')
self.assertFalse(pl)
self.assertEqual(run_cmd(pl, ['echo', ' test '], strip=False), ' test \n')
self.assertFalse(pl)
self.assertEqual(run_cmd(pl, ['cat'], stdin='test'), 'test')
self.assertFalse(pl)
self.assertEqual(run_cmd(pl, ['sh', '-c', 'cat >&2'], stdin='test'), '')
self.assertFalse(pl)
class TestThreaded(TestCase):
def test_threaded_segment(self):
log = []

227
tests/test_listers.py Normal file
View File

@ -0,0 +1,227 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
import powerline.listers.i3wm as i3wm
from tests.lib import Args, replace_attr, Pl
from tests import TestCase
class TestI3WM(TestCase):
@staticmethod
def get_workspaces():
return iter([
{'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False},
{'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True},
{'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True},
{'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True},
])
@staticmethod
def get_outputs(pl):
return iter([
{'name': 'LVDS1'},
{'name': 'HDMI1'},
{'name': 'DVI01'},
])
def test_output_lister(self):
pl = Pl()
with replace_attr(i3wm, 'get_connected_xrandr_outputs', self.get_outputs):
self.assertEqual(
list(i3wm.output_lister(pl=pl, segment_info={'a': 1})),
[
({'a': 1, 'output': 'LVDS1'}, {'draw_inner_divider': None}),
({'a': 1, 'output': 'HDMI1'}, {'draw_inner_divider': None}),
({'a': 1, 'output': 'DVI01'}, {'draw_inner_divider': None}),
]
)
def test_workspace_lister(self):
pl = Pl()
with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
self.assertEqual(
list(i3wm.workspace_lister(pl=pl, segment_info={'a': 1})),
[
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '1: w1',
'focused': False,
'urgent': False,
'visible': False
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '2: w2',
'focused': False,
'urgent': False,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'HDMI1',
'workspace': {
'name': '3: w3',
'focused': False,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'DVI01',
'workspace': {
'name': '4: w4',
'focused': True,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
self.assertEqual(
list(i3wm.workspace_lister(pl=pl, segment_info={'a': 1}, output='LVDS1')),
[
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '1: w1',
'focused': False,
'urgent': False,
'visible': False
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '2: w2',
'focused': False,
'urgent': False,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
self.assertEqual(
list(i3wm.workspace_lister(
pl=pl,
segment_info={'a': 1, 'output': 'LVDS1'}
)),
[
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '1: w1',
'focused': False,
'urgent': False,
'visible': False
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '2: w2',
'focused': False,
'urgent': False,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
self.assertEqual(
list(i3wm.workspace_lister(
pl=pl,
segment_info={'a': 1, 'output': 'LVDS1'},
output=False
)),
[
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '1: w1',
'focused': False,
'urgent': False,
'visible': False
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'LVDS1',
'workspace': {
'name': '2: w2',
'focused': False,
'urgent': False,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'HDMI1',
'workspace': {
'name': '3: w3',
'focused': False,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'DVI01',
'workspace': {
'name': '4: w4',
'focused': True,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
self.assertEqual(
list(i3wm.workspace_lister(
pl=pl,
segment_info={'a': 1},
only_show=['focused', 'urgent']
)),
[
({
'a': 1,
'output': 'HDMI1',
'workspace': {
'name': '3: w3',
'focused': False,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
({
'a': 1,
'output': 'DVI01',
'workspace': {
'name': '4: w4',
'focused': True,
'urgent': True,
'visible': True
}
}, {'draw_inner_divider': None}),
]
)
if __name__ == '__main__':
from tests import main
main()

467
tests/test_logging.py Normal file
View File

@ -0,0 +1,467 @@
# vim:fileencoding=utf-8:noet
'''Tests for various logging features'''
from __future__ import (unicode_literals, division, absolute_import, print_function)
import sys
import re
import codecs
import os
from io import StringIO
from shutil import rmtree
from powerline import finish_common_config, create_logger
from tests import TestCase
from tests.lib import replace_attr
TIMESTAMP_RE = r'\d{4}-\d\d-\d\d \d\d:\d\d:\d\d,\d{3}'
class TestRE(TestCase):
def assertMatches(self, text, regexp):
self.assertTrue(
re.match(regexp, text),
'{0!r} did not match {1!r}'.format(text, regexp),
)
def close_handlers(logger):
for handler in logger.handlers:
handler.close()
class TestHandlers(TestRE):
def test_stderr_handler_is_default(self):
out = StringIO()
err = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config)
pl.error('Foo')
close_handlers(logger)
self.assertMatches(err.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
self.assertEqual(out.getvalue(), '')
def test_stream_override(self):
out = StringIO()
err = StringIO()
stream = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_explicit_none(self):
out = StringIO()
err = StringIO()
stream = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [None]})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_explicit_stream_handler(self):
out = StringIO()
err = StringIO()
stream = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [['logging.StreamHandler', [[]]]]})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
self.assertEqual(stream.getvalue(), '')
self.assertMatches(err.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
self.assertEqual(out.getvalue(), '')
def test_explicit_stream_handler_implicit_stream(self):
out = StringIO()
err = StringIO()
stream = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [['logging.StreamHandler', []]]})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_file_handler(self):
out = StringIO()
err = StringIO()
stream = StringIO()
file_name = 'test_logging-test_file_handler'
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': file_name})
try:
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
with codecs.open(file_name, encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
finally:
os.unlink(file_name)
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_file_handler_create_dir(self):
out = StringIO()
err = StringIO()
stream = StringIO()
file_name = 'test_logging-test_file_handler_create_dir/file'
self.assertFalse(os.path.isdir(os.path.dirname(file_name)))
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': file_name})
try:
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
self.assertTrue(os.path.isdir(os.path.dirname(file_name)))
with codecs.open(file_name, encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
finally:
rmtree(os.path.dirname(file_name))
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_multiple_files(self):
out = StringIO()
err = StringIO()
stream = StringIO()
file_name_1 = 'test_logging-test_multiple_files-1'
file_name_2 = file_name_1[:-1] + '2'
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [file_name_1, file_name_2]})
try:
try:
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
for file_name in (file_name_1, file_name_2):
with codecs.open(file_name, encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
finally:
os.unlink(file_name_1)
finally:
os.unlink(file_name_2)
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_multiple_files_and_stream(self):
out = StringIO()
err = StringIO()
stream = StringIO()
file_name_1 = 'test_logging-test_multiple_files_and_stream-1'
file_name_2 = file_name_1[:-1] + '2'
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [file_name_1, file_name_2, None]})
try:
try:
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
for file_name in (file_name_1, file_name_2):
with codecs.open(file_name, encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
finally:
os.unlink(file_name_1)
finally:
os.unlink(file_name_2)
self.assertMatches(stream.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_handler_args(self):
out = StringIO()
err = StringIO()
stream = StringIO()
file_name = 'test_logging-test_handler_args'
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['RotatingFileHandler', [[file_name]]]
]})
try:
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
close_handlers(logger)
with codecs.open(file_name, encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
finally:
os.unlink(file_name)
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_handler_args_kwargs(self):
out = StringIO()
err = StringIO()
stream = StringIO()
file_name = 'test_logging-test_handler_args_kwargs'
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['RotatingFileHandler', [[file_name], {'maxBytes': 1, 'backupCount': 1}]]
]})
try:
try:
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.error('Foo')
pl.error('Bar')
close_handlers(logger)
with codecs.open(file_name, encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$')
with codecs.open(file_name + '.1', encoding='utf-8') as fp:
self.assertMatches(fp.read(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Foo\n$')
finally:
os.unlink(file_name + '.1')
finally:
os.unlink(file_name)
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_logger_level(self):
out = StringIO()
err = StringIO()
stream = StringIO()
stream1 = StringIO()
stream2 = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['logging.StreamHandler', [[stream1]], 'WARNING'],
['logging.StreamHandler', [[stream2]], 'ERROR'],
]})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.warn('Foo')
pl.error('Bar')
close_handlers(logger)
self.assertMatches(stream1.getvalue(), (
'^' + TIMESTAMP_RE + ':WARNING:__unknown__:Foo\n'
+ TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$'
))
self.assertMatches(stream2.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$')
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_logger_level_not_overriding_default(self):
out = StringIO()
err = StringIO()
stream = StringIO()
stream1 = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['logging.StreamHandler', [[stream1]], 'DEBUG'],
]})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.debug('Foo')
pl.error('Bar')
close_handlers(logger)
self.assertMatches(stream1.getvalue(), '^' + TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$')
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_top_log_level(self):
out = StringIO()
err = StringIO()
stream = StringIO()
stream1 = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['logging.StreamHandler', [[stream1]], 'DEBUG'],
], 'log_level': 'DEBUG'})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.debug('Foo')
pl.error('Bar')
close_handlers(logger)
self.assertMatches(stream1.getvalue(), (
'^' + TIMESTAMP_RE + ':DEBUG:__unknown__:Foo\n'
+ TIMESTAMP_RE + ':ERROR:__unknown__:Bar\n$'
))
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_logger_format(self):
out = StringIO()
err = StringIO()
stream = StringIO()
stream1 = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['logging.StreamHandler', [[stream1]], 'WARNING', 'FOO'],
]})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.warn('Foo')
pl.error('Bar')
close_handlers(logger)
self.assertEqual(stream1.getvalue(), 'FOO\nFOO\n')
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
def test_top_log_format(self):
out = StringIO()
err = StringIO()
stream = StringIO()
stream1 = StringIO()
stream2 = StringIO()
with replace_attr(sys, 'stdout', out, 'stderr', err):
common_config = finish_common_config('utf-8', {'log_file': [
['logging.StreamHandler', [[stream1]], 'WARNING', 'FOO'],
['logging.StreamHandler', [[stream2]], 'WARNING'],
], 'log_format': 'BAR'})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.warn('Foo')
pl.error('Bar')
close_handlers(logger)
self.assertEqual(stream2.getvalue(), 'BAR\nBAR\n')
self.assertEqual(stream1.getvalue(), 'FOO\nFOO\n')
self.assertEqual(stream.getvalue(), '')
self.assertEqual(err.getvalue(), '')
self.assertEqual(out.getvalue(), '')
class TestPowerlineLogger(TestRE):
def test_args_formatting(self):
stream = StringIO()
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.warn('foo {0}', 'Test')
pl.warn('bar {0!r}', 'Test')
close_handlers(logger)
self.assertMatches(stream.getvalue(), (
'^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo Test\n'
+ TIMESTAMP_RE + ':WARNING:__unknown__:bar u?\'Test\'\n$'
))
def test_prefix_formatting(self):
stream = StringIO()
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.prefix = '1'
pl.warn('foo')
pl.prefix = '2'
pl.warn('bar')
close_handlers(logger)
self.assertMatches(stream.getvalue(), (
'^' + TIMESTAMP_RE + ':WARNING:__unknown__:1:foo\n'
+ TIMESTAMP_RE + ':WARNING:__unknown__:2:bar\n$'
))
def test_kwargs_formatting(self):
stream = StringIO()
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.warn('foo {arg}', arg='Test')
pl.warn('bar {arg!r}', arg='Test')
close_handlers(logger)
self.assertMatches(stream.getvalue(), (
'^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo Test\n'
+ TIMESTAMP_RE + ':WARNING:__unknown__:bar u?\'Test\'\n$'
))
def test_args_kwargs_formatting(self):
stream = StringIO()
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.warn('foo {0!r} {arg}', 'Test0', arg='Test')
pl.warn('bar {0} {arg!r}', 'Test0', arg='Test')
close_handlers(logger)
self.assertMatches(stream.getvalue(), (
'^' + TIMESTAMP_RE + ':WARNING:__unknown__:foo u?\'Test0\' Test\n'
+ TIMESTAMP_RE + ':WARNING:__unknown__:bar Test0 u?\'Test\'\n$'
))
def test_exception_formatting(self):
stream = StringIO()
common_config = finish_common_config('utf-8', {})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
try:
raise ValueError('foo')
except ValueError:
pl.exception('Message')
close_handlers(logger)
self.assertMatches(stream.getvalue(), (
'^' + TIMESTAMP_RE + ':ERROR:__unknown__:Message\n'
+ 'Traceback \\(most recent call last\\):\n'
+ '(?: File ".*?", line \\d+, in \\w+\n [^\n]*\n)+'
+ 'ValueError: foo\n$'
))
def test_levels(self):
stream = StringIO()
common_config = finish_common_config('utf-8', {'log_level': 'DEBUG'})
logger, pl, get_module_attr = create_logger(common_config, stream=stream)
pl.debug('1')
pl.info('2')
pl.warn('3')
pl.error('4')
pl.critical('5')
close_handlers(logger)
self.assertMatches(stream.getvalue(), (
'^' + TIMESTAMP_RE + ':DEBUG:__unknown__:1\n'
+ TIMESTAMP_RE + ':INFO:__unknown__:2\n'
+ TIMESTAMP_RE + ':WARNING:__unknown__:3\n'
+ TIMESTAMP_RE + ':ERROR:__unknown__:4\n'
+ TIMESTAMP_RE + ':CRITICAL:__unknown__:5\n$'
))
old_cwd = None
def setUpModule():
global old_cwd
global __file__
old_cwd = os.getcwd()
__file__ = os.path.abspath(__file__)
os.chdir(os.path.dirname(__file__))
def tearDownModule():
global old_cwd
os.chdir(old_cwd)
if __name__ == '__main__':
from tests import main
main()

View File

@ -811,6 +811,12 @@ class TestSys(TestCommon):
{'contents': '4 ', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100},
{'contents': '2', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 75.0}
])
self.assertEqual(self.module.system_load(pl=pl, short=True), [
{'contents': '7.5', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100},
])
self.assertEqual(self.module.system_load(pl=pl, format='{avg:.0f}', threshold_good=0, threshold_bad=1, short=True), [
{'contents': '8', 'highlight_groups': ['system_load_gradient', 'system_load'], 'divider_highlight_group': 'background:divider', 'gradient_level': 100},
])
def test_cpu_load_percent(self):
try:
@ -883,49 +889,85 @@ class TestWthr(TestCommon):
class TestI3WM(TestCase):
def test_workspaces(self):
pl = Pl()
with replace_attr(i3wm, 'conn', Args(get_workspaces=lambda: iter([
@staticmethod
def get_workspaces():
return iter([
{'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False},
{'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True},
{'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True},
{'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True},
]))):
self.assertEqual(i3wm.workspaces(pl=pl), [
])
def test_workspaces(self):
pl = Pl()
with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
segment_info = {}
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info), [
{'contents': '1: w1', 'highlight_groups': ['workspace']},
{'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=None), [
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=None), [
{'contents': '1: w1', 'highlight_groups': ['workspace']},
{'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=['focused', 'urgent']), [
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent']), [
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=['visible']), [
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible']), [
{'contents': '2: w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=['visible'], strip=3), [
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [
{'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']},
{'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
{'contents': 'w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=['focused', 'urgent'], output='DVI01'), [
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['focused', 'urgent'], output='DVI01'), [
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=['visible'], output='HDMI1'), [
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, only_show=['visible'], strip=3, output='LVDS1'), [
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3, output='LVDS1'), [
{'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']},
])
segment_info['output'] = 'LVDS1'
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], output='HDMI1'), [
{'contents': '3: w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspaces(pl=pl, segment_info=segment_info, only_show=['visible'], strip=3), [
{'contents': 'w2', 'highlight_groups': ['w_visible', 'workspace']},
])
def test_workspace(self):
pl = Pl()
with replace_attr(i3wm, 'get_i3_connection', lambda: Args(get_workspaces=self.get_workspaces)):
segment_info = {}
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='1: w1'), [
{'contents': '1: w1', 'highlight_groups': ['workspace']},
])
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='3: w3', strip=True), [
{'contents': 'w3', 'highlight_groups': ['w_urgent', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='9: w9'), None)
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info), [
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
])
segment_info['workspace'] = next(self.get_workspaces())
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, workspace='4: w4'), [
{'contents': '4: w4', 'highlight_groups': ['w_focused', 'w_urgent', 'w_visible', 'workspace']},
])
self.assertEqual(i3wm.workspace(pl=pl, segment_info=segment_info, strip=True), [
{'contents': 'w1', 'highlight_groups': ['workspace']},
])
def test_mode(self):
pl = Pl()
@ -934,6 +976,45 @@ class TestI3WM(TestCase):
self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'default'}, names={'default': 'test'}), 'test')
self.assertEqual(i3wm.mode(pl=pl, segment_info={'mode': 'test'}, names={'default': 'test', 'test': 't'}), 't')
def test_scratchpad(self):
class Conn(object):
def get_tree(self):
return self
def descendents(self):
nodes_unfocused = [Args(focused = False)]
nodes_focused = [Args(focused = True)]
workspace_scratch = lambda: Args(name='__i3_scratch')
workspace_noscratch = lambda: Args(name='2: www')
return [
Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused),
Args(scratchpad_state='changed', urgent=True, workspace=workspace_noscratch, nodes=nodes_focused),
Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_unfocused),
Args(scratchpad_state=None, urgent=False, workspace=workspace_noscratch, nodes=nodes_unfocused),
Args(scratchpad_state='fresh', urgent=False, workspace=workspace_scratch, nodes=nodes_focused),
Args(scratchpad_state=None, urgent=True, workspace=workspace_noscratch, nodes=nodes_unfocused),
]
pl = Pl()
with replace_attr(i3wm, 'get_i3_connection', lambda: Conn()):
self.assertEqual(i3wm.scratchpad(pl=pl), [
{'contents': 'O', 'highlight_groups': ['scratchpad']},
{'contents': 'X', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:focused', 'scratchpad:visible', 'scratchpad']},
{'contents': 'O', 'highlight_groups': ['scratchpad']},
{'contents': 'X', 'highlight_groups': ['scratchpad:visible', 'scratchpad']},
{'contents': 'O', 'highlight_groups': ['scratchpad:focused', 'scratchpad']},
{'contents': 'X', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:visible', 'scratchpad']},
])
self.assertEqual(i3wm.scratchpad(pl=pl, icons={'changed': '-', 'fresh': 'o'}), [
{'contents': 'o', 'highlight_groups': ['scratchpad']},
{'contents': '-', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:focused', 'scratchpad:visible', 'scratchpad']},
{'contents': 'o', 'highlight_groups': ['scratchpad']},
{'contents': '-', 'highlight_groups': ['scratchpad:visible', 'scratchpad']},
{'contents': 'o', 'highlight_groups': ['scratchpad:focused', 'scratchpad']},
{'contents': '-', 'highlight_groups': ['scratchpad:urgent', 'scratchpad:visible', 'scratchpad']},
])
class TestMail(TestCommon):
module_name = 'mail'

View File

@ -16,7 +16,7 @@ catch
cquit
endtry
if result isnot# '%1T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  %2T2 ./def %#Pl_236_3158064_240_5789784_NONE# %3T%#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %T%#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Tabs '
if result isnot# '%1T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %2T%#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %3T%#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %T%#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Tabs '
call writefile(['Unexpected tabline', result], 'message.fail')
cquit
endif
@ -30,7 +30,7 @@ catch
cquit
endtry
if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs '
if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs '
call writefile(['Unexpected tabline (2)', result], 'message.fail')
cquit
endif
@ -42,7 +42,7 @@ catch
call writefile(['Exception while evaluating &tabline (3)', v:exception], 'message.fail')
endtry
if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc  2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs '
if result isnot# '%T%#Pl_247_10395294_236_3158064_NONE# 1 ./abc %#Pl_244_8421504_236_3158064_NONE# %#Pl_247_10395294_236_3158064_NONE#2 ./def %#Pl_236_3158064_240_5789784_NONE# %#Pl_250_12369084_240_5789784_NONE#3 ./%#Pl_231_16777215_240_5789784_bold#ghi %#Pl_240_5789784_236_3158064_NONE# %#Pl_231_16777215_236_3158064_NONE#                                         %#Pl_252_13684944_236_3158064_NONE# %#Pl_235_2500134_252_13684944_bold# Bufs '
call writefile(['Unexpected tabline (3)', result], 'message.fail')
cquit
endif

View File

@ -265,6 +265,14 @@ def eval(expr):
import os
assert os.path.basename(current.buffer.name).startswith('NERD_tree_')
return '/usr/include'
elif expr.startswith('getbufvar('):
import re
match = re.match(r'^getbufvar\((\d+), ["\'](.+)["\']\)$', expr)
if not match:
raise NotImplementedError(expr)
bufnr = int(match.group(1))
varname = match.group(2)
return _emul_getbufvar(bufnr, varname)
elif expr == 'tabpagenr()':
return current.tabpage.number
elif expr == 'tabpagenr("$")':