mirror of
https://github.com/powerline/powerline.git
synced 2025-07-28 16:24:57 +02:00
Release 1.1
Changes: - Deprecated `powerline.segments.common`, moved all segments into deeply nested modules (e.g. `powerline.segments.common.vcs`). - Added support for different tag formats provided by tagbar plugin. - Improved non-unicode locales handling. - Improved non-unicode filenames handling. - Improved Vim troubleshooting: it now runs powerline-lint automatically. - Fixed weather segment: it was remembering its argument from the first run and never allowed to change it. - Fixed Uv watcher: it raised an exception for nonexistent files. - Fixed zsh named directories handling when AUTO_NAME_DIRS option is set. - Fixed --config_path handling when using daemon: in some cases it was remembered once and never changed. - Fixed Vim overrides when Vim is using Python-3*. - Fixed Uv watcher on Python-3*: it used os.path.walk which was removed. - Refactored and documented powerline.lint.
This commit is contained in:
commit
7aafbf0e3b
@ -113,6 +113,8 @@ Programming style
|
||||
|
||||
'''Powerline super module'''
|
||||
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import sys
|
||||
|
||||
from argparse import ArgumentParser
|
||||
|
@ -2,3 +2,5 @@ recursive-include powerline *.json *.vim
|
||||
recursive-include powerline/bindings *.*
|
||||
recursive-exclude powerline/bindings *.pyc *.pyo
|
||||
recursive-include client *.*
|
||||
recursive-include docs/source *.rst *.py
|
||||
include docs/Makefile
|
||||
|
12
README.rst
12
README.rst
@ -34,24 +34,24 @@ Features
|
||||
* **Support for prompts and statuslines in many applications.** Originally
|
||||
created exclusively for vim statuslines, the project has evolved to
|
||||
provide statuslines in tmux and several WMs, and prompts for shells like
|
||||
bash/zsh and other applications. It's simple to write renderers for any
|
||||
other applications that Powerline doesn't yet support.
|
||||
bash/zsh and other applications. It’s simple to write renderers for any
|
||||
other applications that Powerline doesn’t yet support.
|
||||
* **Configuration and colorschemes written in JSON.** JSON is
|
||||
a standardized, simple and easy to use file format that allows for easy
|
||||
user configuration across all of Powerline's supported applications.
|
||||
user configuration across all of Powerline’s supported applications.
|
||||
* **Fast and lightweight, with daemon support for even better performance.**
|
||||
Although the code base spans a couple of thousand lines of code with no
|
||||
goal of "less than X lines of code", the main focus is on good performance
|
||||
goal of “less than X lines of code”, the main focus is on good performance
|
||||
and as little code as possible while still providing a rich set of
|
||||
features. The new daemon also ensures that only one Python instance is
|
||||
launched for prompts and statuslines, which provides excellent
|
||||
performance.
|
||||
|
||||
*But I hate Python / I don't need shell prompts / this is just too much
|
||||
*But I hate Python / I don’t need shell prompts / this is just too much
|
||||
hassle for me / what happened to the original vim-powerline project / …*
|
||||
|
||||
You should check out some of the Powerline derivatives. The most lightweight
|
||||
and feature-rich alternative is currently Bailey Ling's `vim-airline
|
||||
and feature-rich alternative is currently Bailey Ling’s `vim-airline
|
||||
<https://github.com/bling/vim-airline>`_ project.
|
||||
|
||||
------
|
||||
|
@ -7,13 +7,20 @@ import socket
|
||||
import errno
|
||||
import os
|
||||
|
||||
from locale import getpreferredencoding
|
||||
|
||||
try:
|
||||
from posix import environ
|
||||
except ImportError:
|
||||
from os import environ
|
||||
|
||||
# XXX Hack for importing powerline modules to work.
|
||||
sys.path.pop(0)
|
||||
|
||||
try:
|
||||
from powerline.lib.encoding import get_preferred_output_encoding
|
||||
except ImportError:
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(os.path.realpath(__file__)))))
|
||||
from powerline.lib.encoding import get_preferred_output_encoding
|
||||
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print('Must provide at least one argument.', file=sys.stderr)
|
||||
@ -51,7 +58,7 @@ except Exception:
|
||||
args = ['powerline-render'] + sys.argv[1:]
|
||||
os.execvp('powerline-render', args)
|
||||
|
||||
fenc = getpreferredencoding() or 'utf-8'
|
||||
fenc = get_preferred_output_encoding()
|
||||
|
||||
|
||||
def tobytes(s):
|
||||
|
@ -11,7 +11,7 @@ sys.path.insert(0, os.path.abspath(os.getcwd()))
|
||||
extensions = ['powerline_autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
|
||||
source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
project = u'Powerline'
|
||||
project = 'Powerline'
|
||||
version = 'beta'
|
||||
release = 'beta'
|
||||
exclude_patterns = ['_build']
|
||||
@ -22,7 +22,7 @@ html_show_copyright = False
|
||||
|
||||
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
||||
|
||||
if not on_rtd: # only import and set the theme if we're building docs locally
|
||||
if not on_rtd: # only import and set the theme if we’re building docs locally
|
||||
try:
|
||||
import sphinx_rtd_theme
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
@ -87,29 +87,29 @@ You can move the segment dictionaries around to change the segment
|
||||
positions, or remove the entire dictionary to remove the segment from the
|
||||
prompt or statusline.
|
||||
|
||||
.. note:: It's essential that the contents of all your configuration files
|
||||
is valid JSON! It's strongly recommended that you run your configuration
|
||||
.. note:: It’s essential that the contents of all your configuration files
|
||||
is valid JSON! It’s strongly recommended that you run your configuration
|
||||
files through ``jsonlint`` after changing them.
|
||||
|
||||
Some segments need a user configuration to work properly. Here's a couple of
|
||||
Some segments need a user configuration to work properly. Here’s a couple of
|
||||
segments that you may want to customize right away:
|
||||
|
||||
**E-mail alert segment**
|
||||
You have to set your username and password (and possibly server/port)
|
||||
for the e-mail alert segment. If you're using GMail it's recommended
|
||||
for the e-mail alert segment. If you’re using GMail it’s recommended
|
||||
that you `generate an application-specific password
|
||||
<https://accounts.google.com/IssuedAuthSubTokens>`_ for this purpose.
|
||||
|
||||
Open a theme file, scroll down to the ``email_imap_alert`` segment and
|
||||
set your ``username`` and ``password``. The server defaults to GMail's
|
||||
set your ``username`` and ``password``. The server defaults to GMail’s
|
||||
IMAP server, but you can set the server/port by adding a ``server`` and
|
||||
a ``port`` argument.
|
||||
**Weather segment**
|
||||
The weather segment will try to find your location using a GeoIP lookup,
|
||||
so unless you're on a VPN you probably won't have to change the location
|
||||
so unless you’re on a VPN you probably won’t have to change the location
|
||||
query.
|
||||
|
||||
If you want to change the location query or the temperature unit you'll
|
||||
If you want to change the location query or the temperature unit you’ll
|
||||
have to update the segment arguments. Open a theme file, scroll down to
|
||||
the weather segment and update it to include unit/location query
|
||||
arguments:
|
||||
|
@ -177,7 +177,7 @@ Color definitions
|
||||
* A cterm color index.
|
||||
* A list with a cterm color index and a hex color string (e.g. ``[123,
|
||||
"aabbcc"]``). This is useful for colorschemes that use colors that
|
||||
aren't available in color terminals.
|
||||
aren’t available in color terminals.
|
||||
|
||||
``gradients``
|
||||
Gradient definitions, consisting of a dict where the key is the name of the
|
||||
@ -236,7 +236,7 @@ override those from each previous file. It is required that either
|
||||
|
||||
``mode_translations``
|
||||
Mode-specific highlighting for extensions that support it (e.g. the vim
|
||||
extension). It's an easy way of changing a color in a specific mode.
|
||||
extension). It’s an easy way of changing a color in a specific mode.
|
||||
Consists of a dict where the key is the mode and the value is a dict
|
||||
with the following options:
|
||||
|
||||
@ -308,7 +308,7 @@ ascii Theme without any unicode characters at all
|
||||
|
||||
``dividers``
|
||||
Defines the dividers used in all Powerline extensions. This option
|
||||
should usually only be changed if you don't have a patched font, or if
|
||||
should usually only be changed if you don’t have a patched font, or if
|
||||
you use a font patched with the legacy font patcher.
|
||||
|
||||
The ``hard`` dividers are used to divide segments with different
|
||||
|
@ -15,7 +15,7 @@ always absolute.
|
||||
|
||||
Segments are regular Python functions, and they may accept arguments. All
|
||||
arguments should have a default value which will be used for themes that
|
||||
don't provide an ``args`` dict.
|
||||
don’t provide an ``args`` dict.
|
||||
|
||||
More information is available in :ref:`Writing segments <dev-segments>` section.
|
||||
|
||||
|
@ -2,5 +2,56 @@
|
||||
Common segments
|
||||
***************
|
||||
|
||||
.. automodule:: powerline.segments.common
|
||||
VCS submodule
|
||||
=============
|
||||
|
||||
.. automodule:: powerline.segments.common.vcs
|
||||
:members:
|
||||
|
||||
System properties
|
||||
=================
|
||||
|
||||
.. automodule:: powerline.segments.common.sys
|
||||
:members:
|
||||
|
||||
Network
|
||||
=======
|
||||
|
||||
.. automodule:: powerline.segments.common.net
|
||||
:members:
|
||||
|
||||
Current environment
|
||||
===================
|
||||
|
||||
.. automodule:: powerline.segments.common.env
|
||||
:members:
|
||||
|
||||
Battery
|
||||
=======
|
||||
|
||||
.. automodule:: powerline.segments.common.bat
|
||||
:members:
|
||||
|
||||
Weather
|
||||
=======
|
||||
|
||||
.. automodule:: powerline.segments.common.wthr
|
||||
:members:
|
||||
|
||||
Date and time
|
||||
=============
|
||||
|
||||
.. automodule:: powerline.segments.common.time
|
||||
:members:
|
||||
|
||||
Mail
|
||||
====
|
||||
|
||||
.. automodule:: powerline.segments.common.mail
|
||||
:members:
|
||||
|
||||
Media players
|
||||
=============
|
||||
|
||||
.. automodule:: powerline.segments.common.players
|
||||
:members:
|
||||
|
@ -371,6 +371,10 @@ Vim ``segment_info`` argument is a dictionary with the following keys:
|
||||
``mode``
|
||||
Current mode.
|
||||
|
||||
``encoding``
|
||||
Value of ``&encoding`` from the time when powerline was initialized. It
|
||||
should be used to convert return values.
|
||||
|
||||
.. note::
|
||||
Your segment generally should not assume that it is run for the current
|
||||
window, current buffer or current tabpage. “Current window” and “current
|
||||
|
@ -90,8 +90,8 @@ Patched fonts
|
||||
This method is the fallback method and works for every terminal, with the
|
||||
exception of :ref:`rxvt-unicode <tips-and-tricks-urxvt>`.
|
||||
|
||||
Download the font of your choice from `powerline-fonts`_. If you can't find
|
||||
your preferred font in the `powerline-fonts`_ repo, you'll have to patch your
|
||||
Download the font of your choice from `powerline-fonts`_. If you can’t find
|
||||
your preferred font in the `powerline-fonts`_ repo, you’ll have to patch your
|
||||
own font instead.
|
||||
|
||||
.. _powerline-fonts: https://github.com/Lokaltog/powerline-fonts
|
||||
|
@ -10,7 +10,7 @@ automatically do most of the configuration for you.
|
||||
* `Arch Linux (AUR), Python 3 version <https://aur.archlinux.org/packages/python-powerline-git/>`_
|
||||
* Gentoo Live ebuild in `raiagent <https://github.com/leycec/raiagent>`_ overlay
|
||||
|
||||
If you're running a distribution without an official package you'll have to
|
||||
If you’re running a distribution without an official package you’ll have to
|
||||
follow the installation guide below:
|
||||
|
||||
1. Install Python 3.2+ or Python 2.6+ with ``pip``. This step is
|
||||
@ -34,8 +34,8 @@ Fonts installation
|
||||
Fontconfig
|
||||
----------
|
||||
|
||||
This method only works on Linux. It's the recommended method if your
|
||||
terminal emulator supports it as you don't have to patch any fonts, and it
|
||||
This method only works on Linux. It’s the recommended method if your
|
||||
terminal emulator supports it as you don’t have to patch any fonts, and it
|
||||
generally works well with any coding font.
|
||||
|
||||
#. Download the latest version of the symbol font and fontconfig file::
|
||||
@ -54,16 +54,16 @@ generally works well with any coding font.
|
||||
fc-cache -vf ~/.fonts/
|
||||
|
||||
#. Install the fontconfig file. For newer versions of fontconfig the config
|
||||
path is ``~/.config/fontconfig/conf.d/``, for older versions it's
|
||||
path is ``~/.config/fontconfig/conf.d/``, for older versions it’s
|
||||
``~/.fonts.conf.d/``::
|
||||
|
||||
mv 10-powerline-symbols.conf ~/.config/fontconfig/conf.d/
|
||||
|
||||
If you can't see the custom symbols, please close all instances of your
|
||||
If you can’t see the custom symbols, please close all instances of your
|
||||
terminal emulator. You may need to restart X for the changes to take
|
||||
effect.
|
||||
|
||||
If you *still* can't see the custom symbols, double-check that you have
|
||||
If you *still* can’t see the custom symbols, double-check that you have
|
||||
installed the font to a valid X font path, and that you have installed the
|
||||
fontconfig file to a valid fontconfig path. Alternatively try to install
|
||||
a :ref:`patched font <installation-patched-fonts>`.
|
||||
@ -87,9 +87,9 @@ After installing the patched font you need to update Gvim or your terminal
|
||||
emulator to use the patched font. The correct font usually ends with *for
|
||||
Powerline*.
|
||||
|
||||
If you can't see the custom symbols, please close all instances of your
|
||||
If you can’t see the custom symbols, please close all instances of your
|
||||
terminal emulator. You may need to restart X for the changes to take
|
||||
effect.
|
||||
|
||||
If you *still* can't see the custom symbols, double-check that you have
|
||||
If you *still* can’t see the custom symbols, double-check that you have
|
||||
installed the font to a valid X font path.
|
||||
|
@ -45,7 +45,7 @@ Vim installation
|
||||
================
|
||||
|
||||
Any terminal vim version with Python 3.2+ or Python 2.6+ support should work,
|
||||
but if you're using MacVim you need to install it using the following command::
|
||||
but if you’re using MacVim you need to install it using the following command::
|
||||
|
||||
brew install macvim --env-std --override-system-vim
|
||||
|
||||
|
@ -20,24 +20,24 @@ Features
|
||||
* **Support for prompts and statuslines in many applications.** Originally
|
||||
created exclusively for vim statuslines, the project has evolved to
|
||||
provide statuslines in tmux and several WMs, and prompts for shells like
|
||||
bash/zsh and other applications. It's simple to write renderers for any
|
||||
other applications that Powerline doesn't yet support.
|
||||
bash/zsh and other applications. It’s simple to write renderers for any
|
||||
other applications that Powerline doesn’t yet support.
|
||||
* **Configuration and colorschemes written in JSON.** JSON is
|
||||
a standardized, simple and easy to use file format that allows for easy
|
||||
user configuration across all of Powerline's supported applications.
|
||||
user configuration across all of Powerline’s supported applications.
|
||||
* **Fast and lightweight, with daemon support for even better performance.**
|
||||
Although the code base spans a couple of thousand lines of code with no
|
||||
goal of "less than X lines of code", the main focus is on good performance
|
||||
goal of “less than X lines of code”, the main focus is on good performance
|
||||
and as little code as possible while still providing a rich set of
|
||||
features. The new daemon also ensures that only one Python instance is
|
||||
launched for prompts and statuslines, which provides excellent
|
||||
performance.
|
||||
|
||||
*But I hate Python / I don't need shell prompts / this is just too much
|
||||
*But I hate Python / I don’t need shell prompts / this is just too much
|
||||
hassle for me / what happened to the original vim-powerline project / …*
|
||||
|
||||
You should check out some of the Powerline derivatives. The most lightweight
|
||||
and feature-rich alternative is currently Bailey Ling's `vim-airline
|
||||
and feature-rich alternative is currently Bailey Ling’s `vim-airline
|
||||
<https://github.com/bling/vim-airline>`_ project.
|
||||
|
||||
Screenshots
|
||||
|
@ -47,7 +47,7 @@ In the ``~/.Xdefaults`` add the following::
|
||||
I noticed that Source Code Pro has the glyphs there already, but the pixel size
|
||||
of the fonts play a role in whether or not the > or the < separators showing up
|
||||
or not. Using font size 12, glyphs on the right hand side of the powerline are
|
||||
present, but the ones on the left don't. Pixel size 14, brings the reverse
|
||||
present, but the ones on the left don’t. Pixel size 14, brings the reverse
|
||||
problem. Font size 13 seems to work just fine.
|
||||
|
||||
Reloading powerline after update
|
||||
|
@ -13,14 +13,14 @@ System-specific issues
|
||||
Common issues
|
||||
=============
|
||||
|
||||
I'm using tmux and Powerline looks like crap, what's wrong?
|
||||
I’m using tmux and Powerline looks like crap, what’s wrong?
|
||||
-----------------------------------------------------------
|
||||
|
||||
* You need to tell tmux that it has 256-color capabilities. Add this to your
|
||||
:file:`.tmux.conf` to solve this issue::
|
||||
|
||||
set -g default-terminal "screen-256color"
|
||||
* If you're using iTerm2, make sure that you have enabled the setting
|
||||
* If you’re using iTerm2, make sure that you have enabled the setting
|
||||
:guilabel:`Set locale variables automatically` in :menuselection:`Profiles -->
|
||||
Terminal --> Environment`.
|
||||
* Make sure tmux knows that terminal it is running in support 256 colors. You
|
||||
|
@ -2,19 +2,27 @@
|
||||
Troubleshooting on Linux
|
||||
************************
|
||||
|
||||
I can't see any fancy symbols, what's wrong?
|
||||
I can’t see any fancy symbols, what’s wrong?
|
||||
--------------------------------------------
|
||||
|
||||
* Make sure that you've configured gvim or your terminal emulator to use
|
||||
* Make sure that you’ve configured gvim or your terminal emulator to use
|
||||
a patched 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 distro's
|
||||
a UTF-8 locale (e.g. ``LANG=en_US.utf8``). Consult your Linux distro’s
|
||||
documentation for information about setting these variables correctly.
|
||||
* Make sure that vim is compiled with the ``--with-features=big`` flag.
|
||||
* If you're using rxvt-unicode, make sure that it's compiled with the
|
||||
* If you’re using rxvt-unicode make sure that it’s compiled with the
|
||||
``--enable-unicode3`` flag.
|
||||
* If you’re using xterm make sure you have told it to work with unicode. You may
|
||||
need ``-u8`` command-line argument, ``uxterm`` shell wrapper that is usually
|
||||
shipped with xterm for this or ``xterm*utf8`` property set to ``1`` or ``2``
|
||||
in ``~/.Xresources`` (applied with ``xrdb``). Note that in case ``uxterm`` is
|
||||
used configuration is done via ``uxterm*…`` properties and not ``xterm*…``.
|
||||
|
||||
The fancy symbols look a bit blurry or "off"!
|
||||
In any case the only absolute requirement is launching xterm with UTF-8
|
||||
locale.
|
||||
|
||||
The fancy symbols look a bit blurry or “off”!
|
||||
---------------------------------------------
|
||||
|
||||
* Make sure that you have patched all variants of your font (i.e. both the
|
||||
|
@ -2,21 +2,21 @@
|
||||
Troubleshooting on OS X
|
||||
***********************
|
||||
|
||||
I can't see any fancy symbols, what's wrong?
|
||||
I can’t see any fancy symbols, what’s wrong?
|
||||
--------------------------------------------
|
||||
|
||||
* If you're using iTerm2, please update to `this revision
|
||||
* If you’re using iTerm2, please update to `this revision
|
||||
<https://github.com/gnachman/iTerm2/commit/8e3ad6dabf83c60b8cf4a3e3327c596401744af6>`_
|
||||
or newer.
|
||||
* You need to set your ``LANG`` and ``LC_*`` environment variables to
|
||||
a UTF-8 locale (e.g. ``LANG=en_US.utf8``). Consult your Linux distro's
|
||||
a UTF-8 locale (e.g. ``LANG=en_US.utf8``). Consult your Linux distro’s
|
||||
documentation for information about setting these variables correctly.
|
||||
|
||||
The colors look weird in the default OS X Terminal app!
|
||||
-------------------------------------------------------
|
||||
|
||||
* The arrows may have the wrong colors if you have changed the "minimum
|
||||
contrast" slider in the color tab of your OS X settings.
|
||||
* The arrows may have the wrong colors if you have changed the “minimum
|
||||
contrast” slider in the color tab of your OS X settings.
|
||||
* The default OS X Terminal app is known to have some issues with the
|
||||
Powerline colors. Please use another terminal emulator. iTerm2 should work
|
||||
fine.
|
||||
@ -24,8 +24,8 @@ The colors look weird in the default OS X Terminal app!
|
||||
The colors look weird in iTerm2!
|
||||
--------------------------------
|
||||
|
||||
* The arrows may have the wrong colors if you have changed the "minimum
|
||||
contrast" slider in the color tab of your OS X settings.
|
||||
* The arrows may have the wrong colors if you have changed the “minimum
|
||||
contrast” slider in the color tab of your OS X settings.
|
||||
* Please disable background transparency to resolve this issue.
|
||||
|
||||
Statusline is getting wrapped to the next line in iTerm2
|
||||
|
@ -12,11 +12,11 @@ The vim plugin requires a vim version with Python support compiled in. You
|
||||
can check if your vim supports Python by running ``vim --version | grep
|
||||
+python``.
|
||||
|
||||
If your vim version doesn't have support for Python, you'll have to compile
|
||||
If your vim version doesn’t have support for Python, you’ll have to compile
|
||||
it with the ``--enable-pythoninterp`` flag (``--enable-python3interp`` if
|
||||
you want Python 3 support instead). Note that this also requires the related
|
||||
Python headers to be installed on your system. Please consult your
|
||||
distribution's documentation for details on how to compile and install
|
||||
distribution’s documentation for details on how to compile and install
|
||||
packages.
|
||||
|
||||
Vim version 7.4 or newer is recommended for performance reasons, but Powerline
|
||||
|
@ -25,7 +25,7 @@ directory:
|
||||
|
||||
set rtp+={repository_root}/powerline/bindings/vim
|
||||
|
||||
If you're using pathogen and don't want Powerline functionality in any other
|
||||
If you’re using pathogen and don’t want Powerline functionality in any other
|
||||
applications, simply add Powerline as a bundle and point the path above to the
|
||||
Powerline bundle directory, e.g.
|
||||
``~/.vim/bundle/powerline/powerline/bindings/vim``.
|
||||
|
@ -5,7 +5,6 @@ import os
|
||||
import sys
|
||||
import logging
|
||||
|
||||
from locale import getpreferredencoding
|
||||
from threading import Lock, Event
|
||||
|
||||
from powerline.colorscheme import Colorscheme
|
||||
@ -13,6 +12,11 @@ from powerline.lib.config import ConfigLoader
|
||||
from powerline.lib.unicode import safe_unicode, FailedUnicode
|
||||
from powerline.config import DEFAULT_SYSTEM_CONFIG_DIR
|
||||
from powerline.lib import mergedicts
|
||||
from powerline.lib.encoding import get_preferred_output_encoding
|
||||
|
||||
|
||||
class NotInterceptedError(BaseException):
|
||||
pass
|
||||
|
||||
|
||||
def _config_loader_condition(path):
|
||||
@ -413,7 +417,7 @@ class Powerline(object):
|
||||
self.setup_kwargs = {}
|
||||
self.imported_modules = set()
|
||||
|
||||
get_encoding = staticmethod(getpreferredencoding)
|
||||
get_encoding = staticmethod(get_preferred_output_encoding)
|
||||
'''Get encoding used by the current application
|
||||
|
||||
Usually returns encoding of the current locale.
|
||||
@ -744,16 +748,12 @@ class Powerline(object):
|
||||
self.update_renderer()
|
||||
return self.renderer.render(*args, **kwargs)
|
||||
except Exception as e:
|
||||
exc = e
|
||||
try:
|
||||
self.exception('Failed to render: {0}', str(e))
|
||||
except Exception as e:
|
||||
# Updates e variable to new value, masking previous one.
|
||||
# Normally it is the same exception (due to raise in case pl is
|
||||
# unset), but it may also show error in logger. Note that latter
|
||||
# is not logged by logger for obvious reasons, thus this also
|
||||
# prevents us from seeing logger traceback.
|
||||
pass
|
||||
return FailedUnicode(safe_unicode(e))
|
||||
exc = e
|
||||
return FailedUnicode(safe_unicode(exc))
|
||||
|
||||
def render_above_lines(self, *args, **kwargs):
|
||||
'''Like .render(), but for ``self.renderer.render_above_lines()``
|
||||
@ -763,16 +763,12 @@ class Powerline(object):
|
||||
for line in self.renderer.render_above_lines(*args, **kwargs):
|
||||
yield line
|
||||
except Exception as e:
|
||||
exc = e
|
||||
try:
|
||||
self.exception('Failed to render: {0}', str(e))
|
||||
except Exception as e:
|
||||
# Updates e variable to new value, masking previous one.
|
||||
# Normally it is the same exception (due to raise in case pl is
|
||||
# unset), but it may also show error in logger. Note that latter
|
||||
# is not logged by logger for obvious reasons, thus this also
|
||||
# prevents us from seeing logger traceback.
|
||||
pass
|
||||
yield FailedUnicode(safe_unicode(e))
|
||||
exc = e
|
||||
yield FailedUnicode(safe_unicode(exc))
|
||||
|
||||
def setup(self, *args, **kwargs):
|
||||
'''Setup the environment to use powerline.
|
||||
|
@ -33,7 +33,7 @@ def read_to_log(pl, client):
|
||||
while True:
|
||||
start_time = monotonic()
|
||||
s = powerline.render(side='right')
|
||||
request = "powerline_widget:set_markup('" + s.replace('\\', '\\\\').replace("'", "\\'") + "')\n"
|
||||
request = 'powerline_widget:set_markup(\'' + s.replace('\\', '\\\\').replace('\'', '\\\'') + '\')\n'
|
||||
client = Popen(['awesome-client'], shell=False, stdout=PIPE, stderr=PIPE, stdin=PIPE)
|
||||
client.stdin.write(request.encode('utf-8'))
|
||||
client.stdin.close()
|
||||
|
@ -31,7 +31,7 @@ _powerline_init_tmux_support() {
|
||||
# TMUX variable may be unset to create new tmux session inside this one
|
||||
_POWERLINE_TMUX="$TMUX"
|
||||
|
||||
trap "_powerline_tmux_set_columns" WINCH
|
||||
trap '_powerline_tmux_set_columns' WINCH
|
||||
_powerline_tmux_set_columns
|
||||
|
||||
test "x$PROMPT_COMMAND" != "x${PROMPT_COMMAND/_powerline_tmux_set_pwd}" ||
|
||||
|
@ -5,13 +5,12 @@ import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from locale import getpreferredencoding
|
||||
|
||||
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.lib.shell import which
|
||||
from powerline.bindings.tmux import TmuxVersionInfo, run_tmux_command, get_tmux_version
|
||||
from powerline.lib.encoding import get_preferred_output_encoding
|
||||
|
||||
|
||||
CONFIG_FILE_NAME = re.compile(r'powerline_tmux_(?P<major>\d+)\.(?P<minor>\d+)(?P<suffix>[a-z]+)?(?:_(?P<mod>plus|minus))?\.conf')
|
||||
@ -84,7 +83,7 @@ def get_main_config(args):
|
||||
|
||||
def create_powerline_logger(args):
|
||||
config = get_main_config(args)
|
||||
common_config = finish_common_config(getpreferredencoding(), config['common'])
|
||||
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')
|
||||
|
||||
|
@ -8,7 +8,7 @@ from powerline import Powerline as PowerlineCore
|
||||
|
||||
|
||||
class Powerline(base._TextBox):
|
||||
def __init__(self, timeout=2, text=" ", width=bar.CALCULATED, **config):
|
||||
def __init__(self, timeout=2, text=' ', width=bar.CALCULATED, **config):
|
||||
base._TextBox.__init__(self, text, width, **config)
|
||||
self.timeout_add(timeout, self.update)
|
||||
self.powerline = PowerlineCore(ext='wm', renderer_module='pango_markup')
|
||||
|
@ -1,7 +1,7 @@
|
||||
if-shell 'test -z "$POWERLINE_CONFIG_COMMAND"' 'set-environment -g POWERLINE_CONFIG_COMMAND powerline-config'
|
||||
|
||||
# Don't version-check for this core functionality -- anything too old to
|
||||
# support these options likely won't work well with powerline
|
||||
# Don’t version-check for this core functionality -- anything too old to
|
||||
# support these options likely won’t work well with powerline
|
||||
set -g status on
|
||||
set -g status-utf8 on
|
||||
set -g status-interval 2
|
||||
|
@ -7,43 +7,127 @@ import codecs
|
||||
try:
|
||||
import vim
|
||||
except ImportError:
|
||||
vim = {}
|
||||
vim = object()
|
||||
|
||||
if not hasattr(vim, 'bindeval'):
|
||||
import json
|
||||
from powerline.lib.unicode import unicode
|
||||
|
||||
|
||||
try:
|
||||
vim_encoding = vim.eval('&encoding')
|
||||
except AttributeError:
|
||||
vim_encoding = 'utf-8'
|
||||
|
||||
|
||||
python_to_vim_types = {
|
||||
unicode: (
|
||||
lambda o: b'\'' + (o.translate({
|
||||
ord('\''): '\'\'',
|
||||
}).encode(vim_encoding)) + b'\''
|
||||
),
|
||||
bytes: (lambda o: b'\'' + o.replace(b'\'', b'\'\'') + b'\''),
|
||||
int: (str if str is bytes else (lambda o: unicode(o).encode('ascii'))),
|
||||
}
|
||||
python_to_vim_types[float] = python_to_vim_types[int]
|
||||
|
||||
|
||||
def python_to_vim(o):
|
||||
return python_to_vim_types[type(o)](o)
|
||||
|
||||
|
||||
if sys.version_info < (3,):
|
||||
def str_to_bytes(s):
|
||||
return s
|
||||
|
||||
def unicode_eval(expr):
|
||||
ret = vim.eval(expr)
|
||||
return ret.decode(vim_encoding, 'powerline_vim_strtrans_error')
|
||||
else:
|
||||
def str_to_bytes(s):
|
||||
return s.encode(vim_encoding)
|
||||
|
||||
def unicode_eval(expr):
|
||||
return vim.eval(expr)
|
||||
|
||||
|
||||
def safe_bytes_eval(expr):
|
||||
return bytes(bytearray((
|
||||
int(chunk) for chunk in (
|
||||
vim.eval(
|
||||
b'substitute(' + expr + b', ' +
|
||||
b'\'^.*$\', \'\\=join(map(range(len(submatch(0))), ' +
|
||||
b'"char2nr(submatch(0)[v:val])"))\', "")'
|
||||
).split()
|
||||
)
|
||||
)))
|
||||
|
||||
|
||||
def eval_bytes(expr):
|
||||
try:
|
||||
return str_to_bytes(vim.eval(expr))
|
||||
except UnicodeDecodeError:
|
||||
return safe_bytes_eval(expr)
|
||||
|
||||
|
||||
def eval_unicode(expr):
|
||||
try:
|
||||
return unicode_eval(expr)
|
||||
except UnicodeDecodeError:
|
||||
return safe_bytes_eval(expr).decode(vim_encoding, 'powerline_vim_strtrans_error')
|
||||
|
||||
|
||||
if hasattr(vim, 'bindeval'):
|
||||
rettype_func = {
|
||||
None: lambda f: f,
|
||||
'unicode': (
|
||||
lambda f: (
|
||||
lambda *args, **kwargs: (
|
||||
f(*args, **kwargs).decode(
|
||||
vim_encoding, 'powerline_vim_strtrans_error'
|
||||
))))
|
||||
}
|
||||
rettype_func['int'] = rettype_func['bytes'] = rettype_func[None]
|
||||
rettype_func['str'] = rettype_func['bytes'] if str is bytes else rettype_func['unicode']
|
||||
|
||||
def vim_get_func(f, rettype=None):
|
||||
'''Return a vim function binding.'''
|
||||
try:
|
||||
func = vim.bindeval('function("' + f + '")')
|
||||
if sys.version_info >= (3,) and rettype is str:
|
||||
return (lambda *args, **kwargs: func(*args, **kwargs).decode('utf-8', errors='replace'))
|
||||
return func
|
||||
except vim.error:
|
||||
return None
|
||||
else:
|
||||
return rettype_func[rettype](func)
|
||||
else:
|
||||
rettype_eval = {
|
||||
None: getattr(vim, 'eval', None),
|
||||
'int': lambda expr: int(vim.eval(expr)),
|
||||
'bytes': eval_bytes,
|
||||
'unicode': eval_unicode,
|
||||
}
|
||||
rettype_eval['str'] = rettype_eval[None]
|
||||
|
||||
class VimFunc(object):
|
||||
'''Evaluate a vim function using vim.eval().
|
||||
|
||||
This is a fallback class for older vim versions.
|
||||
'''
|
||||
__slots__ = ('f', 'rettype')
|
||||
__slots__ = ('f', 'eval')
|
||||
|
||||
def __init__(self, f, rettype=None):
|
||||
self.f = f
|
||||
self.rettype = rettype
|
||||
self.f = f.encode('utf-8')
|
||||
self.eval = rettype_eval[rettype]
|
||||
|
||||
def __call__(self, *args):
|
||||
r = vim.eval(self.f + '(' + json.dumps(args)[1:-1] + ')')
|
||||
if self.rettype:
|
||||
return self.rettype(r)
|
||||
return r
|
||||
return self.eval(self.f + b'(' + (b','.join((
|
||||
python_to_vim(o) for o in args
|
||||
))) + b')')
|
||||
|
||||
vim_get_func = VimFunc
|
||||
|
||||
|
||||
if type(vim) is object:
|
||||
vim_get_func = lambda *args, **kwargs: None
|
||||
|
||||
|
||||
_getbufvar = vim_get_func('getbufvar')
|
||||
|
||||
|
||||
@ -52,7 +136,10 @@ _getbufvar = vim_get_func('getbufvar')
|
||||
if hasattr(vim, 'vvars') and vim.vvars['version'] > 703:
|
||||
_vim_to_python_types = {
|
||||
getattr(vim, 'Dictionary', None) or type(vim.bindeval('{}')):
|
||||
lambda value: dict(((key, _vim_to_python(value[key])) for key in value.keys())),
|
||||
lambda value: dict((
|
||||
(_vim_to_python(k), _vim_to_python(v))
|
||||
for k, v in value.items()
|
||||
)),
|
||||
getattr(vim, 'List', None) or type(vim.bindeval('[]')):
|
||||
lambda value: [_vim_to_python(item) for item in value],
|
||||
getattr(vim, 'Function', None) or type(vim.bindeval('function("mode")')):
|
||||
@ -74,7 +161,7 @@ else:
|
||||
list: (lambda value: [_vim_to_python(i) for i in value]),
|
||||
}
|
||||
|
||||
_vim_exists = vim_get_func('exists', rettype=int)
|
||||
_vim_exists = vim_get_func('exists', rettype='int')
|
||||
|
||||
def vim_getvar(varname):
|
||||
varname = 'g:' + varname
|
||||
@ -102,7 +189,7 @@ else:
|
||||
if sys.version_info < (3,):
|
||||
getbufvar = _getbufvar
|
||||
else:
|
||||
_vim_to_python_types[bytes] = lambda value: value.decode('utf-8')
|
||||
_vim_to_python_types[bytes] = lambda value: value.decode(vim_encoding)
|
||||
|
||||
def getbufvar(*args):
|
||||
return _vim_to_python(_getbufvar(*args))
|
||||
@ -133,7 +220,7 @@ else:
|
||||
|
||||
def vim_setoption(option, value):
|
||||
vim.command('let &g:{option} = {value}'.format(
|
||||
option=option, value=json.encode(value)))
|
||||
option=option, value=python_to_vim(value)))
|
||||
|
||||
|
||||
if hasattr(vim, 'tabpages'):
|
||||
@ -256,29 +343,27 @@ class VimEnviron(object):
|
||||
|
||||
|
||||
if sys.version_info < (3,):
|
||||
def buffer_name(buf):
|
||||
return buf.name
|
||||
def buffer_name(segment_info):
|
||||
return segment_info['buffer'].name
|
||||
else:
|
||||
vim_bufname = vim_get_func('bufname')
|
||||
vim_bufname = vim_get_func('bufname', rettype='bytes')
|
||||
|
||||
def buffer_name(buf):
|
||||
def buffer_name(segment_info):
|
||||
try:
|
||||
name = buf.name
|
||||
name = segment_info['buffer'].name
|
||||
except UnicodeDecodeError:
|
||||
return vim_bufname(buf.number)
|
||||
return vim_bufname(segment_info['bufnr'])
|
||||
else:
|
||||
return name.encode('utf-8') if name else None
|
||||
return name.encode(segment_info['encoding']) if name else None
|
||||
|
||||
|
||||
vim_strtrans = vim_get_func('strtrans')
|
||||
vim_strtrans = vim_get_func('strtrans', rettype='unicode')
|
||||
|
||||
|
||||
def powerline_vim_strtrans_error(e):
|
||||
if not isinstance(e, UnicodeDecodeError):
|
||||
raise NotImplementedError
|
||||
# Assuming &encoding is utf-8 strtrans should not return anything but ASCII
|
||||
# under current circumstances
|
||||
text = vim_strtrans(e.object[e.start:e.end]).decode()
|
||||
text = vim_strtrans(e.object[e.start:e.end])
|
||||
return (text, e.end)
|
||||
|
||||
|
||||
|
@ -61,22 +61,24 @@ function s:rcmd(s)
|
||||
endfunction
|
||||
try
|
||||
let s:can_replace_pyeval = !exists('g:powerline_pyeval')
|
||||
call s:rcmd("try:")
|
||||
call s:rcmd(" powerline_appended_path = None")
|
||||
call s:rcmd(" try:")
|
||||
call s:rcmd(" ".s:import_cmd."")
|
||||
call s:rcmd(" except ImportError:")
|
||||
call s:rcmd(" import sys, vim")
|
||||
call s:rcmd(" powerline_appended_path = vim.eval('expand(\"<sfile>:h:h:h:h:h\")')")
|
||||
call s:rcmd(" sys.path.append(powerline_appended_path)")
|
||||
call s:rcmd(" ".s:import_cmd."")
|
||||
call s:rcmd(" import vim")
|
||||
call s:rcmd(" VimPowerline().setup(pyeval=vim.eval('s:pyeval'), pycmd=vim.eval('s:pycmd'), can_replace_pyeval=int(vim.eval('s:can_replace_pyeval')))")
|
||||
call s:rcmd(" del VimPowerline")
|
||||
call s:rcmd("except Exception:")
|
||||
call s:rcmd(" import traceback, sys")
|
||||
call s:rcmd(" traceback.print_exc(file=sys.stdout)")
|
||||
call s:rcmd(" raise")
|
||||
call s:rcmd('try:')
|
||||
call s:rcmd(' powerline_appended_path = None')
|
||||
call s:rcmd(' try:')
|
||||
call s:rcmd(' '.s:import_cmd.'')
|
||||
call s:rcmd(' except ImportError:')
|
||||
call s:rcmd(' import sys, vim')
|
||||
call s:rcmd(' powerline_appended_path = vim.eval("expand(\"<sfile>:h:h:h:h:h\")")')
|
||||
call s:rcmd(' sys.path.append(powerline_appended_path)')
|
||||
call s:rcmd(' '.s:import_cmd.'')
|
||||
call s:rcmd(' import vim')
|
||||
call s:rcmd(' powerline_instance = VimPowerline()')
|
||||
call s:rcmd(' powerline_instance.setup(pyeval=vim.eval("s:pyeval"), pycmd=vim.eval("s:pycmd"), can_replace_pyeval=int(vim.eval("s:can_replace_pyeval")))')
|
||||
call s:rcmd(' del VimPowerline')
|
||||
call s:rcmd(' del powerline_instance')
|
||||
call s:rcmd('except Exception:')
|
||||
call s:rcmd(' import traceback, sys')
|
||||
call s:rcmd(' traceback.print_exc(file=sys.stdout)')
|
||||
call s:rcmd(' raise')
|
||||
execute s:pycmd s:pystr
|
||||
unlet s:pystr
|
||||
let s:launched = 1
|
||||
@ -86,7 +88,7 @@ finally
|
||||
if !exists('s:launched')
|
||||
unlet s:pystr
|
||||
echohl ErrorMsg
|
||||
echomsg 'An error occurred while importing powerline package.'
|
||||
echomsg 'An error occurred while importing powerline module.'
|
||||
echomsg 'This could be caused by invalid sys.path setting,'
|
||||
echomsg 'or by an incompatible Python version (powerline requires'
|
||||
echomsg 'Python 2.6, 2.7 or 3.2 and later to work). Please consult'
|
||||
@ -97,48 +99,61 @@ finally
|
||||
echomsg 'should set g:powerline_pycmd to "py3" to make it load correctly.'
|
||||
endif
|
||||
echohl None
|
||||
call s:rcmd("def powerline_troubleshoot():")
|
||||
call s:rcmd(" import sys")
|
||||
call s:rcmd(" import vim")
|
||||
call s:rcmd(" if sys.version_info < (2, 6):")
|
||||
call s:rcmd(" print('Too old python version: ' + sys.version + ' (first supported is 2.6)')")
|
||||
call s:rcmd(" elif sys.version_info[0] == 3 and sys.version_info[1] < 2:")
|
||||
call s:rcmd(" print('Too old python 3 version: ' + sys.version + ' (first supported is 3.2)')")
|
||||
call s:rcmd(" try:")
|
||||
call s:rcmd(" import powerline")
|
||||
call s:rcmd(" except ImportError:")
|
||||
call s:rcmd(" print('Unable to import powerline, is it installed?')")
|
||||
call s:rcmd(" else:")
|
||||
call s:rcmd(" if not vim.eval('expand(\"<sfile>\")').startswith('/usr/'):")
|
||||
call s:rcmd(" import os")
|
||||
call s:rcmd(" powerline_dir = os.path.realpath(os.path.normpath(powerline.__file__))")
|
||||
call s:rcmd(" powerline_dir = os.path.dirname(powerline.__file__)")
|
||||
call s:rcmd(" this_dir = os.path.realpath(os.path.normpath(vim.eval('expand(\"<sfile>:p\")')))")
|
||||
call s:rcmd(" this_dir = os.path.dirname(this_dir)") " powerline/bindings/vim/plugin
|
||||
call s:rcmd(" this_dir = os.path.dirname(this_dir)") " powerline/bindings/vim
|
||||
call s:rcmd(" this_dir = os.path.dirname(this_dir)") " powerline/bindings
|
||||
call s:rcmd(" this_dir = os.path.dirname(this_dir)") " powerline
|
||||
call s:rcmd(" if os.path.basename(this_dir) != 'powerline':")
|
||||
call s:rcmd(" print('Check your installation:')")
|
||||
call s:rcmd(" print('this script is not in powerline[/bindings/vim/plugin] directory,')")
|
||||
call s:rcmd(" print('neither it is installed system-wide')")
|
||||
call s:rcmd(" real_powerline_dir = os.path.realpath(powerline_dir)")
|
||||
call s:rcmd(" real_this_dir = os.path.realpath(this_dir)")
|
||||
call s:rcmd(" this_dir_par = os.path.dirname(real_this_dir)")
|
||||
call s:rcmd(" powerline_appended_path = globals().get('powerline_appended_path')")
|
||||
call s:rcmd(" if powerline_appended_path is not None and this_dir_par != powerline_appended_path:")
|
||||
call s:rcmd(" print('Check your installation: this script is symlinked somewhere')")
|
||||
call s:rcmd(" print('where powerline is not present: {0!r} != {1!r}.'.format(")
|
||||
call s:rcmd(" real_this_dir, powerline_appended_path))")
|
||||
call s:rcmd(" elif real_powerline_dir != real_this_dir:")
|
||||
call s:rcmd(" print('It appears that you have two powerline versions installed:')")
|
||||
call s:rcmd(" print('one in ' + real_powerline_dir + ', other in ' + real_this_dir + '.')")
|
||||
call s:rcmd(" print('You should remove one of this. Check out troubleshooting section,')")
|
||||
call s:rcmd(" print('it contains some information about the alternatives.')")
|
||||
call s:rcmd("try:")
|
||||
call s:rcmd(" powerline_troubleshoot()")
|
||||
call s:rcmd("finally:")
|
||||
call s:rcmd(" del powerline_troubleshoot")
|
||||
call s:rcmd('def powerline_troubleshoot():')
|
||||
call s:rcmd(' import sys')
|
||||
call s:rcmd(' import vim')
|
||||
call s:rcmd(' if sys.version_info < (2, 6):')
|
||||
call s:rcmd(' print("Too old python version: " + sys.version + " (first supported is 2.6)")')
|
||||
call s:rcmd(' elif sys.version_info[0] == 3 and sys.version_info[1] < 2:')
|
||||
call s:rcmd(' print("Too old python 3 version: " + sys.version + " (first supported is 3.2)")')
|
||||
call s:rcmd(' try:')
|
||||
call s:rcmd(' import powerline')
|
||||
call s:rcmd(' except ImportError:')
|
||||
call s:rcmd(' print("Unable to import powerline, is it installed?")')
|
||||
call s:rcmd(' else:')
|
||||
call s:rcmd(' if not vim.eval(''expand("<sfile>")'').startswith("/usr/"):')
|
||||
call s:rcmd(' import os')
|
||||
call s:rcmd(' powerline_dir = os.path.realpath(os.path.normpath(powerline.__file__))')
|
||||
call s:rcmd(' powerline_dir = os.path.dirname(powerline.__file__)')
|
||||
call s:rcmd(' this_dir = os.path.realpath(os.path.normpath(vim.eval(''expand("<sfile>:p")'')))')
|
||||
call s:rcmd(' this_dir = os.path.dirname(this_dir)') " powerline/bindings/vim/plugin
|
||||
call s:rcmd(' this_dir = os.path.dirname(this_dir)') " powerline/bindings/vim
|
||||
call s:rcmd(' this_dir = os.path.dirname(this_dir)') " powerline/bindings
|
||||
call s:rcmd(' this_dir = os.path.dirname(this_dir)') " powerline
|
||||
call s:rcmd(' if os.path.basename(this_dir) != "powerline":')
|
||||
call s:rcmd(' print("Check your installation:")')
|
||||
call s:rcmd(' print("this script is not in powerline[/bindings/vim/plugin] directory,")')
|
||||
call s:rcmd(' print("neither it is installed system-wide")')
|
||||
call s:rcmd(' real_powerline_dir = os.path.realpath(powerline_dir)')
|
||||
call s:rcmd(' real_this_dir = os.path.realpath(this_dir)')
|
||||
call s:rcmd(' this_dir_par = os.path.dirname(real_this_dir)')
|
||||
call s:rcmd(' powerline_appended_path = globals().get("powerline_appended_path")')
|
||||
call s:rcmd(' if powerline_appended_path is not None and this_dir_par != powerline_appended_path:')
|
||||
call s:rcmd(' print("Check your installation: this script is symlinked somewhere")')
|
||||
call s:rcmd(' print("where powerline is not present: {0!r} != {1!r}.".format(')
|
||||
call s:rcmd(' real_this_dir, powerline_appended_path))')
|
||||
call s:rcmd(' elif real_powerline_dir != real_this_dir:')
|
||||
call s:rcmd(' print("It appears that you have two powerline versions installed:")')
|
||||
call s:rcmd(' print("one in " + real_powerline_dir + ", other in " + real_this_dir + ".")')
|
||||
call s:rcmd(' print("You should remove one of this. Check out troubleshooting section,")')
|
||||
call s:rcmd(' print("it contains some information about the alternatives.")')
|
||||
call s:rcmd(' try:')
|
||||
call s:rcmd(' from powerline.lint import check')
|
||||
call s:rcmd(' except ImportError:')
|
||||
call s:rcmd(' print("Failed to import powerline.lint.check, cannot run powerline-lint")')
|
||||
call s:rcmd(' else:')
|
||||
call s:rcmd(' try:')
|
||||
call s:rcmd(' paths = powerline_instance.get_config_paths()')
|
||||
call s:rcmd(' except NameError:')
|
||||
call s:rcmd(' pass')
|
||||
call s:rcmd(' else:')
|
||||
call s:rcmd(' from powerline.lint.markedjson.error import echoerr')
|
||||
call s:rcmd(' ee = lambda *args, **kwargs: echoerr(*args, stream=sys.stdout, **kwargs)')
|
||||
call s:rcmd(' check(paths=paths, echoerr=ee, require_ext="vim")')
|
||||
call s:rcmd('try:')
|
||||
call s:rcmd(' powerline_troubleshoot()')
|
||||
call s:rcmd('finally:')
|
||||
call s:rcmd(' del powerline_troubleshoot')
|
||||
execute s:pycmd s:pystr
|
||||
unlet s:pystr
|
||||
unlet s:pycmd
|
||||
|
@ -10,6 +10,8 @@ import zsh
|
||||
from powerline.shell import ShellPowerline
|
||||
from powerline.lib import parsedotval
|
||||
from powerline.lib.unicode import unicode
|
||||
from powerline.lib.encoding import (get_preferred_output_encoding,
|
||||
get_preferred_environment_encoding)
|
||||
|
||||
|
||||
used_powerlines = WeakValueDictionary()
|
||||
@ -65,7 +67,7 @@ class Args(object):
|
||||
|
||||
def string(s):
|
||||
if type(s) is bytes:
|
||||
return s.decode('utf-8', 'replace')
|
||||
return s.decode(get_preferred_environment_encoding(), 'replace')
|
||||
else:
|
||||
return str(s)
|
||||
|
||||
@ -129,6 +131,7 @@ class Prompt(object):
|
||||
|
||||
def __str__(self):
|
||||
zsh.eval('_POWERLINE_PARSER_STATE="${(%):-%_}"')
|
||||
zsh.eval('_POWERLINE_SHORTENED_PATH="${(%):-%~}"')
|
||||
segment_info = {
|
||||
'args': self.args,
|
||||
'environ': environ,
|
||||
@ -137,6 +140,8 @@ class Prompt(object):
|
||||
'parser_state': zsh.getvalue('_POWERLINE_PARSER_STATE'),
|
||||
'shortened_path': zsh.getvalue('_POWERLINE_SHORTENED_PATH'),
|
||||
}
|
||||
zsh.setvalue('_POWERLINE_PARSER_STATE', None)
|
||||
zsh.setvalue('_POWERLINE_SHORTENED_PATH', None)
|
||||
r = ''
|
||||
if self.above:
|
||||
for line in self.powerline.render_above_lines(
|
||||
@ -151,9 +156,9 @@ class Prompt(object):
|
||||
)
|
||||
if type(r) is not str:
|
||||
if type(r) is bytes:
|
||||
return r.decode('utf-8')
|
||||
return r.decode(get_preferred_output_encoding(), 'replace')
|
||||
else:
|
||||
return r.encode('utf-8')
|
||||
return r.encode(get_preferred_output_encoding(), 'replace')
|
||||
return r
|
||||
|
||||
def __del__(self):
|
||||
|
@ -36,7 +36,7 @@ _powerline_init_tmux_support() {
|
||||
}
|
||||
|
||||
chpwd_functions+=( _powerline_tmux_set_pwd )
|
||||
trap "_powerline_tmux_set_columns" SIGWINCH
|
||||
trap '_powerline_tmux_set_columns' SIGWINCH
|
||||
_powerline_tmux_set_columns
|
||||
_powerline_tmux_set_pwd
|
||||
fi
|
||||
@ -103,10 +103,6 @@ _powerline_set_jobnum() {
|
||||
_POWERLINE_JOBNUM=${(%):-%j}
|
||||
}
|
||||
|
||||
_powerline_set_shortened_path() {
|
||||
_POWERLINE_SHORTENED_PATH="${(%):-%~}"
|
||||
}
|
||||
|
||||
_powerline_update_counter() {
|
||||
zpython '_powerline.precmd()'
|
||||
}
|
||||
@ -115,13 +111,11 @@ _powerline_setup_prompt() {
|
||||
emulate -L zsh
|
||||
|
||||
for f in "${precmd_functions[@]}"; do
|
||||
if [[ "$f" = "_powerline_set_jobnum" ]]; then
|
||||
if [[ "$f" = '_powerline_set_jobnum' ]]; then
|
||||
return
|
||||
fi
|
||||
done
|
||||
precmd_functions+=( _powerline_set_jobnum )
|
||||
chpwd_functions+=( _powerline_set_shortened_path )
|
||||
_powerline_set_shortened_path
|
||||
|
||||
VIRTUAL_ENV_DISABLE_PROMPT=1
|
||||
|
||||
@ -144,7 +138,7 @@ _powerline_setup_prompt() {
|
||||
add_args+=' --last_exit_code=$?'
|
||||
add_args+=' --last_pipe_status="$pipestatus"'
|
||||
add_args+=' --renderer_arg="client_id=$$"'
|
||||
add_args+=' --renderer_arg="shortened_path=$_POWERLINE_SHORTENED_PATH"'
|
||||
add_args+=' --renderer_arg="shortened_path=${(%):-%~}"'
|
||||
add_args+=' --jobnum=$_POWERLINE_JOBNUM'
|
||||
local new_args_2=' --renderer_arg="parser_state=${(%%):-%_}"'
|
||||
new_args_2+=' --renderer_arg="local_theme=continuation"'
|
||||
|
@ -29,13 +29,16 @@
|
||||
"before": ""
|
||||
},
|
||||
|
||||
"powerline.segments.common.network_load": {
|
||||
"powerline.segments.common.net.network_load": {
|
||||
"args": {
|
||||
"recv_format": "DL {value:>8}",
|
||||
"sent_format": "UL {value:>8}"
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.now_playing": {
|
||||
"powerline.segments.common.net.hostname": {
|
||||
"before": "H "
|
||||
},
|
||||
"powerline.segments.common.players.now_playing": {
|
||||
"args": {
|
||||
"state_symbols": {
|
||||
"fallback": "",
|
||||
@ -45,25 +48,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.battery": {
|
||||
"powerline.segments.common.bat.battery": {
|
||||
"args": {
|
||||
"full_heart": "O",
|
||||
"empty_heart": "O"
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.uptime": {
|
||||
"powerline.segments.common.sys.uptime": {
|
||||
"before": "UP "
|
||||
},
|
||||
"powerline.segments.common.email_imap_alert": {
|
||||
"powerline.segments.common.mail.email_imap_alert": {
|
||||
"before": "MAIL "
|
||||
},
|
||||
"powerline.segments.common.virtualenv": {
|
||||
"powerline.segments.common.env.virtualenv": {
|
||||
"before": "(e) "
|
||||
},
|
||||
"powerline.segments.common.hostname": {
|
||||
"before": "H "
|
||||
},
|
||||
"powerline.segments.common.weather": {
|
||||
"powerline.segments.common.wthr.weather": {
|
||||
"args": {
|
||||
"icons": {
|
||||
"day": "DAY",
|
||||
@ -82,7 +82,7 @@
|
||||
"temp_format": "{temp:.0f} C"
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.fuzzy_time": {
|
||||
"powerline.segments.common.time.fuzzy_time": {
|
||||
"args": {
|
||||
"unicode_text": false
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
{
|
||||
"default_module": "powerline.segments.common",
|
||||
"segments": {
|
||||
"left": [
|
||||
{
|
||||
"function": "virtualenv",
|
||||
"function": "powerline.segments.common.env.virtualenv",
|
||||
"priority": 10
|
||||
},
|
||||
{
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"default_module": "powerline.segments.common",
|
||||
"segments": {
|
||||
"left": [
|
||||
{
|
||||
|
@ -28,13 +28,16 @@
|
||||
"before": "⌚ "
|
||||
},
|
||||
|
||||
"powerline.segments.common.network_load": {
|
||||
"powerline.segments.common.net.network_load": {
|
||||
"args": {
|
||||
"recv_format": "⬇ {value:>8}",
|
||||
"sent_format": "⬆ {value:>8}"
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.now_playing": {
|
||||
"powerline.segments.common.net.hostname": {
|
||||
"before": " "
|
||||
},
|
||||
"powerline.segments.common.players.now_playing": {
|
||||
"args": {
|
||||
"state_symbols": {
|
||||
"fallback": "♫",
|
||||
@ -44,25 +47,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.battery": {
|
||||
"powerline.segments.common.bat.battery": {
|
||||
"args": {
|
||||
"full_heart": "♥",
|
||||
"empty_heart": "♥"
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.uptime": {
|
||||
"powerline.segments.common.sys.uptime": {
|
||||
"before": "⇑ "
|
||||
},
|
||||
"powerline.segments.common.email_imap_alert": {
|
||||
"powerline.segments.common.mail.email_imap_alert": {
|
||||
"before": "✉ "
|
||||
},
|
||||
"powerline.segments.common.virtualenv": {
|
||||
"powerline.segments.common.env.virtualenv": {
|
||||
"before": "ⓔ "
|
||||
},
|
||||
"powerline.segments.common.hostname": {
|
||||
"before": " "
|
||||
},
|
||||
"powerline.segments.common.weather": {
|
||||
"powerline.segments.common.wthr.weather": {
|
||||
"args": {
|
||||
"icons": {
|
||||
"day": "〇",
|
||||
@ -80,7 +80,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.fuzzy_time": {
|
||||
"powerline.segments.common.time.fuzzy_time": {
|
||||
"args": {
|
||||
"unicode_text": true
|
||||
}
|
||||
|
@ -1,20 +1,19 @@
|
||||
{
|
||||
"default_module": "powerline.segments.common",
|
||||
"segments": {
|
||||
"left": [
|
||||
{
|
||||
"function": "powerline.segments.shell.mode"
|
||||
},
|
||||
{
|
||||
"function": "hostname",
|
||||
"function": "powerline.segments.common.net.hostname",
|
||||
"priority": 10
|
||||
},
|
||||
{
|
||||
"function": "user",
|
||||
"function": "powerline.segments.common.env.user",
|
||||
"priority": 30
|
||||
},
|
||||
{
|
||||
"function": "virtualenv",
|
||||
"function": "powerline.segments.common.env.virtualenv",
|
||||
"priority": 50
|
||||
},
|
||||
{
|
||||
@ -32,7 +31,7 @@
|
||||
"priority": 10
|
||||
},
|
||||
{
|
||||
"function": "branch",
|
||||
"function": "powerline.segments.common.vcs.branch",
|
||||
"priority": 40
|
||||
}
|
||||
]
|
||||
|
@ -1,21 +1,20 @@
|
||||
{
|
||||
"default_module": "powerline.segments.common",
|
||||
"segments": {
|
||||
"left": [
|
||||
{
|
||||
"function": "hostname",
|
||||
"function": "powerline.segments.common.net.hostname",
|
||||
"priority": 10
|
||||
},
|
||||
{
|
||||
"function": "user",
|
||||
"function": "powerline.segments.common.env.user",
|
||||
"priority": 30
|
||||
},
|
||||
{
|
||||
"function": "virtualenv",
|
||||
"function": "powerline.segments.common.env.virtualenv",
|
||||
"priority": 50
|
||||
},
|
||||
{
|
||||
"function": "branch",
|
||||
"function": "powerline.segments.common.vcs.branch",
|
||||
"priority": 40
|
||||
},
|
||||
{
|
||||
|
@ -1,20 +1,19 @@
|
||||
{
|
||||
"default_module": "powerline.segments.common",
|
||||
"segments": {
|
||||
"right": [
|
||||
{
|
||||
"function": "uptime",
|
||||
"function": "powerline.segments.common.sys.uptime",
|
||||
"priority": 50
|
||||
},
|
||||
{
|
||||
"function": "system_load",
|
||||
"function": "powerline.segments.common.sys.system_load",
|
||||
"priority": 50
|
||||
},
|
||||
{
|
||||
"function": "date"
|
||||
"function": "powerline.segments.common.time.date"
|
||||
},
|
||||
{
|
||||
"function": "date",
|
||||
"function": "powerline.segments.common.time.date",
|
||||
"name": "time",
|
||||
"args": {
|
||||
"format": "%H:%M",
|
||||
@ -22,7 +21,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"function": "hostname"
|
||||
"function": "powerline.segments.common.net.hostname"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -28,13 +28,16 @@
|
||||
"before": "⌚ "
|
||||
},
|
||||
|
||||
"powerline.segments.common.network_load": {
|
||||
"powerline.segments.common.net.network_load": {
|
||||
"args": {
|
||||
"recv_format": "⬇ {value:>8}",
|
||||
"sent_format": "⬆ {value:>8}"
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.now_playing": {
|
||||
"powerline.segments.common.net.hostname": {
|
||||
"before": "⌂ "
|
||||
},
|
||||
"powerline.segments.common.players.now_playing": {
|
||||
"args": {
|
||||
"state_symbols": {
|
||||
"fallback": "♫",
|
||||
@ -44,25 +47,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.battery": {
|
||||
"powerline.segments.common.bat.battery": {
|
||||
"args": {
|
||||
"full_heart": "♥",
|
||||
"empty_heart": "♥"
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.uptime": {
|
||||
"powerline.segments.common.sys.uptime": {
|
||||
"before": "⇑ "
|
||||
},
|
||||
"powerline.segments.common.email_imap_alert": {
|
||||
"powerline.segments.common.mail.email_imap_alert": {
|
||||
"before": "✉ "
|
||||
},
|
||||
"powerline.segments.common.virtualenv": {
|
||||
"powerline.segments.common.env.virtualenv": {
|
||||
"before": "ⓔ "
|
||||
},
|
||||
"powerline.segments.common.hostname": {
|
||||
"before": "⌂ "
|
||||
},
|
||||
"powerline.segments.common.weather": {
|
||||
"powerline.segments.common.wthr.weather": {
|
||||
"args": {
|
||||
"icons": {
|
||||
"day": "〇",
|
||||
@ -80,7 +80,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.fuzzy_time": {
|
||||
"powerline.segments.common.time.fuzzy_time": {
|
||||
"args": {
|
||||
"unicode_text": true
|
||||
}
|
||||
|
@ -28,13 +28,16 @@
|
||||
"before": ""
|
||||
},
|
||||
|
||||
"powerline.segments.common.network_load": {
|
||||
"powerline.segments.common.net.network_load": {
|
||||
"args": {
|
||||
"recv_format": "⇓ {value:>8}",
|
||||
"sent_format": "⇑ {value:>8}"
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.now_playing": {
|
||||
"powerline.segments.common.net.hostname": {
|
||||
"before": "⌂ "
|
||||
},
|
||||
"powerline.segments.common.players.now_playing": {
|
||||
"args": {
|
||||
"state_symbols": {
|
||||
"fallback": "♫",
|
||||
@ -44,25 +47,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.battery": {
|
||||
"powerline.segments.common.bat.battery": {
|
||||
"args": {
|
||||
"full_heart": "♥",
|
||||
"empty_heart": "♥"
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.uptime": {
|
||||
"powerline.segments.common.sys.uptime": {
|
||||
"before": "↑ "
|
||||
},
|
||||
"powerline.segments.common.email_imap_alert": {
|
||||
"powerline.segments.common.mail.email_imap_alert": {
|
||||
"before": "MAIL "
|
||||
},
|
||||
"powerline.segments.common.virtualenv": {
|
||||
"powerline.segments.common.env.virtualenv": {
|
||||
"before": "(e) "
|
||||
},
|
||||
"powerline.segments.common.hostname": {
|
||||
"before": "⌂ "
|
||||
},
|
||||
"powerline.segments.common.weather": {
|
||||
"powerline.segments.common.wthr.weather": {
|
||||
"args": {
|
||||
"icons": {
|
||||
"day": "DAY",
|
||||
@ -80,7 +80,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.fuzzy_time": {
|
||||
"powerline.segments.common.time.fuzzy_time": {
|
||||
"args": {
|
||||
"unicode_text": true
|
||||
}
|
||||
|
@ -29,13 +29,16 @@
|
||||
"before": ""
|
||||
},
|
||||
|
||||
"powerline.segments.common.network_load": {
|
||||
"powerline.segments.common.net.network_load": {
|
||||
"args": {
|
||||
"recv_format": "⇓{value:>8}",
|
||||
"sent_format": "⇑{value:>8}"
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.now_playing": {
|
||||
"powerline.segments.common.net.hostname": {
|
||||
"before": "⌂"
|
||||
},
|
||||
"powerline.segments.common.players.now_playing": {
|
||||
"args": {
|
||||
"state_symbols": {
|
||||
"fallback": "♫",
|
||||
@ -45,25 +48,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.battery": {
|
||||
"powerline.segments.common.bat.battery": {
|
||||
"args": {
|
||||
"full_heart": "♥",
|
||||
"empty_heart": "♥"
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.uptime": {
|
||||
"powerline.segments.common.sys.uptime": {
|
||||
"before": "↑"
|
||||
},
|
||||
"powerline.segments.common.email_imap_alert": {
|
||||
"powerline.segments.common.mail.email_imap_alert": {
|
||||
"before": "M "
|
||||
},
|
||||
"powerline.segments.common.virtualenv": {
|
||||
"powerline.segments.common.env.virtualenv": {
|
||||
"before": "E "
|
||||
},
|
||||
"powerline.segments.common.hostname": {
|
||||
"before": "⌂"
|
||||
},
|
||||
"powerline.segments.common.weather": {
|
||||
"powerline.segments.common.wthr.weather": {
|
||||
"args": {
|
||||
"icons": {
|
||||
"day": "D",
|
||||
@ -81,7 +81,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"powerline.segments.common.fuzzy_time": {
|
||||
"powerline.segments.common.time.fuzzy_time": {
|
||||
"args": {
|
||||
"unicode_text": true
|
||||
}
|
||||
|
@ -1,16 +1,15 @@
|
||||
{
|
||||
"default_module": "powerline.segments.common",
|
||||
"segments": {
|
||||
"right": [
|
||||
{
|
||||
"function": "weather",
|
||||
"function": "powerline.segments.common.wthr.weather",
|
||||
"priority": 50
|
||||
},
|
||||
{
|
||||
"function": "date"
|
||||
"function": "powerline.segments.common.time.date"
|
||||
},
|
||||
{
|
||||
"function": "date",
|
||||
"function": "powerline.segments.common.time.date",
|
||||
"name": "time",
|
||||
"args": {
|
||||
"format": "%H:%M",
|
||||
@ -18,7 +17,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"function": "email_imap_alert",
|
||||
"function": "powerline.segments.common.mail.email_imap_alert",
|
||||
"priority": 10,
|
||||
"args": {
|
||||
"username": "",
|
||||
|
@ -24,10 +24,11 @@ class RewriteResult(object):
|
||||
|
||||
|
||||
class IPythonPowerline(Powerline):
|
||||
def init(self):
|
||||
def init(self, **kwargs):
|
||||
super(IPythonPowerline, self).init(
|
||||
'ipython',
|
||||
use_daemon_threads=True
|
||||
use_daemon_threads=True,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def get_config_paths(self):
|
||||
|
@ -24,25 +24,25 @@ def print_cycles(objects, outstream=sys.stdout, show_progress=False):
|
||||
'''
|
||||
def print_path(path):
|
||||
for i, step in enumerate(path):
|
||||
# next "wraps around"
|
||||
# next “wraps around”
|
||||
next = path[(i + 1) % len(path)]
|
||||
|
||||
outstream.write(" %s -- " % str(type(step)))
|
||||
outstream.write(' %s -- ' % str(type(step)))
|
||||
written = False
|
||||
if isinstance(step, dict):
|
||||
for key, val in step.items():
|
||||
if val is next:
|
||||
outstream.write("[%s]" % repr(key))
|
||||
outstream.write('[%s]' % repr(key))
|
||||
written = True
|
||||
break
|
||||
if key is next:
|
||||
outstream.write("[key] = %s" % repr(val))
|
||||
outstream.write('[key] = %s' % repr(val))
|
||||
written = True
|
||||
break
|
||||
elif isinstance(step, (list, tuple)):
|
||||
for i, item in enumerate(step):
|
||||
if item is next:
|
||||
outstream.write("[%d]" % i)
|
||||
outstream.write('[%d]' % i)
|
||||
written = True
|
||||
elif getattr(type(step), '__getattribute__', None) in (object.__getattribute__, type.__getattribute__):
|
||||
for attr in chain(dir(step), getattr(step, '__dict__', ())):
|
||||
@ -55,18 +55,18 @@ def print_cycles(objects, outstream=sys.stdout, show_progress=False):
|
||||
break
|
||||
if not written:
|
||||
outstream.write(repr(step))
|
||||
outstream.write(" ->\n")
|
||||
outstream.write("\n")
|
||||
outstream.write(' ->\n')
|
||||
outstream.write('\n')
|
||||
|
||||
def recurse(obj, start, all, current_path):
|
||||
if show_progress:
|
||||
outstream.write("%d\r" % len(all))
|
||||
outstream.write('%d\r' % len(all))
|
||||
|
||||
all[id(obj)] = None
|
||||
|
||||
referents = gc.get_referents(obj)
|
||||
for referent in referents:
|
||||
# If we've found our way back to the start, this is
|
||||
# If we’ve found our way back to the start, this is
|
||||
# a cycle, so print it out
|
||||
if referent is start:
|
||||
try:
|
||||
@ -78,13 +78,13 @@ def print_cycles(objects, outstream=sys.stdout, show_progress=False):
|
||||
outstream.write('Cyclic reference: %i\n' % id(referent))
|
||||
print_path(current_path)
|
||||
|
||||
# Don't go back through the original list of objects, or
|
||||
# Don’t go back through the original list of objects, or
|
||||
# through temporary references to the object, since those
|
||||
# are just an artifact of the cycle detector itself.
|
||||
elif referent is objects or isinstance(referent, FrameType):
|
||||
continue
|
||||
|
||||
# We haven't seen this object before, so recurse
|
||||
# We haven’t seen this object before, so recurse
|
||||
elif id(referent) not in all:
|
||||
recurse(referent, start, all, current_path + (obj,))
|
||||
|
||||
|
73
powerline/lib/encoding.py
Normal file
73
powerline/lib/encoding.py
Normal file
@ -0,0 +1,73 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
|
||||
'''Encodings support
|
||||
|
||||
This is the only module from which functions obtaining encoding should be
|
||||
exported. Note: you should always care about errors= argument since it is not
|
||||
guaranteed that encoding returned by some function can encode/decode given
|
||||
string.
|
||||
|
||||
All functions in this module must always return a valid encoding. Most of them
|
||||
are not thread-safe.
|
||||
'''
|
||||
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import sys
|
||||
import locale
|
||||
|
||||
|
||||
def get_preferred_file_name_encoding():
|
||||
'''Get preferred file name encoding
|
||||
'''
|
||||
return (
|
||||
sys.getfilesystemencoding()
|
||||
or locale.getpreferredencoding()
|
||||
or 'utf-8'
|
||||
)
|
||||
|
||||
|
||||
def get_preferred_file_contents_encoding():
|
||||
'''Get encoding preferred for file contents
|
||||
'''
|
||||
return (
|
||||
locale.getpreferredencoding()
|
||||
or 'utf-8'
|
||||
)
|
||||
|
||||
|
||||
def get_preferred_output_encoding():
|
||||
'''Get encoding that should be used for printing strings
|
||||
|
||||
.. warning::
|
||||
Falls back to ASCII, so that output is most likely to be displayed
|
||||
correctly.
|
||||
'''
|
||||
return (
|
||||
locale.getlocale(locale.LC_MESSAGES)[1]
|
||||
or locale.getdefaultlocale()[1]
|
||||
or 'ascii'
|
||||
)
|
||||
|
||||
|
||||
def get_preferred_input_encoding():
|
||||
'''Get encoding that should be used for reading shell command output
|
||||
|
||||
.. warning::
|
||||
Falls back to latin1 so that function is less likely to throw as decoded
|
||||
output is primary searched for ASCII values.
|
||||
'''
|
||||
return (
|
||||
locale.getlocale(locale.LC_MESSAGES)[1]
|
||||
or locale.getdefaultlocale()[1]
|
||||
or 'latin1'
|
||||
)
|
||||
|
||||
|
||||
def get_preferred_environment_encoding():
|
||||
'''Get encoding that should be used for decoding environment variables
|
||||
'''
|
||||
return (
|
||||
locale.getpreferredencoding()
|
||||
or 'utf-8'
|
||||
)
|
@ -9,6 +9,8 @@ import struct
|
||||
|
||||
from ctypes.util import find_library
|
||||
|
||||
from powerline.lib.encoding import get_preferred_file_name_encoding
|
||||
|
||||
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
@ -39,27 +41,27 @@ def load_inotify():
|
||||
if not name:
|
||||
raise INotifyError('Cannot find C library')
|
||||
libc = ctypes.CDLL(name, use_errno=True)
|
||||
for function in ("inotify_add_watch", "inotify_init1", "inotify_rm_watch"):
|
||||
for function in ('inotify_add_watch', 'inotify_init1', 'inotify_rm_watch'):
|
||||
if not hasattr(libc, function):
|
||||
raise INotifyError('libc is too old')
|
||||
# inotify_init1()
|
||||
prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, use_errno=True)
|
||||
init1 = prototype(('inotify_init1', libc), ((1, "flags", 0),))
|
||||
init1 = prototype(('inotify_init1', libc), ((1, 'flags', 0),))
|
||||
|
||||
# inotify_add_watch()
|
||||
prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint32, use_errno=True)
|
||||
add_watch = prototype(('inotify_add_watch', libc), (
|
||||
(1, "fd"), (1, "pathname"), (1, "mask")))
|
||||
(1, 'fd'), (1, 'pathname'), (1, 'mask')))
|
||||
|
||||
# inotify_rm_watch()
|
||||
prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int, use_errno=True)
|
||||
rm_watch = prototype(('inotify_rm_watch', libc), (
|
||||
(1, "fd"), (1, "wd")))
|
||||
(1, 'fd'), (1, 'wd')))
|
||||
|
||||
# read()
|
||||
prototype = ctypes.CFUNCTYPE(ctypes.c_ssize_t, ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t, use_errno=True)
|
||||
read = prototype(('read', libc), (
|
||||
(1, "fd"), (1, "buf"), (1, "count")))
|
||||
(1, 'fd'), (1, 'buf'), (1, 'count')))
|
||||
_inotify = (init1, add_watch, rm_watch, read)
|
||||
return _inotify
|
||||
|
||||
@ -121,10 +123,8 @@ class INotify(object):
|
||||
raise INotifyError(os.strerror(ctypes.get_errno()))
|
||||
|
||||
self._buf = ctypes.create_string_buffer(5000)
|
||||
self.fenc = sys.getfilesystemencoding() or 'utf-8'
|
||||
self.fenc = get_preferred_file_name_encoding()
|
||||
self.hdr = struct.Struct(b'iIII')
|
||||
if self.fenc == 'ascii':
|
||||
self.fenc = 'utf-8'
|
||||
# We keep a reference to os to prevent it from being deleted
|
||||
# during interpreter shutdown, which would lead to errors in the
|
||||
# __del__ method
|
||||
@ -176,7 +176,7 @@ class INotify(object):
|
||||
pos += self.hdr.size
|
||||
name = None
|
||||
if get_name:
|
||||
name = raw[pos:pos + name_len].rstrip(b'\0').decode(self.fenc)
|
||||
name = raw[pos:pos + name_len].rstrip(b'\0')
|
||||
pos += name_len
|
||||
self.process_event(wd, mask, cookie, name)
|
||||
|
||||
|
@ -6,3 +6,13 @@ import os
|
||||
|
||||
def realpath(path):
|
||||
return os.path.abspath(os.path.realpath(path))
|
||||
|
||||
|
||||
def join(*components):
|
||||
if any((isinstance(p, bytes) for p in components)):
|
||||
return os.path.join(*[
|
||||
p if isinstance(p, bytes) else p.encode('ascii')
|
||||
for p in components
|
||||
])
|
||||
else:
|
||||
return os.path.join(*components)
|
||||
|
@ -5,9 +5,10 @@ import sys
|
||||
import os
|
||||
|
||||
from subprocess import Popen, PIPE
|
||||
from locale import getlocale, getdefaultlocale, LC_MESSAGES
|
||||
from functools import partial
|
||||
|
||||
from powerline.lib.encoding import get_preferred_input_encoding
|
||||
|
||||
|
||||
if sys.platform.startswith('win32'):
|
||||
# Prevent windows from launching consoles when calling commands
|
||||
@ -15,10 +16,6 @@ if sys.platform.startswith('win32'):
|
||||
Popen = partial(Popen, creationflags=0x08000000)
|
||||
|
||||
|
||||
def _get_shell_encoding():
|
||||
return getlocale(LC_MESSAGES)[1] or getdefaultlocale()[1] or 'utf-8'
|
||||
|
||||
|
||||
def run_cmd(pl, cmd, stdin=None):
|
||||
'''Run command and return its stdout, stripped
|
||||
|
||||
@ -38,7 +35,7 @@ def run_cmd(pl, cmd, stdin=None):
|
||||
return None
|
||||
else:
|
||||
stdout, err = p.communicate(stdin)
|
||||
stdout = stdout.decode(_get_shell_encoding())
|
||||
stdout = stdout.decode(get_preferred_input_encoding())
|
||||
return stdout.strip()
|
||||
|
||||
|
||||
@ -56,7 +53,7 @@ def readlines(cmd, cwd):
|
||||
Working directory of the command which will be run.
|
||||
'''
|
||||
p = Popen(cmd, shell=False, stdout=PIPE, stderr=PIPE, cwd=cwd)
|
||||
encoding = _get_shell_encoding()
|
||||
encoding = get_preferred_input_encoding()
|
||||
p.stderr.close()
|
||||
with p.stdout:
|
||||
for line in p.stdout:
|
||||
@ -69,15 +66,14 @@ except ImportError:
|
||||
# shutil.which was added in python-3.3. Here is what was added:
|
||||
# Lib/shutil.py, commit 5abe28a9c8fe701ba19b1db5190863384e96c798
|
||||
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
||||
"""Given a command, mode, and a PATH string, return the path which
|
||||
conforms to the given mode on the PATH, or None if there is no such
|
||||
'''Given a command, mode, and a PATH string, return the path which
|
||||
conforms to the given mode on the PATH, or None if there is no such
|
||||
file.
|
||||
|
||||
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
|
||||
of os.environ.get("PATH"), or can be overridden with a custom search
|
||||
``mode`` defaults to os.F_OK | os.X_OK. ``path`` defaults to the result
|
||||
of ``os.environ.get('PATH')``, or can be overridden with a custom search
|
||||
path.
|
||||
|
||||
"""
|
||||
'''
|
||||
# Check that a given file can be accessed with the correct mode.
|
||||
# Additionally check that `file` is not a directory, as on Windows
|
||||
# directories pass the os.access check.
|
||||
@ -88,7 +84,7 @@ except ImportError:
|
||||
and not os.path.isdir(fn)
|
||||
)
|
||||
|
||||
# If we're given a path with a directory part, look it up directly rather
|
||||
# If we’re given a path with a directory part, look it up directly rather
|
||||
# than referring to PATH directories. This includes checking relative to the
|
||||
# current directory, e.g. ./script
|
||||
if os.path.dirname(cmd):
|
||||
@ -97,20 +93,20 @@ except ImportError:
|
||||
return None
|
||||
|
||||
if path is None:
|
||||
path = os.environ.get("PATH", os.defpath)
|
||||
path = os.environ.get('PATH', os.defpath)
|
||||
if not path:
|
||||
return None
|
||||
path = path.split(os.pathsep)
|
||||
|
||||
if sys.platform == "win32":
|
||||
if sys.platform == 'win32':
|
||||
# The current directory takes precedence on Windows.
|
||||
if os.curdir not in path:
|
||||
path.insert(0, os.curdir)
|
||||
|
||||
# PATHEXT is necessary to check on Windows.
|
||||
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
|
||||
pathext = os.environ.get('PATHEXT', '').split(os.pathsep)
|
||||
# See if the given file matches any of the expected path extensions.
|
||||
# This will allow us to short circuit when given "python.exe".
|
||||
# This will allow us to short circuit when given 'python.exe'.
|
||||
# If it does match, only test that one, otherwise we have to try
|
||||
# others.
|
||||
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
|
||||
@ -118,7 +114,7 @@ except ImportError:
|
||||
else:
|
||||
files = [cmd + ext for ext in pathext]
|
||||
else:
|
||||
# On other platforms you don't have things like PATHEXT to tell you
|
||||
# On other platforms you don’t have things like PATHEXT to tell you
|
||||
# what file suffixes are executable, so just pass on cmd as-is.
|
||||
files = [cmd]
|
||||
|
||||
|
@ -102,7 +102,7 @@ class ThreadedSegment(Segment, MultiRunnedThread):
|
||||
def shutdown(self):
|
||||
self.shutdown_event.set()
|
||||
if self.daemon and self.is_alive():
|
||||
# Give the worker thread a chance to shutdown, but don't block for
|
||||
# Give the worker thread a chance to shutdown, but don’t block for
|
||||
# too long
|
||||
self.join(0.01)
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
from locale import getpreferredencoding
|
||||
import sys
|
||||
import codecs
|
||||
|
||||
from powerline.lib.encoding import get_preferred_output_encoding
|
||||
|
||||
|
||||
try:
|
||||
@ -25,6 +28,62 @@ def u(s):
|
||||
return unicode(s, 'utf-8')
|
||||
|
||||
|
||||
if sys.version_info < (3,):
|
||||
def tointiter(s):
|
||||
'''Convert a byte string to the sequence of integers
|
||||
'''
|
||||
return (ord(c) for c in s)
|
||||
else:
|
||||
def tointiter(s):
|
||||
'''Convert a byte string to the sequence of integers
|
||||
'''
|
||||
return iter(s)
|
||||
|
||||
|
||||
def powerline_decode_error(e):
|
||||
if not isinstance(e, UnicodeDecodeError):
|
||||
raise NotImplementedError
|
||||
return (''.join((
|
||||
'<{0:02X}>'.format(c)
|
||||
for c in tointiter(e.object[e.start:e.end])
|
||||
)), e.end)
|
||||
|
||||
|
||||
codecs.register_error('powerline_decode_error', powerline_decode_error)
|
||||
|
||||
|
||||
last_swe_idx = 0
|
||||
|
||||
|
||||
def register_strwidth_error(strwidth):
|
||||
global last_swe_idx
|
||||
last_swe_idx += 1
|
||||
|
||||
def powerline_encode_strwidth_error(e):
|
||||
if not isinstance(e, UnicodeEncodeError):
|
||||
raise NotImplementedError
|
||||
return ('?' * strwidth(e.object[e.start:e.end]), e.end)
|
||||
|
||||
ename = 'powerline_encode_strwidth_error_{0}'.format(last_swe_idx)
|
||||
codecs.register_error(ename, powerline_encode_strwidth_error)
|
||||
return ename
|
||||
|
||||
|
||||
def out_u(s):
|
||||
'''Return unicode string suitable for displaying
|
||||
|
||||
Unlike other functions assumes get_preferred_output_encoding() first. Unlike
|
||||
u() does not throw exceptions for invalid unicode strings. Unlike
|
||||
safe_unicode() does throw an exception if object is not a string.
|
||||
'''
|
||||
if isinstance(s, unicode):
|
||||
return s
|
||||
elif isinstance(s, bytes):
|
||||
return unicode(s, get_preferred_output_encoding(), 'powerline_decode_error')
|
||||
else:
|
||||
raise TypeError('Expected unicode or bytes instance, got {0}'.format(repr(type(s))))
|
||||
|
||||
|
||||
def safe_unicode(s):
|
||||
'''Return unicode instance without raising an exception.
|
||||
|
||||
@ -33,7 +92,7 @@ def safe_unicode(s):
|
||||
* UTF-8 string
|
||||
* Object with __str__() or __repr__() method that returns UTF-8 string or
|
||||
unicode object (depending on python version)
|
||||
* String in locale.getpreferredencoding() encoding
|
||||
* String in powerline.lib.encoding.get_preferred_output_encoding() encoding
|
||||
* If everything failed use safe_unicode on last exception with which
|
||||
everything failed
|
||||
'''
|
||||
@ -46,7 +105,7 @@ def safe_unicode(s):
|
||||
except TypeError:
|
||||
return unicode(str(s), 'utf-8')
|
||||
except UnicodeDecodeError:
|
||||
return unicode(s, getpreferredencoding())
|
||||
return unicode(s, get_preferred_output_encoding())
|
||||
except Exception as e:
|
||||
return safe_unicode(e)
|
||||
|
||||
|
@ -8,6 +8,8 @@ from threading import Lock
|
||||
from collections import defaultdict
|
||||
|
||||
from powerline.lib.watcher import create_tree_watcher
|
||||
from powerline.lib.unicode import out_u
|
||||
from powerline.lib.path import join
|
||||
|
||||
|
||||
def generate_directories(path):
|
||||
@ -75,10 +77,10 @@ def get_branch_name(directory, config_file, get_func, create_watcher):
|
||||
raise
|
||||
# Config file does not exist (happens for mercurial)
|
||||
if config_file not in branch_name_cache:
|
||||
branch_name_cache[config_file] = get_func(directory, config_file)
|
||||
branch_name_cache[config_file] = out_u(get_func(directory, config_file))
|
||||
if changed:
|
||||
# Config file has changed or was not tracked
|
||||
branch_name_cache[config_file] = get_func(directory, config_file)
|
||||
branch_name_cache[config_file] = out_u(get_func(directory, config_file))
|
||||
return branch_name_cache[config_file]
|
||||
|
||||
|
||||
@ -96,7 +98,7 @@ class FileStatusCache(dict):
|
||||
if nparent == parent:
|
||||
break
|
||||
parent = nparent
|
||||
ignore_files.add(os.path.join(parent, ignore_file_name))
|
||||
ignore_files.add(join(parent, ignore_file_name))
|
||||
for f in extra_ignore_files:
|
||||
ignore_files.add(f)
|
||||
self.keypath_ignore_map[keypath] = ignore_files
|
||||
@ -120,7 +122,7 @@ file_status_cache = FileStatusCache()
|
||||
|
||||
def get_file_status(directory, dirstate_file, file_path, ignore_file_name, get_func, create_watcher, extra_ignore_files=()):
|
||||
global file_status_cache
|
||||
keypath = file_path if os.path.isabs(file_path) else os.path.join(directory, file_path)
|
||||
keypath = file_path if os.path.isabs(file_path) else join(directory, file_path)
|
||||
file_status_cache.update_maps(keypath, directory, dirstate_file, ignore_file_name, extra_ignore_files)
|
||||
|
||||
with file_status_lock:
|
||||
@ -218,9 +220,15 @@ vcs_props = (
|
||||
)
|
||||
|
||||
|
||||
vcs_props_bytes = [
|
||||
(vcs, vcs_dir.encode('ascii'), check)
|
||||
for vcs, vcs_dir, check in vcs_props
|
||||
]
|
||||
|
||||
|
||||
def guess(path, create_watcher):
|
||||
for directory in generate_directories(path):
|
||||
for vcs, vcs_dir, check in vcs_props:
|
||||
for vcs, vcs_dir, check in (vcs_props_bytes if isinstance(path, bytes) else vcs_props):
|
||||
repo_dir = os.path.join(directory, vcs_dir)
|
||||
if check(repo_dir):
|
||||
if os.path.isdir(repo_dir) and not os.access(repo_dir, os.X_OK):
|
||||
@ -245,7 +253,7 @@ def debug():
|
||||
'''Test run guess(), repo.branch() and repo.status()
|
||||
|
||||
To use::
|
||||
python -c "from powerline.lib.vcs import debug; debug()" some_file_to_watch.
|
||||
python -c 'from powerline.lib.vcs import debug; debug()' some_file_to_watch.
|
||||
'''
|
||||
import sys
|
||||
dest = sys.argv[-1]
|
||||
|
@ -1,7 +1,6 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
@ -10,12 +9,14 @@ from io import StringIO
|
||||
from bzrlib import (workingtree, status, library_state, trace, ui)
|
||||
|
||||
from powerline.lib.vcs import get_branch_name, get_file_status
|
||||
from powerline.lib.path import join
|
||||
from powerline.lib.encoding import get_preferred_file_contents_encoding
|
||||
|
||||
|
||||
class CoerceIO(StringIO):
|
||||
def write(self, arg):
|
||||
if isinstance(arg, bytes):
|
||||
arg = arg.decode('utf-8', 'replace')
|
||||
arg = arg.decode(get_preferred_file_contents_encoding(), 'replace')
|
||||
return super(CoerceIO, self).write(arg)
|
||||
|
||||
|
||||
@ -29,7 +30,7 @@ def branch_name_from_config_file(directory, config_file):
|
||||
for line in f:
|
||||
m = nick_pat.match(line)
|
||||
if m is not None:
|
||||
ans = m.group(1).strip().decode('utf-8', 'replace')
|
||||
ans = m.group(1).strip().decode(get_preferred_file_contents_encoding(), 'replace')
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
@ -41,8 +42,6 @@ state = None
|
||||
|
||||
class Repository(object):
|
||||
def __init__(self, directory, create_watcher):
|
||||
if isinstance(directory, bytes):
|
||||
directory = directory.decode(sys.getfilesystemencoding() or sys.getdefaultencoding() or 'utf-8')
|
||||
self.directory = os.path.abspath(directory)
|
||||
self.create_watcher = create_watcher
|
||||
|
||||
@ -51,8 +50,8 @@ class Repository(object):
|
||||
|
||||
Without file argument: returns status of the repository:
|
||||
|
||||
:"D?": dirty (tracked modified files: added, removed, deleted, modified),
|
||||
:"?U": untracked-dirty (added, but not tracked files)
|
||||
:'D?': dirty (tracked modified files: added, removed, deleted, modified),
|
||||
:'?U': untracked-dirty (added, but not tracked files)
|
||||
:None: clean (status is empty)
|
||||
|
||||
With file argument: returns status of this file: The status codes are
|
||||
@ -61,7 +60,7 @@ class Repository(object):
|
||||
if path is not None:
|
||||
return get_file_status(
|
||||
directory=self.directory,
|
||||
dirstate_file=os.path.join(self.directory, '.bzr', 'checkout', 'dirstate'),
|
||||
dirstate_file=join(self.directory, '.bzr', 'checkout', 'dirstate'),
|
||||
file_path=path,
|
||||
ignore_file_name='.bzrignore',
|
||||
get_func=self.do_status,
|
||||
@ -100,7 +99,7 @@ class Repository(object):
|
||||
return ans if ans.strip() else None
|
||||
|
||||
def branch(self):
|
||||
config_file = os.path.join(self.directory, '.bzr', 'branch', 'branch.conf')
|
||||
config_file = join(self.directory, '.bzr', 'branch', 'branch.conf')
|
||||
return get_branch_name(
|
||||
directory=self.directory,
|
||||
config_file=config_file,
|
||||
|
@ -2,11 +2,14 @@
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
from powerline.lib.vcs import get_branch_name, get_file_status
|
||||
from powerline.lib.shell import readlines
|
||||
from powerline.lib.path import join
|
||||
from powerline.lib.encoding import (get_preferred_file_name_encoding,
|
||||
get_preferred_file_contents_encoding)
|
||||
from powerline.lib.shell import which
|
||||
|
||||
|
||||
_ref_pat = re.compile(br'ref:\s*refs/heads/(.+)')
|
||||
@ -20,20 +23,22 @@ def branch_name_from_config_file(directory, config_file):
|
||||
return os.path.basename(directory)
|
||||
m = _ref_pat.match(raw)
|
||||
if m is not None:
|
||||
return m.group(1).decode('utf-8', 'replace')
|
||||
return m.group(1).decode(get_preferred_file_contents_encoding(), 'replace')
|
||||
return raw[:7]
|
||||
|
||||
|
||||
def git_directory(directory):
|
||||
path = os.path.join(directory, '.git')
|
||||
path = join(directory, '.git')
|
||||
if os.path.isfile(path):
|
||||
with open(path, 'rb') as f:
|
||||
raw = f.read()
|
||||
if not raw.startswith(b'gitdir: '):
|
||||
raise IOError('invalid gitfile format')
|
||||
raw = raw[8:].decode(sys.getfilesystemencoding() or 'utf-8')
|
||||
if raw[-1] == '\n':
|
||||
raw = raw[8:]
|
||||
if raw[-1:] == b'\n':
|
||||
raw = raw[:-1]
|
||||
if not isinstance(path, bytes):
|
||||
raw = raw.decode(get_preferred_file_name_encoding())
|
||||
if not raw:
|
||||
raise IOError('no path in gitfile')
|
||||
return os.path.abspath(os.path.join(directory, raw))
|
||||
@ -59,28 +64,28 @@ class GitRepository(object):
|
||||
:None: repository clean
|
||||
|
||||
With file argument: returns status of this file. Output is
|
||||
equivalent to the first two columns of "git status --porcelain"
|
||||
equivalent to the first two columns of ``git status --porcelain``
|
||||
(except for merge statuses as they are not supported by libgit2).
|
||||
'''
|
||||
if path:
|
||||
gitd = git_directory(self.directory)
|
||||
# We need HEAD as without it using fugitive to commit causes the
|
||||
# current file's status (and only the current file) to not be updated
|
||||
# current file’s status (and only the current file) to not be updated
|
||||
# for some reason I cannot be bothered to figure out.
|
||||
return get_file_status(
|
||||
directory=self.directory,
|
||||
dirstate_file=os.path.join(gitd, 'index'),
|
||||
dirstate_file=join(gitd, 'index'),
|
||||
file_path=path,
|
||||
ignore_file_name='.gitignore',
|
||||
get_func=self.do_status,
|
||||
create_watcher=self.create_watcher,
|
||||
extra_ignore_files=tuple(os.path.join(gitd, x) for x in ('logs/HEAD', 'info/exclude')),
|
||||
extra_ignore_files=tuple(join(gitd, x) for x in ('logs/HEAD', 'info/exclude')),
|
||||
)
|
||||
return self.do_status(self.directory, path)
|
||||
|
||||
def branch(self):
|
||||
directory = git_directory(self.directory)
|
||||
head = os.path.join(directory, 'HEAD')
|
||||
head = join(directory, 'HEAD')
|
||||
return get_branch_name(
|
||||
directory=directory,
|
||||
config_file=head,
|
||||
@ -151,6 +156,11 @@ try:
|
||||
return r if r != ' ' else None
|
||||
except ImportError:
|
||||
class Repository(GitRepository):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if not which('git'):
|
||||
raise OSError('git executable is not available')
|
||||
super(Repository, self).__init__(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def ignore_event(path, name):
|
||||
# Ignore changes to the index.lock file, since they happen
|
||||
|
@ -6,13 +6,15 @@ import os
|
||||
from mercurial import hg, ui, match
|
||||
|
||||
from powerline.lib.vcs import get_branch_name, get_file_status
|
||||
from powerline.lib.path import join
|
||||
from powerline.lib.encoding import get_preferred_file_contents_encoding
|
||||
|
||||
|
||||
def branch_name_from_config_file(directory, config_file):
|
||||
try:
|
||||
with open(config_file, 'rb') as f:
|
||||
raw = f.read()
|
||||
return raw.decode('utf-8', 'replace').strip()
|
||||
return raw.decode(get_preferred_file_contents_encoding(), 'replace').strip()
|
||||
except Exception:
|
||||
return 'default'
|
||||
|
||||
@ -39,18 +41,18 @@ class Repository(object):
|
||||
|
||||
Without file argument: returns status of the repository:
|
||||
|
||||
:"D?": dirty (tracked modified files: added, removed, deleted, modified),
|
||||
:"?U": untracked-dirty (added, but not tracked files)
|
||||
:'D?': dirty (tracked modified files: added, removed, deleted, modified),
|
||||
:'?U': untracked-dirty (added, but not tracked files)
|
||||
:None: clean (status is empty)
|
||||
|
||||
With file argument: returns status of this file: "M"odified, "A"dded,
|
||||
"R"emoved, "D"eleted (removed from filesystem, but still tracked),
|
||||
"U"nknown, "I"gnored, (None)Clean.
|
||||
With file argument: returns status of this file: `M`odified, `A`dded,
|
||||
`R`emoved, `D`eleted (removed from filesystem, but still tracked),
|
||||
`U`nknown, `I`gnored, (None)Clean.
|
||||
'''
|
||||
if path:
|
||||
return get_file_status(
|
||||
directory=self.directory,
|
||||
dirstate_file=os.path.join(self.directory, '.hg', 'dirstate'),
|
||||
dirstate_file=join(self.directory, '.hg', 'dirstate'),
|
||||
file_path=path,
|
||||
ignore_file_name='.hgignore',
|
||||
get_func=self.do_status,
|
||||
@ -75,7 +77,7 @@ class Repository(object):
|
||||
return self.repo_statuses_str[resulting_status]
|
||||
|
||||
def branch(self):
|
||||
config_file = os.path.join(self.directory, '.hg', 'branch')
|
||||
config_file = join(self.directory, '.hg', 'branch')
|
||||
return get_branch_name(
|
||||
directory=self.directory,
|
||||
config_file=config_file,
|
||||
|
@ -247,6 +247,8 @@ class INotifyTreeWatcher(INotify):
|
||||
if mask & self.CREATE:
|
||||
# A new sub-directory might have been created, monitor it.
|
||||
try:
|
||||
if not isinstance(path, bytes):
|
||||
name = name.decode(self.fenc)
|
||||
self.add_watch(os.path.join(path, name))
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
|
@ -7,8 +7,10 @@ from collections import defaultdict
|
||||
from threading import RLock
|
||||
from functools import partial
|
||||
from threading import Thread
|
||||
from errno import ENOENT
|
||||
|
||||
from powerline.lib.path import realpath
|
||||
from powerline.lib.encoding import get_preferred_file_name_encoding
|
||||
|
||||
|
||||
class UvNotFound(NotImplementedError):
|
||||
@ -55,15 +57,24 @@ def start_uv_thread():
|
||||
return _uv_thread.uv_loop
|
||||
|
||||
|
||||
def normpath(path, fenc):
|
||||
path = realpath(path)
|
||||
if isinstance(path, bytes):
|
||||
return path.decode(fenc)
|
||||
else:
|
||||
return path
|
||||
|
||||
|
||||
class UvWatcher(object):
|
||||
def __init__(self):
|
||||
import_pyuv()
|
||||
self.watches = {}
|
||||
self.lock = RLock()
|
||||
self.loop = start_uv_thread()
|
||||
self.fenc = get_preferred_file_name_encoding()
|
||||
|
||||
def watch(self, path):
|
||||
path = realpath(path)
|
||||
path = normpath(path, self.fenc)
|
||||
with self.lock:
|
||||
if path not in self.watches:
|
||||
try:
|
||||
@ -76,12 +87,12 @@ class UvWatcher(object):
|
||||
except pyuv.error.FSEventError as e:
|
||||
code = e.args[0]
|
||||
if code == pyuv.errno.UV_ENOENT:
|
||||
raise OSError('No such file or directory: ' + path)
|
||||
raise OSError(ENOENT, 'No such file or directory: ' + path)
|
||||
else:
|
||||
raise
|
||||
|
||||
def unwatch(self, path):
|
||||
path = realpath(path)
|
||||
path = normpath(path, self.fenc)
|
||||
with self.lock:
|
||||
try:
|
||||
watch = self.watches.pop(path)
|
||||
@ -91,7 +102,7 @@ class UvWatcher(object):
|
||||
|
||||
def is_watching(self, path):
|
||||
with self.lock:
|
||||
return realpath(path) in self.watches
|
||||
return normpath(path, self.fenc) in self.watches
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
@ -121,7 +132,7 @@ class UvFileWatcher(UvWatcher):
|
||||
self.events.pop(path, None)
|
||||
|
||||
def __call__(self, path):
|
||||
path = realpath(path)
|
||||
path = normpath(path, self.fenc)
|
||||
with self.lock:
|
||||
events = self.events.pop(path, None)
|
||||
if events:
|
||||
@ -138,14 +149,15 @@ class UvTreeWatcher(UvWatcher):
|
||||
def __init__(self, basedir, ignore_event=None):
|
||||
super(UvTreeWatcher, self).__init__()
|
||||
self.ignore_event = ignore_event or (lambda path, name: False)
|
||||
self.basedir = realpath(basedir)
|
||||
self.basedir = normpath(basedir, self.fenc)
|
||||
self.modified = True
|
||||
self.watch_directory(self.basedir)
|
||||
|
||||
def watch_directory(self, path):
|
||||
os.path.walk(realpath(path), self.watch_one_directory, None)
|
||||
for root, dirs, files in os.walk(normpath(path, self.fenc)):
|
||||
self.watch_one_directory(root)
|
||||
|
||||
def watch_one_directory(self, arg, dirname, fnames):
|
||||
def watch_one_directory(self, dirname):
|
||||
try:
|
||||
self.watch(dirname)
|
||||
except OSError:
|
||||
|
File diff suppressed because it is too large
Load Diff
706
powerline/lint/checks.py
Normal file
706
powerline/lint/checks.py
Normal file
@ -0,0 +1,706 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
|
||||
from powerline.lib.threaded import ThreadedSegment
|
||||
from powerline.lib.unicode import unicode
|
||||
from powerline.lint.markedjson.markedvalue import MarkedUnicode
|
||||
from powerline.lint.markedjson.error import DelayedEchoErr, Mark
|
||||
from powerline.lint.selfcheck import havemarks
|
||||
from powerline.lint.context import JStr, list_themes
|
||||
from powerline.lint.imp import WithPath, import_function, import_segment
|
||||
from powerline.lint.spec import Spec
|
||||
from powerline.lint.inspect import getconfigargspec
|
||||
|
||||
|
||||
list_sep = JStr(', ')
|
||||
|
||||
|
||||
generic_keys = set((
|
||||
'exclude_modes', 'include_modes',
|
||||
'exclude_function', 'include_function',
|
||||
'width', 'align',
|
||||
'name',
|
||||
'draw_soft_divider', 'draw_hard_divider',
|
||||
'priority',
|
||||
'after', 'before',
|
||||
'display'
|
||||
))
|
||||
type_keys = {
|
||||
'function': set(('function', 'args', 'draw_inner_divider')),
|
||||
'string': set(('contents', 'type', 'highlight_group', 'divider_highlight_group')),
|
||||
'segment_list': set(('function', 'segments', 'args', 'type')),
|
||||
}
|
||||
required_keys = {
|
||||
'function': set(('function',)),
|
||||
'string': set(()),
|
||||
'segment_list': set(('function', 'segments',)),
|
||||
}
|
||||
highlight_keys = set(('highlight_group', 'name'))
|
||||
|
||||
|
||||
def get_function_strings(function_name, context, ext):
|
||||
if '.' in function_name:
|
||||
module, function_name = function_name.rpartition('.')[::2]
|
||||
else:
|
||||
module = context[0][1].get(
|
||||
'default_module', MarkedUnicode('powerline.segments.' + ext, None))
|
||||
return module, function_name
|
||||
|
||||
|
||||
def check_matcher_func(ext, match_name, data, context, echoerr):
|
||||
havemarks(match_name)
|
||||
import_paths = [os.path.expanduser(path) for path in context[0][1].get('common', {}).get('paths', [])]
|
||||
|
||||
match_module, separator, match_function = match_name.rpartition('.')
|
||||
if not separator:
|
||||
match_module = 'powerline.matchers.{0}'.format(ext)
|
||||
match_function = match_name
|
||||
with WithPath(import_paths):
|
||||
try:
|
||||
func = getattr(__import__(str(match_module), fromlist=[str(match_function)]), str(match_function))
|
||||
except ImportError:
|
||||
echoerr(context='Error while loading matcher functions',
|
||||
problem='failed to load module {0}'.format(match_module),
|
||||
problem_mark=match_name.mark)
|
||||
return True, False, True
|
||||
except AttributeError:
|
||||
echoerr(context='Error while loading matcher functions',
|
||||
problem='failed to load matcher function {0}'.format(match_function),
|
||||
problem_mark=match_name.mark)
|
||||
return True, False, True
|
||||
|
||||
if not callable(func):
|
||||
echoerr(context='Error while loading matcher functions',
|
||||
problem='loaded “function” {0} is not callable'.format(match_function),
|
||||
problem_mark=match_name.mark)
|
||||
return True, False, True
|
||||
|
||||
if hasattr(func, 'func_code') and hasattr(func.func_code, 'co_argcount'):
|
||||
if func.func_code.co_argcount != 1:
|
||||
echoerr(
|
||||
context='Error while loading matcher functions',
|
||||
problem=(
|
||||
'function {0} accepts {1} arguments instead of 1. '
|
||||
'Are you sure it is the proper function?'
|
||||
).format(match_function, func.func_code.co_argcount),
|
||||
problem_mark=match_name.mark
|
||||
)
|
||||
|
||||
return True, False, False
|
||||
|
||||
|
||||
def check_ext(ext, data, context, echoerr):
|
||||
havemarks(ext)
|
||||
hadsomedirs = False
|
||||
hadproblem = False
|
||||
if ext not in data['lists']['exts']:
|
||||
hadproblem = True
|
||||
echoerr(context='Error while loading {0} extension configuration'.format(ext),
|
||||
context_mark=ext.mark,
|
||||
problem='extension configuration does not exist')
|
||||
else:
|
||||
for typ in ('themes', 'colorschemes'):
|
||||
if ext not in data['configs'][typ] and not data['configs']['top_' + typ]:
|
||||
hadproblem = True
|
||||
echoerr(context='Error while loading {0} extension configuration'.format(ext),
|
||||
context_mark=ext.mark,
|
||||
problem='{0} configuration does not exist'.format(typ))
|
||||
else:
|
||||
hadsomedirs = True
|
||||
return hadsomedirs, hadproblem
|
||||
|
||||
|
||||
def check_config(d, theme, data, context, echoerr):
|
||||
if len(context) == 4:
|
||||
ext = context[-2][0]
|
||||
else:
|
||||
# local_themes
|
||||
ext = context[-3][0]
|
||||
if ext not in data['lists']['exts']:
|
||||
echoerr(context='Error while loading {0} extension configuration'.format(ext),
|
||||
context_mark=ext.mark,
|
||||
problem='extension configuration does not exist')
|
||||
return True, False, True
|
||||
if (
|
||||
(ext not in data['configs'][d] or theme not in data['configs'][d][ext])
|
||||
and theme not in data['configs']['top_' + d]
|
||||
):
|
||||
echoerr(context='Error while loading {0} from {1} extension configuration'.format(d[:-1], ext),
|
||||
problem='failed to find configuration file {0}/{1}/{2}.json'.format(d, ext, theme),
|
||||
problem_mark=theme.mark)
|
||||
return True, False, True
|
||||
return True, False, False
|
||||
|
||||
|
||||
def check_top_theme(theme, data, context, echoerr):
|
||||
havemarks(theme)
|
||||
if theme not in data['configs']['top_themes']:
|
||||
echoerr(context='Error while checking extension configuration (key {key})'.format(key=context.key),
|
||||
context_mark=context[-2][0].mark,
|
||||
problem='failed to find top theme {0}'.format(theme),
|
||||
problem_mark=theme.mark)
|
||||
return True, False, True
|
||||
return True, False, False
|
||||
|
||||
|
||||
def check_color(color, data, context, echoerr):
|
||||
havemarks(color)
|
||||
if (color not in data['colors_config'].get('colors', {})
|
||||
and color not in data['colors_config'].get('gradients', {})):
|
||||
echoerr(
|
||||
context='Error while checking highlight group in colorscheme (key {key})'.format(
|
||||
key=context.key),
|
||||
problem='found unexistent color or gradient {0}'.format(color),
|
||||
problem_mark=color.mark
|
||||
)
|
||||
return True, False, True
|
||||
return True, False, False
|
||||
|
||||
|
||||
def check_translated_group_name(group, data, context, echoerr):
|
||||
return check_group(group, data, context, echoerr)
|
||||
|
||||
|
||||
def check_group(group, data, context, echoerr):
|
||||
havemarks(group)
|
||||
if not isinstance(group, unicode):
|
||||
return True, False, False
|
||||
colorscheme = data['colorscheme']
|
||||
ext = data['ext']
|
||||
configs = []
|
||||
if ext:
|
||||
if colorscheme == '__main__':
|
||||
configs.append([config for config in data['ext_colorscheme_configs'][ext].items()])
|
||||
configs.append([config for config in data['top_colorscheme_configs'].items()])
|
||||
else:
|
||||
try:
|
||||
configs.append([data['ext_colorscheme_configs'][ext][colorscheme]])
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
configs.append([data['ext_colorscheme_configs'][ext]['__main__']])
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
configs.append([data['top_colorscheme_configs'][colorscheme]])
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
configs.append([data['top_colorscheme_configs'][colorscheme]])
|
||||
except KeyError:
|
||||
pass
|
||||
new_echoerr = DelayedEchoErr(echoerr)
|
||||
hadproblem = False
|
||||
for config_lst in configs:
|
||||
tofind = len(config_lst)
|
||||
not_found = []
|
||||
for config in config_lst:
|
||||
if isinstance(config, tuple):
|
||||
new_colorscheme, config = config
|
||||
new_data = data.copy()
|
||||
new_data['colorscheme'] = new_colorscheme
|
||||
else:
|
||||
new_data = data
|
||||
havemarks(config)
|
||||
try:
|
||||
group_data = config['groups'][group]
|
||||
except KeyError:
|
||||
not_found.append(config.mark.name)
|
||||
else:
|
||||
proceed, echo, chadproblem = check_group(
|
||||
group_data,
|
||||
new_data,
|
||||
context,
|
||||
echoerr,
|
||||
)
|
||||
if chadproblem:
|
||||
hadproblem = True
|
||||
else:
|
||||
tofind -= 1
|
||||
if not tofind:
|
||||
return proceed, echo, hadproblem
|
||||
if not proceed:
|
||||
break
|
||||
if not_found:
|
||||
new_echoerr(
|
||||
context='Error while checking group definition in colorscheme (key {key})'.format(
|
||||
key=context.key),
|
||||
problem='name {0} is not present in {1} {2} colorschemes: {3}'.format(
|
||||
group, tofind, ext, ', '.join(not_found)),
|
||||
problem_mark=group.mark
|
||||
)
|
||||
new_echoerr.echo_all()
|
||||
return True, False, hadproblem
|
||||
|
||||
|
||||
def check_key_compatibility(segment, data, context, echoerr):
|
||||
havemarks(segment)
|
||||
segment_type = segment.get('type', MarkedUnicode('function', None))
|
||||
havemarks(segment_type)
|
||||
|
||||
if segment_type not in type_keys:
|
||||
echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
|
||||
problem='found segment with unknown type {0}'.format(segment_type),
|
||||
problem_mark=segment_type.mark)
|
||||
return False, False, True
|
||||
|
||||
hadproblem = False
|
||||
|
||||
keys = set(segment)
|
||||
if not ((keys - generic_keys) < type_keys[segment_type]):
|
||||
unknown_keys = keys - generic_keys - type_keys[segment_type]
|
||||
echoerr(
|
||||
context='Error while checking segments (key {key})'.format(key=context.key),
|
||||
context_mark=context[-1][1].mark,
|
||||
problem='found keys not used with the current segment type: {0}'.format(
|
||||
list_sep.join(unknown_keys)),
|
||||
problem_mark=list(unknown_keys)[0].mark
|
||||
)
|
||||
hadproblem = True
|
||||
|
||||
if not (keys >= required_keys[segment_type]):
|
||||
missing_keys = required_keys[segment_type] - keys
|
||||
echoerr(
|
||||
context='Error while checking segments (key {key})'.format(key=context.key),
|
||||
context_mark=context[-1][1].mark,
|
||||
problem='found missing required keys: {0}'.format(
|
||||
list_sep.join(missing_keys))
|
||||
)
|
||||
hadproblem = True
|
||||
|
||||
if not (segment_type == 'function' or (keys & highlight_keys)):
|
||||
echoerr(
|
||||
context='Error while checking segments (key {key})'.format(key=context.key),
|
||||
context_mark=context[-1][1].mark,
|
||||
problem=(
|
||||
'found missing keys required to determine highlight group. '
|
||||
'Either highlight_group or name key must be present'
|
||||
)
|
||||
)
|
||||
hadproblem = True
|
||||
|
||||
return True, False, hadproblem
|
||||
|
||||
|
||||
def check_segment_module(module, data, context, echoerr):
|
||||
havemarks(module)
|
||||
with WithPath(data['import_paths']):
|
||||
try:
|
||||
__import__(str(module))
|
||||
except ImportError as e:
|
||||
if echoerr.logger.level >= logging.DEBUG:
|
||||
echoerr.logger.exception(e)
|
||||
echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
|
||||
problem='failed to import module {0}'.format(module),
|
||||
problem_mark=module.mark)
|
||||
return True, False, True
|
||||
return True, False, False
|
||||
|
||||
|
||||
def check_full_segment_data(segment, data, context, echoerr):
|
||||
if 'name' not in segment and 'function' not in segment:
|
||||
return True, False, False
|
||||
|
||||
ext = data['ext']
|
||||
theme_segment_data = context[0][1].get('segment_data', {})
|
||||
main_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None)
|
||||
if not main_theme_name or data['theme'] == main_theme_name:
|
||||
top_segment_data = {}
|
||||
else:
|
||||
top_segment_data = data['ext_theme_configs'].get(main_theme_name, {}).get('segment_data', {})
|
||||
|
||||
if segment.get('type', 'function') == 'function':
|
||||
function_name = segment.get('function')
|
||||
if function_name:
|
||||
module, function_name = get_function_strings(function_name, context, ext)
|
||||
names = [module + '.' + function_name, function_name]
|
||||
else:
|
||||
names = []
|
||||
elif segment.get('name'):
|
||||
names = [segment['name']]
|
||||
else:
|
||||
return True, False, False
|
||||
|
||||
segment_copy = segment.copy()
|
||||
|
||||
for key in ('before', 'after', 'args', 'contents'):
|
||||
if key not in segment_copy:
|
||||
for segment_data in [theme_segment_data, top_segment_data]:
|
||||
for name in names:
|
||||
try:
|
||||
val = segment_data[name][key]
|
||||
k = segment_data[name].keydict[key]
|
||||
segment_copy[k] = val
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return check_key_compatibility(segment_copy, data, context, echoerr)
|
||||
|
||||
|
||||
def check_segment_function(function_name, data, context, echoerr):
|
||||
havemarks(function_name)
|
||||
ext = data['ext']
|
||||
module, function_name = get_function_strings(function_name, context, ext)
|
||||
if context[-2][1].get('type', 'function') == 'function':
|
||||
func = import_segment(function_name, data, context, echoerr, module=module)
|
||||
|
||||
if not func:
|
||||
return True, False, True
|
||||
|
||||
hl_groups = []
|
||||
divider_hl_group = None
|
||||
|
||||
if func.__doc__:
|
||||
H_G_USED_STR = 'Highlight groups used: '
|
||||
LHGUS = len(H_G_USED_STR)
|
||||
D_H_G_USED_STR = 'Divider highlight group used: '
|
||||
LDHGUS = len(D_H_G_USED_STR)
|
||||
pointer = 0
|
||||
mark_name = '<{0} docstring>'.format(function_name)
|
||||
for i, line in enumerate(func.__doc__.split('\n')):
|
||||
if H_G_USED_STR in line:
|
||||
idx = line.index(H_G_USED_STR) + LHGUS
|
||||
hl_groups.append((
|
||||
line[idx:],
|
||||
(mark_name, i + 1, idx + 1, func.__doc__),
|
||||
pointer + idx
|
||||
))
|
||||
elif D_H_G_USED_STR in line:
|
||||
idx = line.index(D_H_G_USED_STR) + LDHGUS + 2
|
||||
mark = Mark(mark_name, i + 1, idx + 1, func.__doc__, pointer + idx)
|
||||
divider_hl_group = MarkedUnicode(line[idx:-3], mark)
|
||||
pointer += len(line) + len('\n')
|
||||
|
||||
hadproblem = False
|
||||
|
||||
if divider_hl_group:
|
||||
r = hl_exists(divider_hl_group, data, context, echoerr, allow_gradients=True)
|
||||
if r:
|
||||
echoerr(
|
||||
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||
problem=(
|
||||
'found highlight group {0} not defined in the following colorschemes: {1}\n'
|
||||
'(Group name was obtained from function documentation.)'
|
||||
).format(divider_hl_group, list_sep.join(r)),
|
||||
problem_mark=function_name.mark
|
||||
)
|
||||
hadproblem = True
|
||||
|
||||
if hl_groups:
|
||||
greg = re.compile(r'``([^`]+)``( \(gradient\))?')
|
||||
parsed_hl_groups = []
|
||||
for line, mark_args, pointer in hl_groups:
|
||||
for s in line.split(', '):
|
||||
required_pack = []
|
||||
sub_pointer = pointer
|
||||
for subs in s.split(' or '):
|
||||
match = greg.match(subs)
|
||||
try:
|
||||
if not match:
|
||||
continue
|
||||
hl_group = MarkedUnicode(
|
||||
match.group(1),
|
||||
Mark(*mark_args, pointer=sub_pointer + match.start(1))
|
||||
)
|
||||
gradient = bool(match.group(2))
|
||||
required_pack.append((hl_group, gradient))
|
||||
finally:
|
||||
sub_pointer += len(subs) + len(' or ')
|
||||
parsed_hl_groups.append(required_pack)
|
||||
pointer += len(s) + len(', ')
|
||||
del hl_group, gradient
|
||||
for required_pack in parsed_hl_groups:
|
||||
rs = [
|
||||
hl_exists(hl_group, data, context, echoerr, allow_gradients=('force' if gradient else False))
|
||||
for hl_group, gradient in required_pack
|
||||
]
|
||||
if all(rs):
|
||||
echoerr(
|
||||
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||
problem=(
|
||||
'found highlight groups list ({0}) with all groups not defined in some colorschemes\n'
|
||||
'(Group names were taken from function documentation.)'
|
||||
).format(list_sep.join((h[0] for h in required_pack))),
|
||||
problem_mark=function_name.mark
|
||||
)
|
||||
for r, h in zip(rs, required_pack):
|
||||
echoerr(
|
||||
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||
problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
|
||||
h[0], list_sep.join(r))
|
||||
)
|
||||
hadproblem = True
|
||||
else:
|
||||
r = hl_exists(function_name, data, context, echoerr, allow_gradients=True)
|
||||
if r:
|
||||
echoerr(
|
||||
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||
problem=(
|
||||
'found highlight group {0} not defined in the following colorschemes: {1}\n'
|
||||
'(If not specified otherwise in documentation, '
|
||||
'highlight group for function segments\n'
|
||||
'is the same as the function name.)'
|
||||
).format(function_name, list_sep.join(r)),
|
||||
problem_mark=function_name.mark
|
||||
)
|
||||
hadproblem = True
|
||||
|
||||
return True, False, hadproblem
|
||||
elif context[-2][1].get('type') != 'segment_list':
|
||||
if function_name not in context[0][1].get('segment_data', {}):
|
||||
main_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None)
|
||||
if data['theme'] == main_theme_name:
|
||||
main_theme = {}
|
||||
else:
|
||||
main_theme = data['ext_theme_configs'].get(main_theme_name, {})
|
||||
if (
|
||||
function_name not in main_theme.get('segment_data', {})
|
||||
and function_name not in data['ext_theme_configs'].get('__main__', {}).get('segment_data', {})
|
||||
and not any(((function_name in theme.get('segment_data', {})) for theme in data['top_themes'].values()))
|
||||
):
|
||||
echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
|
||||
problem='found useless use of name key (such name is not present in theme/segment_data)',
|
||||
problem_mark=function_name.mark)
|
||||
|
||||
return True, False, False
|
||||
|
||||
|
||||
def hl_exists(hl_group, data, context, echoerr, allow_gradients=False):
|
||||
havemarks(hl_group)
|
||||
ext = data['ext']
|
||||
if ext not in data['colorscheme_configs']:
|
||||
# No colorschemes. Error was already reported, no need to report it
|
||||
# twice
|
||||
return []
|
||||
r = []
|
||||
for colorscheme, cconfig in data['colorscheme_configs'][ext].items():
|
||||
if hl_group not in cconfig.get('groups', {}):
|
||||
r.append(colorscheme)
|
||||
elif not allow_gradients or allow_gradients == 'force':
|
||||
group_config = cconfig['groups'][hl_group]
|
||||
havemarks(group_config)
|
||||
hadgradient = False
|
||||
for ckey in ('fg', 'bg'):
|
||||
color = group_config.get(ckey)
|
||||
if not color:
|
||||
# No color. Error was already reported.
|
||||
continue
|
||||
havemarks(color)
|
||||
# Gradients are only allowed for function segments. Note that
|
||||
# whether *either* color or gradient exists should have been
|
||||
# already checked
|
||||
hascolor = color in data['colors_config'].get('colors', {})
|
||||
hasgradient = color in data['colors_config'].get('gradients', {})
|
||||
if hasgradient:
|
||||
hadgradient = True
|
||||
if allow_gradients is False and not hascolor and hasgradient:
|
||||
echoerr(
|
||||
context='Error while checking highlight group in theme (key {key})'.format(
|
||||
key=context.key),
|
||||
context_mark=hl_group.mark,
|
||||
problem='group {0} is using gradient {1} instead of a color'.format(hl_group, color),
|
||||
problem_mark=color.mark
|
||||
)
|
||||
r.append(colorscheme)
|
||||
continue
|
||||
if allow_gradients == 'force' and not hadgradient:
|
||||
echoerr(
|
||||
context='Error while checking highlight group in theme (key {key})'.format(
|
||||
key=context.key),
|
||||
context_mark=hl_group.mark,
|
||||
problem='group {0} should have at least one gradient color, but it has no'.format(hl_group),
|
||||
problem_mark=group_config.mark
|
||||
)
|
||||
r.append(colorscheme)
|
||||
return r
|
||||
|
||||
|
||||
def check_highlight_group(hl_group, data, context, echoerr):
|
||||
havemarks(hl_group)
|
||||
r = hl_exists(hl_group, data, context, echoerr)
|
||||
if r:
|
||||
echoerr(
|
||||
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||
problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
|
||||
hl_group, list_sep.join(r)),
|
||||
problem_mark=hl_group.mark
|
||||
)
|
||||
return True, False, True
|
||||
return True, False, False
|
||||
|
||||
|
||||
def check_highlight_groups(hl_groups, data, context, echoerr):
|
||||
havemarks(hl_groups)
|
||||
rs = [hl_exists(hl_group, data, context, echoerr) for hl_group in hl_groups]
|
||||
if all(rs):
|
||||
echoerr(
|
||||
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||
problem='found highlight groups list ({0}) with all groups not defined in some colorschemes'.format(
|
||||
list_sep.join((unicode(h) for h in hl_groups))),
|
||||
problem_mark=hl_groups.mark
|
||||
)
|
||||
for r, hl_group in zip(rs, hl_groups):
|
||||
echoerr(
|
||||
context='Error while checking theme (key {key})'.format(key=context.key),
|
||||
problem='found highlight group {0} not defined in the following colorschemes: {1}'.format(
|
||||
hl_group, list_sep.join(r)),
|
||||
problem_mark=hl_group.mark
|
||||
)
|
||||
return True, False, True
|
||||
return True, False, False
|
||||
|
||||
|
||||
def check_segment_data_key(key, data, context, echoerr):
|
||||
havemarks(key)
|
||||
has_module_name = '.' in key
|
||||
found = False
|
||||
for ext, theme in list_themes(data, context):
|
||||
for segments in theme.get('segments', {}).values():
|
||||
for segment in segments:
|
||||
if 'name' in segment:
|
||||
if key == segment['name']:
|
||||
found = True
|
||||
break
|
||||
else:
|
||||
function_name = segment.get('function')
|
||||
if function_name:
|
||||
module, function_name = get_function_strings(function_name, ((None, theme),), ext)
|
||||
if has_module_name:
|
||||
full_name = module + '.' + function_name
|
||||
if key == full_name:
|
||||
found = True
|
||||
break
|
||||
else:
|
||||
if key == function_name:
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
break
|
||||
if found:
|
||||
break
|
||||
else:
|
||||
if data['theme_type'] != 'top':
|
||||
echoerr(context='Error while checking segment data',
|
||||
problem='found key {0} that cannot be associated with any segment'.format(key),
|
||||
problem_mark=key.mark)
|
||||
return True, False, True
|
||||
|
||||
return True, False, False
|
||||
|
||||
|
||||
threaded_args_specs = {
|
||||
'interval': Spec().cmp('gt', 0.0),
|
||||
'update_first': Spec().type(bool),
|
||||
'shutdown_event': Spec().error('Shutdown event must be set by powerline'),
|
||||
}
|
||||
|
||||
|
||||
def check_args_variant(func, args, data, context, echoerr):
|
||||
havemarks(args)
|
||||
argspec = getconfigargspec(func)
|
||||
present_args = set(args)
|
||||
all_args = set(argspec.args)
|
||||
required_args = set(argspec.args[:-len(argspec.defaults)])
|
||||
|
||||
hadproblem = False
|
||||
|
||||
if required_args - present_args:
|
||||
echoerr(
|
||||
context='Error while checking segment arguments (key {key})'.format(key=context.key),
|
||||
context_mark=args.mark,
|
||||
problem='some of the required keys are missing: {0}'.format(list_sep.join(required_args - present_args))
|
||||
)
|
||||
hadproblem = True
|
||||
|
||||
if not all_args >= present_args:
|
||||
echoerr(context='Error while checking segment arguments (key {key})'.format(key=context.key),
|
||||
context_mark=args.mark,
|
||||
problem='found unknown keys: {0}'.format(list_sep.join(present_args - all_args)),
|
||||
problem_mark=next(iter(present_args - all_args)).mark)
|
||||
hadproblem = True
|
||||
|
||||
if isinstance(func, ThreadedSegment):
|
||||
for key in set(threaded_args_specs) & present_args:
|
||||
proceed, khadproblem = threaded_args_specs[key].match(
|
||||
args[key],
|
||||
args.mark,
|
||||
data,
|
||||
context.enter_key(args, key),
|
||||
echoerr
|
||||
)
|
||||
if khadproblem:
|
||||
hadproblem = True
|
||||
if not proceed:
|
||||
return hadproblem
|
||||
|
||||
return hadproblem
|
||||
|
||||
|
||||
def check_args(get_functions, args, data, context, echoerr):
|
||||
new_echoerr = DelayedEchoErr(echoerr)
|
||||
count = 0
|
||||
hadproblem = False
|
||||
for func in get_functions(data, context, new_echoerr):
|
||||
count += 1
|
||||
shadproblem = check_args_variant(func, args, data, context, echoerr)
|
||||
if shadproblem:
|
||||
hadproblem = True
|
||||
|
||||
if not count:
|
||||
hadproblem = True
|
||||
if new_echoerr:
|
||||
new_echoerr.echo_all()
|
||||
else:
|
||||
echoerr(context='Error while checking segment arguments (key {key})'.format(key=context.key),
|
||||
context_mark=context[-2][1].mark,
|
||||
problem='no suitable segments found')
|
||||
|
||||
return True, False, hadproblem
|
||||
|
||||
|
||||
def get_one_segment_function(data, context, echoerr):
|
||||
ext = data['ext']
|
||||
function_name = context[-2][1].get('function')
|
||||
if function_name:
|
||||
module, function_name = get_function_strings(function_name, context, ext)
|
||||
func = import_segment(function_name, data, context, echoerr, module=module)
|
||||
if func:
|
||||
yield func
|
||||
|
||||
|
||||
def get_all_possible_functions(data, context, echoerr):
|
||||
name = context[-2][0]
|
||||
module, name = name.rpartition('.')[::2]
|
||||
if module:
|
||||
func = import_segment(name, data, context, echoerr, module=module)
|
||||
if func:
|
||||
yield func
|
||||
else:
|
||||
for ext, theme_config in list_themes(data, context):
|
||||
for segments in theme_config.get('segments', {}).values():
|
||||
for segment in segments:
|
||||
if segment.get('type', 'function') == 'function':
|
||||
function_name = segment.get('function')
|
||||
current_name = segment.get('name')
|
||||
if function_name:
|
||||
module, function_name = get_function_strings(function_name, ((None, theme_config),), ext)
|
||||
if current_name == name or function_name == name:
|
||||
func = import_segment(function_name, data, context, echoerr, module=module)
|
||||
if func:
|
||||
yield func
|
||||
|
||||
|
||||
def check_exinclude_function(name, data, context, echoerr):
|
||||
ext = data['ext']
|
||||
module, name = name.rpartition('.')[::2]
|
||||
if not module:
|
||||
module = MarkedUnicode('powerline.selectors.' + ext, None)
|
||||
func = import_function('selector', name, data, context, echoerr, module=module)
|
||||
if not func:
|
||||
return True, False, True
|
||||
return True, False, False
|
68
powerline/lint/context.py
Normal file
68
powerline/lint/context.py
Normal file
@ -0,0 +1,68 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import itertools
|
||||
|
||||
from powerline.lib.unicode import unicode
|
||||
from powerline.lint.markedjson.markedvalue import MarkedUnicode
|
||||
from powerline.lint.selfcheck import havemarks
|
||||
|
||||
|
||||
class JStr(unicode):
|
||||
def join(self, iterable):
|
||||
return super(JStr, self).join((unicode(item) for item in iterable))
|
||||
|
||||
|
||||
key_sep = JStr('/')
|
||||
|
||||
|
||||
def list_themes(data, context):
|
||||
theme_type = data['theme_type']
|
||||
ext = data['ext']
|
||||
main_theme_name = data['main_config'].get('ext', {}).get(ext, {}).get('theme', None)
|
||||
is_main_theme = (data['theme'] == main_theme_name)
|
||||
if theme_type == 'top':
|
||||
return list(itertools.chain(*[
|
||||
[(theme_ext, theme) for theme in theme_configs.values()]
|
||||
for theme_ext, theme_configs in data['theme_configs'].items()
|
||||
]))
|
||||
elif theme_type == 'main' or is_main_theme:
|
||||
return [(ext, theme) for theme in data['ext_theme_configs'].values()]
|
||||
else:
|
||||
return [(ext, context[0][1])]
|
||||
|
||||
|
||||
class Context(tuple):
|
||||
for func in dir(tuple):
|
||||
if func in ('__getitem__', '__init__', '__getattribute__', '__len__', '__iter__'):
|
||||
continue
|
||||
exec((
|
||||
'def {0}(self, *args, **kwargs):\n'
|
||||
' raise TypeError("{0} is not allowed for Context")'
|
||||
).format(func))
|
||||
del func
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __new__(cls, base, context_key=None, context_value=None):
|
||||
if context_key is not None:
|
||||
assert(context_value is not None)
|
||||
assert(type(base) is Context)
|
||||
havemarks(context_key, context_value)
|
||||
return tuple.__new__(cls, tuple.__add__(base, ((context_key, context_value),)))
|
||||
else:
|
||||
havemarks(base)
|
||||
return tuple.__new__(cls, ((MarkedUnicode('', base.mark), base),))
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return key_sep.join((c[0] for c in self))
|
||||
|
||||
def enter_key(self, value, key):
|
||||
return self.enter(value.keydict[key], value[key])
|
||||
|
||||
def enter_item(self, name, item):
|
||||
return self.enter(MarkedUnicode(name, item.mark), item)
|
||||
|
||||
def enter(self, context_key, context_value):
|
||||
return Context.__new__(Context, self, context_key, context_value)
|
56
powerline/lint/imp.py
Normal file
56
powerline/lint/imp.py
Normal file
@ -0,0 +1,56 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import sys
|
||||
|
||||
from powerline.lint.selfcheck import havemarks
|
||||
|
||||
|
||||
class WithPath(object):
|
||||
def __init__(self, import_paths):
|
||||
self.import_paths = import_paths
|
||||
|
||||
def __enter__(self):
|
||||
self.oldpath = sys.path
|
||||
sys.path = self.import_paths + sys.path
|
||||
|
||||
def __exit__(self, *args):
|
||||
sys.path = self.oldpath
|
||||
|
||||
|
||||
def import_function(function_type, name, data, context, echoerr, module):
|
||||
havemarks(name, module)
|
||||
|
||||
if module == 'powerline.segments.common':
|
||||
echoerr(context='Warning while checking segments (key {key})'.format(key=context.key),
|
||||
context_mark=name.mark,
|
||||
problem='module {0} is deprecated'.format(module),
|
||||
problem_mark=module.mark)
|
||||
|
||||
with WithPath(data['import_paths']):
|
||||
try:
|
||||
func = getattr(__import__(str(module), fromlist=[str(name)]), str(name))
|
||||
except ImportError:
|
||||
echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
|
||||
context_mark=name.mark,
|
||||
problem='failed to import module {0}'.format(module),
|
||||
problem_mark=module.mark)
|
||||
return None
|
||||
except AttributeError:
|
||||
echoerr(context='Error while loading {0} function (key {key})'.format(function_type, key=context.key),
|
||||
problem='failed to load function {0} from module {1}'.format(name, module),
|
||||
problem_mark=name.mark)
|
||||
return None
|
||||
|
||||
if not callable(func):
|
||||
echoerr(context='Error while checking segments (key {key})'.format(key=context.key),
|
||||
context_mark=name.mark,
|
||||
problem='imported “function” {0} from module {1} is not callable'.format(name, module),
|
||||
problem_mark=module.mark)
|
||||
return None
|
||||
|
||||
return func
|
||||
|
||||
|
||||
def import_segment(*args, **kwargs):
|
||||
return import_function('segment', *args, **kwargs)
|
@ -5,10 +5,12 @@ from powerline.lint.markedjson.loader import Loader
|
||||
|
||||
|
||||
def load(stream, Loader=Loader):
|
||||
"""
|
||||
Parse the first YAML document in a stream
|
||||
and produce the corresponding Python object.
|
||||
"""
|
||||
'''Parse JSON value and produce the corresponding Python object
|
||||
|
||||
:return:
|
||||
(hadproblem, object) where first argument is true if there were errors
|
||||
during loading JSON stream and second is the corresponding JSON object.
|
||||
'''
|
||||
loader = Loader(stream)
|
||||
try:
|
||||
r = loader.get_single_data()
|
||||
|
@ -43,9 +43,9 @@ class Composer:
|
||||
if not self.check_event(events.StreamEndEvent):
|
||||
event = self.get_event()
|
||||
raise ComposerError(
|
||||
"expected a single document in the stream",
|
||||
'expected a single document in the stream',
|
||||
document.start_mark,
|
||||
"but found another document",
|
||||
'but found another document',
|
||||
event.start_mark
|
||||
)
|
||||
|
||||
@ -109,8 +109,8 @@ class Composer:
|
||||
# key_event = self.peek_event()
|
||||
item_key = self.compose_node(node, None)
|
||||
# if item_key in node.value:
|
||||
# raise ComposerError("while composing a mapping", start_event.start_mark,
|
||||
# "found duplicate key", key_event.start_mark)
|
||||
# raise ComposerError('while composing a mapping', start_event.start_mark,
|
||||
# 'found duplicate key', key_event.start_mark)
|
||||
item_value = self.compose_node(node, item_key)
|
||||
# node.value[item_key] = item_value
|
||||
node.value.append((item_key, item_value))
|
||||
|
@ -94,7 +94,7 @@ class BaseConstructor:
|
||||
if not isinstance(node, nodes.ScalarNode):
|
||||
raise ConstructorError(
|
||||
None, None,
|
||||
"expected a scalar node, but found %s" % node.id,
|
||||
'expected a scalar node, but found %s' % node.id,
|
||||
node.start_mark
|
||||
)
|
||||
return node.value
|
||||
@ -103,7 +103,7 @@ class BaseConstructor:
|
||||
if not isinstance(node, nodes.SequenceNode):
|
||||
raise ConstructorError(
|
||||
None, None,
|
||||
"expected a sequence node, but found %s" % node.id,
|
||||
'expected a sequence node, but found %s' % node.id,
|
||||
node.start_mark
|
||||
)
|
||||
return [
|
||||
@ -116,7 +116,7 @@ class BaseConstructor:
|
||||
if not isinstance(node, nodes.MappingNode):
|
||||
raise ConstructorError(
|
||||
None, None,
|
||||
"expected a mapping node, but found %s" % node.id,
|
||||
'expected a mapping node, but found %s' % node.id,
|
||||
node.start_mark
|
||||
)
|
||||
mapping = {}
|
||||
@ -174,9 +174,9 @@ class Constructor(BaseConstructor):
|
||||
for subnode in value_node.value:
|
||||
if not isinstance(subnode, nodes.MappingNode):
|
||||
raise ConstructorError(
|
||||
"while constructing a mapping",
|
||||
'while constructing a mapping',
|
||||
node.start_mark,
|
||||
"expected a mapping for merging, but found %s" % subnode.id,
|
||||
'expected a mapping for merging, but found %s' % subnode.id,
|
||||
subnode.start_mark
|
||||
)
|
||||
self.flatten_mapping(subnode)
|
||||
@ -186,9 +186,9 @@ class Constructor(BaseConstructor):
|
||||
merge.extend(value)
|
||||
else:
|
||||
raise ConstructorError(
|
||||
"while constructing a mapping",
|
||||
'while constructing a mapping',
|
||||
node.start_mark,
|
||||
("expected a mapping or list of mappings for merging, but found %s" % value_node.id),
|
||||
('expected a mapping or list of mappings for merging, but found %s' % value_node.id),
|
||||
value_node.start_mark
|
||||
)
|
||||
elif key_node.tag == 'tag:yaml.org,2002:value':
|
||||
@ -255,7 +255,7 @@ class Constructor(BaseConstructor):
|
||||
def construct_undefined(self, node):
|
||||
raise ConstructorError(
|
||||
None, None,
|
||||
"could not determine a constructor for the tag %r" % node.tag,
|
||||
'could not determine a constructor for the tag %r' % node.tag,
|
||||
node.start_mark
|
||||
)
|
||||
|
||||
|
@ -57,10 +57,10 @@ class Mark:
|
||||
|
||||
def __str__(self):
|
||||
snippet = self.get_snippet()
|
||||
where = (" in \"%s\", line %d, column %d" % (
|
||||
where = (' in "%s", line %d, column %d' % (
|
||||
self.name, self.line + 1, self.column + 1))
|
||||
if snippet is not None:
|
||||
where += ":\n" + snippet
|
||||
where += ':\n' + snippet
|
||||
if type(where) is str:
|
||||
return where
|
||||
else:
|
||||
@ -68,8 +68,9 @@ class Mark:
|
||||
|
||||
|
||||
def echoerr(*args, **kwargs):
|
||||
sys.stderr.write('\n')
|
||||
sys.stderr.write(format_error(*args, **kwargs) + '\n')
|
||||
stream = kwargs.pop('stream', sys.stderr)
|
||||
stream.write('\n')
|
||||
stream.write(format_error(*args, **kwargs) + '\n')
|
||||
|
||||
|
||||
def format_error(context=None, context_mark=None, problem=None, problem_mark=None, note=None):
|
||||
@ -98,3 +99,34 @@ def format_error(context=None, context_mark=None, problem=None, problem_mark=Non
|
||||
class MarkedError(Exception):
|
||||
def __init__(self, context=None, context_mark=None, problem=None, problem_mark=None, note=None):
|
||||
Exception.__init__(self, format_error(context, context_mark, problem, problem_mark, note))
|
||||
|
||||
|
||||
class EchoErr(object):
|
||||
__slots__ = ('echoerr', 'logger',)
|
||||
|
||||
def __init__(self, echoerr, logger):
|
||||
self.echoerr = echoerr
|
||||
self.logger = logger
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
self.echoerr(*args, **kwargs)
|
||||
|
||||
|
||||
class DelayedEchoErr(EchoErr):
|
||||
__slots__ = ('echoerr', 'logger', 'errs')
|
||||
|
||||
def __init__(self, echoerr):
|
||||
super(DelayedEchoErr, self).__init__(echoerr, echoerr.logger)
|
||||
self.errs = []
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
self.errs.append((args, kwargs))
|
||||
|
||||
def echo_all(self):
|
||||
for args, kwargs in self.errs:
|
||||
self.echoerr(*args, **kwargs)
|
||||
|
||||
def __nonzero__(self):
|
||||
return not not self.errs
|
||||
|
||||
__bool__ = __nonzero__
|
||||
|
@ -88,7 +88,7 @@ class Parser:
|
||||
token = self.peek_token()
|
||||
self.echoerr(
|
||||
None, None,
|
||||
("expected '<stream end>', but found %r" % token.id), token.start_mark
|
||||
('expected \'<stream end>\', but found %r' % token.id), token.start_mark
|
||||
)
|
||||
return events.StreamEndEvent(token.start_mark, token.end_mark)
|
||||
else:
|
||||
@ -141,8 +141,8 @@ class Parser:
|
||||
else:
|
||||
token = self.peek_token()
|
||||
raise ParserError(
|
||||
"while parsing a flow node", start_mark,
|
||||
"expected the node content, but found %r" % token.id,
|
||||
'while parsing a flow node', start_mark,
|
||||
'expected the node content, but found %r' % token.id,
|
||||
token.start_mark
|
||||
)
|
||||
return event
|
||||
@ -160,14 +160,14 @@ class Parser:
|
||||
if self.check_token(tokens.FlowSequenceEndToken):
|
||||
token = self.peek_token()
|
||||
self.echoerr(
|
||||
"While parsing a flow sequence", self.marks[-1],
|
||||
("expected sequence value, but got %r" % token.id), token.start_mark
|
||||
'While parsing a flow sequence', self.marks[-1],
|
||||
('expected sequence value, but got %r' % token.id), token.start_mark
|
||||
)
|
||||
else:
|
||||
token = self.peek_token()
|
||||
raise ParserError(
|
||||
"while parsing a flow sequence", self.marks[-1],
|
||||
("expected ',' or ']', but got %r" % token.id), token.start_mark
|
||||
'while parsing a flow sequence', self.marks[-1],
|
||||
('expected \',\' or \']\', but got %r' % token.id), token.start_mark
|
||||
)
|
||||
|
||||
if not self.check_token(tokens.FlowSequenceEndToken):
|
||||
@ -197,14 +197,14 @@ class Parser:
|
||||
if self.check_token(tokens.FlowMappingEndToken):
|
||||
token = self.peek_token()
|
||||
self.echoerr(
|
||||
"While parsing a flow mapping", self.marks[-1],
|
||||
("expected mapping key, but got %r" % token.id), token.start_mark
|
||||
'While parsing a flow mapping', self.marks[-1],
|
||||
('expected mapping key, but got %r' % token.id), token.start_mark
|
||||
)
|
||||
else:
|
||||
token = self.peek_token()
|
||||
raise ParserError(
|
||||
"while parsing a flow mapping", self.marks[-1],
|
||||
("expected ',' or '}', but got %r" % token.id), token.start_mark
|
||||
'while parsing a flow mapping', self.marks[-1],
|
||||
('expected \',\' or \'}\', but got %r' % token.id), token.start_mark
|
||||
)
|
||||
if self.check_token(tokens.KeyToken):
|
||||
token = self.get_token()
|
||||
@ -214,8 +214,8 @@ class Parser:
|
||||
else:
|
||||
token = self.peek_token()
|
||||
raise ParserError(
|
||||
"while parsing a flow mapping", self.marks[-1],
|
||||
("expected value, but got %r" % token.id), token.start_mark
|
||||
'while parsing a flow mapping', self.marks[-1],
|
||||
('expected value, but got %r' % token.id), token.start_mark
|
||||
)
|
||||
elif not self.check_token(tokens.FlowMappingEndToken):
|
||||
token = self.peek_token()
|
||||
@ -226,14 +226,14 @@ class Parser:
|
||||
|
||||
if expect_key:
|
||||
raise ParserError(
|
||||
"while parsing a flow mapping", self.marks[-1],
|
||||
("expected string key, but got %r" % token.id), token.start_mark
|
||||
'while parsing a flow mapping', self.marks[-1],
|
||||
('expected string key, but got %r' % token.id), token.start_mark
|
||||
)
|
||||
else:
|
||||
token = self.peek_token()
|
||||
raise ParserError(
|
||||
"while parsing a flow mapping", self.marks[-1],
|
||||
("expected ':', but got %r" % token.id), token.start_mark
|
||||
'while parsing a flow mapping', self.marks[-1],
|
||||
('expected \':\', but got %r' % token.id), token.start_mark
|
||||
)
|
||||
token = self.get_token()
|
||||
event = events.MappingEndEvent(token.start_mark, token.end_mark)
|
||||
@ -250,6 +250,6 @@ class Parser:
|
||||
|
||||
token = self.peek_token()
|
||||
raise ParserError(
|
||||
"while parsing a flow mapping", self.marks[-1],
|
||||
("expected mapping value, but got %r" % token.id), token.start_mark
|
||||
'while parsing a flow mapping', self.marks[-1],
|
||||
('expected mapping value, but got %r' % token.id), token.start_mark
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ from powerline.lint.markedjson.error import MarkedError, Mark, NON_PRINTABLE
|
||||
from powerline.lib.unicode import unicode
|
||||
|
||||
|
||||
# This module contains abstractions for the input stream. You don't have to
|
||||
# This module contains abstractions for the input stream. You don’t have to
|
||||
# looks further, there are no pretty code.
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ class Reader(object):
|
||||
# Reader accepts
|
||||
# - a file-like object with its `read` method returning `str`,
|
||||
|
||||
# Yeah, it's ugly and slow.
|
||||
# Yeah, it’s ugly and slow.
|
||||
def __init__(self, stream):
|
||||
self.name = None
|
||||
self.stream = None
|
||||
@ -42,7 +42,7 @@ class Reader(object):
|
||||
self.column = 0
|
||||
|
||||
self.stream = stream
|
||||
self.name = getattr(stream, 'name', "<file>")
|
||||
self.name = getattr(stream, 'name', '<file>')
|
||||
self.eof = False
|
||||
self.raw_buffer = None
|
||||
|
||||
|
@ -39,7 +39,7 @@ class SimpleKey:
|
||||
|
||||
class Scanner:
|
||||
def __init__(self):
|
||||
"""Initialize the scanner."""
|
||||
'''Initialize the scanner.'''
|
||||
# It is assumed that Scanner and Reader will have a common descendant.
|
||||
# Reader do the dirty work of checking for BOM and converting the
|
||||
# input data to Unicode. It also adds NUL to the end.
|
||||
@ -168,17 +168,17 @@ class Scanner:
|
||||
return self.fetch_value()
|
||||
|
||||
# Is it a double quoted scalar?
|
||||
if ch == '\"':
|
||||
if ch == '"':
|
||||
return self.fetch_double()
|
||||
|
||||
# It must be a plain scalar then.
|
||||
if self.check_plain():
|
||||
return self.fetch_plain()
|
||||
|
||||
# No? It's an error. Let's produce a nice error message.
|
||||
# No? It’s an error. Let’s produce a nice error message.
|
||||
raise ScannerError(
|
||||
"while scanning for the next token", None,
|
||||
"found character %r that cannot start any token" % ch,
|
||||
'while scanning for the next token', None,
|
||||
'found character %r that cannot start any token' % ch,
|
||||
self.get_mark()
|
||||
)
|
||||
|
||||
@ -186,7 +186,7 @@ class Scanner:
|
||||
|
||||
def next_possible_simple_key(self):
|
||||
# Return the number of the nearest possible simple key. Actually we
|
||||
# don't need to loop through the whole dictionary. We may replace it
|
||||
# don’t need to loop through the whole dictionary. We may replace it
|
||||
# with the following code:
|
||||
# if not self.possible_simple_keys:
|
||||
# return None
|
||||
@ -211,11 +211,11 @@ class Scanner:
|
||||
del self.possible_simple_keys[level]
|
||||
|
||||
def save_possible_simple_key(self):
|
||||
# The next token may start a simple key. We check if it's possible
|
||||
# The next token may start a simple key. We check if it’s possible
|
||||
# and save its position. This function is called for
|
||||
# SCALAR(flow), '[', and '{'.
|
||||
|
||||
# The next token might be a simple key. Let's save it's number and
|
||||
# The next token might be a simple key. Let’s save it’s number and
|
||||
# position.
|
||||
if self.allow_simple_key:
|
||||
self.remove_possible_simple_key()
|
||||
@ -364,7 +364,7 @@ class Scanner:
|
||||
def scan_flow_scalar(self):
|
||||
# See the specification for details.
|
||||
# Note that we loose indentation rules for quoted scalars. Quoted
|
||||
# scalars don't need to adhere indentation because " and ' clearly
|
||||
# scalars don’t need to adhere indentation because " and ' clearly
|
||||
# mark the beginning and the end of them. Therefore we are less
|
||||
# restrictive then the specification requires. We only need to check
|
||||
# that document separators are not included in scalars.
|
||||
@ -386,7 +386,7 @@ class Scanner:
|
||||
'n': '\x0A',
|
||||
'f': '\x0C',
|
||||
'r': '\x0D',
|
||||
'\"': '\"',
|
||||
'"': '\"',
|
||||
'\\': '\\',
|
||||
}
|
||||
|
||||
@ -417,8 +417,8 @@ class Scanner:
|
||||
for k in range(length):
|
||||
if self.peek(k) not in '0123456789ABCDEFabcdef':
|
||||
raise ScannerError(
|
||||
"while scanning a double-quoted scalar", start_mark,
|
||||
"expected escape sequence of %d hexdecimal numbers, but found %r" % (
|
||||
'while scanning a double-quoted scalar', start_mark,
|
||||
'expected escape sequence of %d hexdecimal numbers, but found %r' % (
|
||||
length, self.peek(k)),
|
||||
self.get_mark()
|
||||
)
|
||||
@ -427,8 +427,8 @@ class Scanner:
|
||||
self.forward(length)
|
||||
else:
|
||||
raise ScannerError(
|
||||
"while scanning a double-quoted scalar", start_mark,
|
||||
("found unknown escape character %r" % ch), self.get_mark()
|
||||
'while scanning a double-quoted scalar', start_mark,
|
||||
('found unknown escape character %r' % ch), self.get_mark()
|
||||
)
|
||||
else:
|
||||
return chunks
|
||||
@ -444,13 +444,13 @@ class Scanner:
|
||||
ch = self.peek()
|
||||
if ch == '\0':
|
||||
raise ScannerError(
|
||||
"while scanning a quoted scalar", start_mark,
|
||||
"found unexpected end of stream", self.get_mark()
|
||||
'while scanning a quoted scalar', start_mark,
|
||||
'found unexpected end of stream', self.get_mark()
|
||||
)
|
||||
elif ch == '\n':
|
||||
raise ScannerError(
|
||||
"while scanning a quoted scalar", start_mark,
|
||||
"found unexpected line end", self.get_mark()
|
||||
'while scanning a quoted scalar', start_mark,
|
||||
'found unexpected line end', self.get_mark()
|
||||
)
|
||||
else:
|
||||
chunks.append(whitespaces)
|
||||
|
16
powerline/lint/selfcheck.py
Normal file
16
powerline/lint/selfcheck.py
Normal file
@ -0,0 +1,16 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
from powerline.lib.unicode import unicode
|
||||
|
||||
|
||||
def havemarks(*args, **kwargs):
|
||||
origin = kwargs.get('origin', '')
|
||||
for i, v in enumerate(args):
|
||||
if not hasattr(v, 'mark'):
|
||||
raise AssertionError('Value #{0}/{1} ({2!r}) has no attribute `mark`'.format(origin, i, v))
|
||||
if isinstance(v, dict):
|
||||
for key, val in v.items():
|
||||
havemarks(key, val, origin=(origin + '[' + unicode(i) + ']/' + unicode(key)))
|
||||
elif isinstance(v, list):
|
||||
havemarks(*v, origin=(origin + '[' + unicode(i) + ']'))
|
724
powerline/lint/spec.py
Normal file
724
powerline/lint/spec.py
Normal file
@ -0,0 +1,724 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import itertools
|
||||
import re
|
||||
|
||||
from copy import copy
|
||||
|
||||
from powerline.lib.unicode import unicode
|
||||
from powerline.lint.markedjson.error import echoerr, DelayedEchoErr
|
||||
from powerline.lint.selfcheck import havemarks
|
||||
|
||||
|
||||
class Spec(object):
|
||||
'''Class that describes some JSON value
|
||||
|
||||
In powerline it is only used to describe JSON values stored in powerline
|
||||
configuration.
|
||||
|
||||
:param dict keys:
|
||||
Dictionary that maps keys that may be present in the given JSON
|
||||
dictionary to their descriptions. If this parameter is not empty it
|
||||
implies that described value has dictionary type. Non-dictionary types
|
||||
must be described using ``Spec()``: without arguments.
|
||||
|
||||
.. note::
|
||||
Methods that create the specifications return ``self``, so calls to them
|
||||
may be chained: ``Spec().type(unicode).re('^\w+$')``. This does not
|
||||
apply to functions that *apply* specification like :py:meth`Spec.match`.
|
||||
|
||||
.. note::
|
||||
Methods starting with ``check_`` return two values: first determines
|
||||
whether caller should proceed on running other checks, second
|
||||
determines whether there were any problems (i.e. whether error was
|
||||
reported). One should not call these methods directly: there is
|
||||
:py:meth:`Spec.match` method for checking values.
|
||||
|
||||
.. note::
|
||||
In ``check_`` and ``match`` methods specifications are identified by
|
||||
their indexes for the purpose of simplyfying :py:meth:`Spec.copy`
|
||||
method.
|
||||
|
||||
Some common parameters:
|
||||
|
||||
``data``:
|
||||
Whatever data supplied by the first caller for checker functions. Is not
|
||||
processed by :py:class:`Spec` methods in any fashion.
|
||||
``context``:
|
||||
:py:class:`powerline.lint.context.Context` instance, describes context
|
||||
of the value. :py:class:`Spec` methods only use its ``.key`` methods for
|
||||
error messages.
|
||||
``echoerr``:
|
||||
Callable that should be used to echo errors. Is supposed to take four
|
||||
optional keyword arguments: ``problem``, ``problem_mark``, ``context``,
|
||||
``context_mark``.
|
||||
``value``:
|
||||
Checked value.
|
||||
'''
|
||||
|
||||
def __init__(self, **keys):
|
||||
self.specs = []
|
||||
self.keys = {}
|
||||
self.checks = []
|
||||
self.cmsg = ''
|
||||
self.isoptional = False
|
||||
self.uspecs = []
|
||||
self.ufailmsg = lambda key: 'found unknown key: {0}'.format(key)
|
||||
self.did_type = False
|
||||
self.update(**keys)
|
||||
|
||||
def update(self, **keys):
|
||||
'''Describe additional keys that may be present in given JSON value
|
||||
|
||||
If called with some keyword arguments implies that described value is
|
||||
a dictionary. If called without keyword parameters it is no-op.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
for k, v in keys.items():
|
||||
self.keys[k] = len(self.specs)
|
||||
self.specs.append(v)
|
||||
if self.keys and not self.did_type:
|
||||
self.type(dict)
|
||||
self.did_type = True
|
||||
return self
|
||||
|
||||
def copy(self, copied=None):
|
||||
'''Deep copy the spec
|
||||
|
||||
:param dict copied:
|
||||
Internal dictionary used for storing already copied values. This
|
||||
parameter should not be used.
|
||||
|
||||
:return: New :py:class:`Spec` object that is a deep copy of ``self``.
|
||||
'''
|
||||
copied = copied or {}
|
||||
try:
|
||||
return copied[id(self)]
|
||||
except KeyError:
|
||||
instance = self.__class__()
|
||||
copied[id(self)] = instance
|
||||
return self.__class__()._update(self.__dict__, copied)
|
||||
|
||||
def _update(self, d, copied):
|
||||
'''Helper for the :py:meth:`Spec.copy` function
|
||||
|
||||
Populates new instance with values taken from the old one.
|
||||
|
||||
:param dict d:
|
||||
``__dict__`` of the old instance.
|
||||
:param dict copied:
|
||||
Storage for already copied values.
|
||||
'''
|
||||
self.__dict__.update(d)
|
||||
self.keys = copy(self.keys)
|
||||
self.checks = copy(self.checks)
|
||||
self.uspecs = copy(self.uspecs)
|
||||
self.specs = [spec.copy(copied) for spec in self.specs]
|
||||
return self
|
||||
|
||||
def unknown_spec(self, keyfunc, spec):
|
||||
'''Define specification for non-static keys
|
||||
|
||||
This method should be used if key names cannot be determined at runtime
|
||||
or if a number of keys share identical spec (in order to not repeat it).
|
||||
:py:meth:`Spec.match` method processes dictionary in the given order:
|
||||
|
||||
* First it tries to use specifications provided at the initialization or
|
||||
by the :py:meth:`Spec.update` method.
|
||||
* If no specification for given key was provided it processes
|
||||
specifications from ``keyfunc`` argument in order they were supplied.
|
||||
Once some key matches specification supplied second ``spec`` argument
|
||||
is used to determine correctness of the value.
|
||||
|
||||
:param Spec keyfunc:
|
||||
:py:class:`Spec` instance or a regular function that returns two
|
||||
values (the same :py:meth:`Spec.match` returns). This argument is
|
||||
used to match keys that were not provided at initialization or via
|
||||
:py:meth:`Spec.update`.
|
||||
:param Spec spec:
|
||||
:py:class:`Spec` instance that will be used to check keys matched by
|
||||
``keyfunc``.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
if isinstance(keyfunc, Spec):
|
||||
self.specs.append(keyfunc)
|
||||
keyfunc = len(self.specs) - 1
|
||||
self.specs.append(spec)
|
||||
self.uspecs.append((keyfunc, len(self.specs) - 1))
|
||||
return self
|
||||
|
||||
def unknown_msg(self, msgfunc):
|
||||
'''Define message which will be used when unknown key was found
|
||||
|
||||
“Unknown” is a key that was not provided at the initialization and via
|
||||
:py:meth:`Spec.update` and did not match any ``keyfunc`` proided via
|
||||
:py:meth:`Spec.unknown_spec`.
|
||||
|
||||
:param msgfunc:
|
||||
Function that takes that unknown key as an argument and returns the
|
||||
message text. Text will appear at the top (start of the sentence).
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
self.ufailmsg = msgfunc
|
||||
return self
|
||||
|
||||
def context_message(self, msg):
|
||||
'''Define message that describes context
|
||||
|
||||
:param str msg:
|
||||
Message that describes context. Is written using the
|
||||
:py:meth:`str.format` syntax and is expected to display keyword
|
||||
parameter ``key``.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
self.cmsg = msg
|
||||
for spec in self.specs:
|
||||
if not spec.cmsg:
|
||||
spec.context_message(msg)
|
||||
return self
|
||||
|
||||
def check_type(self, value, context_mark, data, context, echoerr, types):
|
||||
'''Check that given value matches given type(s)
|
||||
|
||||
:param tuple types:
|
||||
List of accepted types. Since :py:class:`Spec` is supposed to
|
||||
describe JSON values only ``dict``, ``list``, ``unicode``, ``bool``,
|
||||
``float`` and ``NoneType`` types make any sense.
|
||||
|
||||
:return: proceed, hadproblem.
|
||||
'''
|
||||
havemarks(value)
|
||||
if type(value.value) not in types:
|
||||
echoerr(
|
||||
context=self.cmsg.format(key=context.key),
|
||||
context_mark=context_mark,
|
||||
problem='{0!r} must be a {1} instance, not {2}'.format(
|
||||
value,
|
||||
', '.join((t.__name__ for t in types)),
|
||||
type(value.value).__name__
|
||||
),
|
||||
problem_mark=value.mark
|
||||
)
|
||||
return False, True
|
||||
return True, False
|
||||
|
||||
def check_func(self, value, context_mark, data, context, echoerr, func, msg_func):
|
||||
'''Check value using given function
|
||||
|
||||
:param function func:
|
||||
Callable that should accept four positional parameters:
|
||||
|
||||
#. checked value,
|
||||
#. ``data`` parameter with arbitrary data (supplied by top-level
|
||||
caller),
|
||||
#. current context and
|
||||
#. function used for echoing errors.
|
||||
|
||||
This callable should return three values:
|
||||
|
||||
#. determines whether ``check_func`` caller should proceed
|
||||
calling other checks,
|
||||
#. determines whether ``check_func`` should echo error on its own
|
||||
(it should be set to False if ``func`` echoes error itself) and
|
||||
#. determines whether function has found some errors in the checked
|
||||
value.
|
||||
|
||||
:param function msg_func:
|
||||
Callable that takes checked value as the only positional parameter
|
||||
and returns a string that describes the problem. Only useful for
|
||||
small checker functions since it is ignored when second returned
|
||||
value is false.
|
||||
|
||||
:return: proceed, hadproblem.
|
||||
'''
|
||||
havemarks(value)
|
||||
proceed, echo, hadproblem = func(value, data, context, echoerr)
|
||||
if echo and hadproblem:
|
||||
echoerr(context=self.cmsg.format(key=context.key),
|
||||
context_mark=context_mark,
|
||||
problem=msg_func(value),
|
||||
problem_mark=value.mark)
|
||||
return proceed, hadproblem
|
||||
|
||||
def check_list(self, value, context_mark, data, context, echoerr, item_func, msg_func):
|
||||
'''Check that each value in the list matches given specification
|
||||
|
||||
:param function item_func:
|
||||
Callable like ``func`` from :py:meth:`Spec.check_func`. Unlike
|
||||
``func`` this callable is called for each value in the list and may
|
||||
be a :py:class:`Spec` object index.
|
||||
:param func msg_func:
|
||||
Callable like ``msg_func`` from :py:meth:`Spec.check_func`. Should
|
||||
accept one problematic item and is not used for :py:class:`Spec`
|
||||
object indicies in ``item_func`` method.
|
||||
|
||||
:return: proceed, hadproblem.
|
||||
'''
|
||||
havemarks(value)
|
||||
i = 0
|
||||
hadproblem = False
|
||||
for item in value:
|
||||
havemarks(item)
|
||||
if isinstance(item_func, int):
|
||||
spec = self.specs[item_func]
|
||||
proceed, fhadproblem = spec.match(
|
||||
item,
|
||||
value.mark,
|
||||
data,
|
||||
context.enter_item('list item ' + unicode(i), item),
|
||||
echoerr
|
||||
)
|
||||
else:
|
||||
proceed, echo, fhadproblem = item_func(item, data, context, echoerr)
|
||||
if echo and fhadproblem:
|
||||
echoerr(context=self.cmsg.format(key=context.key + '/list item ' + unicode(i)),
|
||||
context_mark=value.mark,
|
||||
problem=msg_func(item),
|
||||
problem_mark=item.mark)
|
||||
if fhadproblem:
|
||||
hadproblem = True
|
||||
if not proceed:
|
||||
return proceed, hadproblem
|
||||
i += 1
|
||||
return True, hadproblem
|
||||
|
||||
def check_either(self, value, context_mark, data, context, echoerr, start, end):
|
||||
'''Check that given value matches one of the given specifications
|
||||
|
||||
:param int start:
|
||||
First specification index.
|
||||
:param int end:
|
||||
Specification index that is greater by 1 then last specification
|
||||
index.
|
||||
|
||||
This method does not give an error if any specification from
|
||||
``self.specs[start:end]`` is matched by the given value.
|
||||
'''
|
||||
havemarks(value)
|
||||
new_echoerr = DelayedEchoErr(echoerr)
|
||||
|
||||
hadproblem = False
|
||||
for spec in self.specs[start:end]:
|
||||
proceed, hadproblem = spec.match(value, value.mark, data, context, new_echoerr)
|
||||
if not proceed:
|
||||
break
|
||||
if not hadproblem:
|
||||
return True, False
|
||||
|
||||
new_echoerr.echo_all()
|
||||
|
||||
return False, hadproblem
|
||||
|
||||
def check_tuple(self, value, context_mark, data, context, echoerr, start, end):
|
||||
'''Check that given value is a list with items matching specifications
|
||||
|
||||
:param int start:
|
||||
First specification index.
|
||||
:param int end:
|
||||
Specification index that is greater by 1 then last specification
|
||||
index.
|
||||
|
||||
This method checks that each item in the value list matches
|
||||
specification with index ``start + item_number``.
|
||||
'''
|
||||
havemarks(value)
|
||||
hadproblem = False
|
||||
for (i, item, spec) in zip(itertools.count(), value, self.specs[start:end]):
|
||||
proceed, ihadproblem = spec.match(
|
||||
item,
|
||||
value.mark,
|
||||
data,
|
||||
context.enter_item('tuple item ' + unicode(i), item),
|
||||
echoerr
|
||||
)
|
||||
if ihadproblem:
|
||||
hadproblem = True
|
||||
if not proceed:
|
||||
return False, hadproblem
|
||||
return True, hadproblem
|
||||
|
||||
def type(self, *args):
|
||||
'''Describe value that has one of the types given in arguments
|
||||
|
||||
:param args:
|
||||
List of accepted types. Since :py:class:`Spec` is supposed to
|
||||
describe JSON values only ``dict``, ``list``, ``unicode``, ``bool``,
|
||||
``float`` and ``NoneType`` types make any sense.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
self.checks.append(('check_type', args))
|
||||
return self
|
||||
|
||||
cmp_funcs = {
|
||||
'le': lambda x, y: x <= y,
|
||||
'lt': lambda x, y: x < y,
|
||||
'ge': lambda x, y: x >= y,
|
||||
'gt': lambda x, y: x > y,
|
||||
'eq': lambda x, y: x == y,
|
||||
}
|
||||
|
||||
cmp_msgs = {
|
||||
'le': 'lesser or equal to',
|
||||
'lt': 'lesser then',
|
||||
'ge': 'greater or equal to',
|
||||
'gt': 'greater then',
|
||||
'eq': 'equal to',
|
||||
}
|
||||
|
||||
def len(self, comparison, cint, msg_func=None):
|
||||
'''Describe value that has given length
|
||||
|
||||
:param str comparison:
|
||||
Type of the comparison. Valid values: ``le``, ``lt``, ``ge``,
|
||||
``gt``, ``eq``.
|
||||
:param int cint:
|
||||
Integer with which length is compared.
|
||||
:param function msg_func:
|
||||
Function that should accept checked value and return message that
|
||||
describes the problem with this value. Default value will emit
|
||||
something like “length of ['foo', 'bar'] is not greater then 10”.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
cmp_func = self.cmp_funcs[comparison]
|
||||
msg_func = (
|
||||
msg_func
|
||||
or (lambda value: 'length of {0!r} is not {1} {2}'.format(
|
||||
value, self.cmp_msgs[comparison], cint))
|
||||
)
|
||||
self.checks.append((
|
||||
'check_func',
|
||||
(lambda value, *args: (True, True, not cmp_func(len(value), cint))),
|
||||
msg_func
|
||||
))
|
||||
return self
|
||||
|
||||
def cmp(self, comparison, cint, msg_func=None):
|
||||
'''Describe value that is a number or string that has given property
|
||||
|
||||
:param str comparison:
|
||||
Type of the comparison. Valid values: ``le``, ``lt``, ``ge``,
|
||||
``gt``, ``eq``. This argument will restrict the number or string to
|
||||
emit True on the given comparison.
|
||||
:param cint:
|
||||
Number or string with which value is compared. Type of this
|
||||
parameter affects required type of the checked value: ``str`` and
|
||||
``unicode`` types imply ``unicode`` values, ``float`` type implies
|
||||
that value can be either ``int`` or ``float``, ``int`` type implies
|
||||
``int`` value and for any other type the behavior is undefined.
|
||||
:param function msg_func:
|
||||
Function that should accept checked value and return message that
|
||||
describes the problem with this value. Default value will emit
|
||||
something like “10 is not greater then 10”.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
if type(cint) is str:
|
||||
self.type(unicode)
|
||||
elif type(cint) is float:
|
||||
self.type(int, float)
|
||||
else:
|
||||
self.type(type(cint))
|
||||
cmp_func = self.cmp_funcs[comparison]
|
||||
msg_func = msg_func or (lambda value: '{0} is not {1} {2}'.format(value, self.cmp_msgs[comparison], cint))
|
||||
self.checks.append((
|
||||
'check_func',
|
||||
(lambda value, *args: (True, True, not cmp_func(value.value, cint))),
|
||||
msg_func
|
||||
))
|
||||
return self
|
||||
|
||||
def unsigned(self, msg_func=None):
|
||||
'''Describe unsigned integer value
|
||||
|
||||
:param function msg_func:
|
||||
Function that should accept checked value and return message that
|
||||
describes the problem with this value.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
self.type(int)
|
||||
self.checks.append((
|
||||
'check_func',
|
||||
(lambda value, *args: (True, True, value < 0)),
|
||||
(lambda value: '{0} must be greater then zero'.format(value))
|
||||
))
|
||||
return self
|
||||
|
||||
def list(self, item_func, msg_func=None):
|
||||
'''Describe list with any number of elements, each matching given spec
|
||||
|
||||
:param item_func:
|
||||
:py:class:`Spec` instance or a callable. Check out
|
||||
:py:meth:`Spec.check_list` documentation for more details. Note that
|
||||
in :py:meth:`Spec.check_list` description :py:class:`Spec` instance
|
||||
is replaced with its index in ``self.specs``.
|
||||
:param function msg_func:
|
||||
Function that should accept checked value and return message that
|
||||
describes the problem with this value. Default value will emit just
|
||||
“failed check”, which is rather indescriptive.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
self.type(list)
|
||||
if isinstance(item_func, Spec):
|
||||
self.specs.append(item_func)
|
||||
item_func = len(self.specs) - 1
|
||||
self.checks.append(('check_list', item_func, msg_func or (lambda item: 'failed check')))
|
||||
return self
|
||||
|
||||
def tuple(self, *specs):
|
||||
'''Describe list with the given number of elements, each matching corresponding spec
|
||||
|
||||
:param (Spec,) specs:
|
||||
List of specifications. Last element(s) in this list may be
|
||||
optional. Each element in this list describes element with the same
|
||||
index in the checked value. Check out :py:meth:`Spec.check_tuple`
|
||||
for more details, but note that there list of specifications is
|
||||
replaced with start and end indicies in ``self.specs``.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
self.type(list)
|
||||
|
||||
max_len = len(specs)
|
||||
min_len = max_len
|
||||
for spec in reversed(specs):
|
||||
if spec.isoptional:
|
||||
min_len -= 1
|
||||
else:
|
||||
break
|
||||
if max_len == min_len:
|
||||
self.len('eq', len(specs))
|
||||
else:
|
||||
self.len('ge', min_len)
|
||||
self.len('le', max_len)
|
||||
|
||||
start = len(self.specs)
|
||||
for i, spec in zip(itertools.count(), specs):
|
||||
self.specs.append(spec)
|
||||
self.checks.append(('check_tuple', start, len(self.specs)))
|
||||
return self
|
||||
|
||||
def func(self, func, msg_func=None):
|
||||
'''Describe value that is checked by the given function
|
||||
|
||||
Check out :py:meth:`Spec.check_func` documentation for more details.
|
||||
'''
|
||||
self.checks.append(('check_func', func, msg_func or (lambda value: 'failed check')))
|
||||
return self
|
||||
|
||||
def re(self, regex, msg_func=None):
|
||||
'''Describe value that is a string that matches given regular expression
|
||||
|
||||
:param str regex:
|
||||
Regular expression that should be matched by the value.
|
||||
:param function msg_func:
|
||||
Function that should accept checked value and return message that
|
||||
describes the problem with this value. Default value will emit
|
||||
something like “String "xyz" does not match "[a-f]+"”.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
self.type(unicode)
|
||||
compiled = re.compile(regex)
|
||||
msg_func = msg_func or (lambda value: 'String "{0}" does not match "{1}"'.format(value, regex))
|
||||
self.checks.append((
|
||||
'check_func',
|
||||
(lambda value, *args: (True, True, not compiled.match(value.value))),
|
||||
msg_func
|
||||
))
|
||||
return self
|
||||
|
||||
def ident(self, msg_func=None):
|
||||
'''Describe value that is an identifier like ``foo:bar`` or ``foo``
|
||||
|
||||
:param function msg_func:
|
||||
Function that should accept checked value and return message that
|
||||
describes the problem with this value. Default value will emit
|
||||
something like “String "xyz" is not an … identifier”.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
msg_func = (
|
||||
msg_func
|
||||
or (lambda value: 'String "{0}" is not an alphanumeric/underscore colon-separated identifier'.format(value))
|
||||
)
|
||||
return self.re('^\w+(?::\w+)?$', msg_func)
|
||||
|
||||
def oneof(self, collection, msg_func=None):
|
||||
'''Describe value that is equal to one of the value in the collection
|
||||
|
||||
:param set collection:
|
||||
A collection of possible values.
|
||||
:param function msg_func:
|
||||
Function that should accept checked value and return message that
|
||||
describes the problem with this value. Default value will emit
|
||||
something like “"xyz" must be one of {'abc', 'def', 'ghi'}”.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
msg_func = msg_func or (lambda value: '"{0}" must be one of {1!r}'.format(value, list(collection)))
|
||||
self.checks.append((
|
||||
'check_func',
|
||||
(lambda value, *args: (True, True, value not in collection)),
|
||||
msg_func
|
||||
))
|
||||
return self
|
||||
|
||||
def error(self, msg):
|
||||
'''Describe value that must not be there
|
||||
|
||||
Useful for giving more descriptive errors for some specific keys then
|
||||
just “found unknown key: shutdown_event” or for forbidding certain
|
||||
values when :py:meth:`Spec.unknown_spec` was used.
|
||||
|
||||
:param str msg:
|
||||
Message given for the offending value. It is formatted using
|
||||
:py:meth:`str.format` with the only positional parameter which is
|
||||
the value itself.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
self.checks.append((
|
||||
'check_func',
|
||||
(lambda *args: (True, True, True)),
|
||||
(lambda value: msg.format(value))
|
||||
))
|
||||
return self
|
||||
|
||||
def either(self, *specs):
|
||||
'''Describes value that matches one of the given specs
|
||||
|
||||
Check out :py:meth:`Spec.check_either` method documentation for more
|
||||
details, but note that there a list of specs was replaced by start and
|
||||
end indicies in ``self.specs``.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
start = len(self.specs)
|
||||
self.specs.extend(specs)
|
||||
self.checks.append(('check_either', start, len(self.specs)))
|
||||
return self
|
||||
|
||||
def optional(self):
|
||||
'''Mark value as optional
|
||||
|
||||
Only useful for key specs in :py:meth:`Spec.__init__` and
|
||||
:py:meth:`Spec.update` and some last supplied to :py:meth:`Spec.tuple`.
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
self.isoptional = True
|
||||
return self
|
||||
|
||||
def required(self):
|
||||
'''Mark value as required
|
||||
|
||||
Only useful for key specs in :py:meth:`Spec.__init__` and
|
||||
:py:meth:`Spec.update` and some last supplied to :py:meth:`Spec.tuple`.
|
||||
|
||||
.. note::
|
||||
Value is required by default. This method is only useful for
|
||||
altering existing specification (or rather its copy).
|
||||
|
||||
:return: self.
|
||||
'''
|
||||
self.isoptional = False
|
||||
return self
|
||||
|
||||
def match_checks(self, *args):
|
||||
'''Process checks registered for the given value
|
||||
|
||||
Processes only “top-level” checks: key specifications given using at the
|
||||
initialization or via :py:meth:`Spec.unknown_spec` are processed by
|
||||
:py:meth:`Spec.match`.
|
||||
|
||||
:return: proceed, hadproblem.
|
||||
'''
|
||||
hadproblem = False
|
||||
for check in self.checks:
|
||||
proceed, chadproblem = getattr(self, check[0])(*(args + check[1:]))
|
||||
if chadproblem:
|
||||
hadproblem = True
|
||||
if not proceed:
|
||||
return False, hadproblem
|
||||
return True, hadproblem
|
||||
|
||||
def match(self, value, context_mark=None, data=None, context=(), echoerr=echoerr):
|
||||
'''Check that given value matches this specification
|
||||
|
||||
:return: proceed, hadproblem.
|
||||
'''
|
||||
havemarks(value)
|
||||
proceed, hadproblem = self.match_checks(value, context_mark, data, context, echoerr)
|
||||
if proceed:
|
||||
if self.keys or self.uspecs:
|
||||
for key, vali in self.keys.items():
|
||||
valspec = self.specs[vali]
|
||||
if key in value:
|
||||
proceed, mhadproblem = valspec.match(
|
||||
value[key],
|
||||
value.mark,
|
||||
data,
|
||||
context.enter_key(value, key),
|
||||
echoerr
|
||||
)
|
||||
if mhadproblem:
|
||||
hadproblem = True
|
||||
if not proceed:
|
||||
return False, hadproblem
|
||||
else:
|
||||
if not valspec.isoptional:
|
||||
hadproblem = True
|
||||
echoerr(context=self.cmsg.format(key=context.key),
|
||||
context_mark=None,
|
||||
problem='required key is missing: {0}'.format(key),
|
||||
problem_mark=value.mark)
|
||||
for key in value.keys():
|
||||
havemarks(key)
|
||||
if key not in self.keys:
|
||||
for keyfunc, vali in self.uspecs:
|
||||
valspec = self.specs[vali]
|
||||
if isinstance(keyfunc, int):
|
||||
spec = self.specs[keyfunc]
|
||||
proceed, khadproblem = spec.match(key, context_mark, data, context, echoerr)
|
||||
else:
|
||||
proceed, khadproblem = keyfunc(key, data, context, echoerr)
|
||||
if khadproblem:
|
||||
hadproblem = True
|
||||
if proceed:
|
||||
proceed, vhadproblem = valspec.match(
|
||||
value[key],
|
||||
value.mark,
|
||||
data,
|
||||
context.enter_key(value, key),
|
||||
echoerr
|
||||
)
|
||||
if vhadproblem:
|
||||
hadproblem = True
|
||||
break
|
||||
else:
|
||||
hadproblem = True
|
||||
if self.ufailmsg:
|
||||
echoerr(context=self.cmsg.format(key=context.key),
|
||||
context_mark=None,
|
||||
problem=self.ufailmsg(key),
|
||||
problem_mark=key.mark)
|
||||
return True, hadproblem
|
||||
|
||||
def __getitem__(self, key):
|
||||
'''Get specification for the given key
|
||||
'''
|
||||
return self.specs[self.keys[key]]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
'''Set specification for the given key
|
||||
'''
|
||||
self.update(**{key: value})
|
@ -3,7 +3,7 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
|
||||
|
||||
import os
|
||||
|
||||
from powerline.bindings.vim import vim_getbufoption
|
||||
from powerline.bindings.vim import vim_getbufoption, buffer_name
|
||||
|
||||
|
||||
def help(matcher_info):
|
||||
@ -11,8 +11,8 @@ def help(matcher_info):
|
||||
|
||||
|
||||
def cmdwin(matcher_info):
|
||||
name = matcher_info['buffer'].name
|
||||
return name and os.path.basename(name) == '[Command Line]'
|
||||
name = buffer_name(matcher_info)
|
||||
return name and os.path.basename(name) == b'[Command Line]'
|
||||
|
||||
|
||||
def quickfix(matcher_info):
|
||||
|
@ -3,10 +3,13 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
|
||||
|
||||
import os
|
||||
|
||||
from powerline.bindings.vim import buffer_name
|
||||
|
||||
|
||||
try:
|
||||
import vim
|
||||
except ImportError:
|
||||
vim = object()
|
||||
pass
|
||||
else:
|
||||
vim.command('''
|
||||
function! Powerline_plugin_ctrlp_main(...)
|
||||
@ -26,5 +29,5 @@ else:
|
||||
|
||||
|
||||
def ctrlp(matcher_info):
|
||||
name = matcher_info['buffer'].name
|
||||
return name and os.path.basename(name) == 'ControlP'
|
||||
name = buffer_name(matcher_info)
|
||||
return name and os.path.basename(name) == b'ControlP'
|
||||
|
@ -3,12 +3,14 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
|
||||
|
||||
import os
|
||||
|
||||
from powerline.bindings.vim import buffer_name
|
||||
|
||||
|
||||
def gundo(matcher_info):
|
||||
name = matcher_info['buffer'].name
|
||||
return name and os.path.basename(name) == '__Gundo__'
|
||||
name = buffer_name(matcher_info)
|
||||
return name and os.path.basename(name) == b'__Gundo__'
|
||||
|
||||
|
||||
def gundo_preview(matcher_info):
|
||||
name = matcher_info['buffer'].name
|
||||
return name and os.path.basename(name) == '__Gundo_Preview__'
|
||||
name = buffer_name(matcher_info)
|
||||
return name and os.path.basename(name) == b'__Gundo_Preview__'
|
||||
|
@ -4,7 +4,12 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
|
||||
import os
|
||||
import re
|
||||
|
||||
from powerline.bindings.vim import buffer_name
|
||||
|
||||
|
||||
NERD_TREE_RE = re.compile(b'NERD_TREE_\\d+')
|
||||
|
||||
|
||||
def nerdtree(matcher_info):
|
||||
name = matcher_info['buffer'].name
|
||||
return name and re.match(r'NERD_tree_\d+', os.path.basename(name))
|
||||
name = buffer_name(matcher_info)
|
||||
return name and NERD_TREE_RE.match(os.path.basename(name))
|
||||
|
@ -248,7 +248,7 @@ class Renderer(object):
|
||||
current_width = 0
|
||||
|
||||
if not width:
|
||||
# No width specified, so we don't need to crop or pad anything
|
||||
# No width specified, so we don’t need to crop or pad anything
|
||||
if output_width:
|
||||
current_width = self._render_length(theme, segments, self.compute_divider_widths(theme))
|
||||
return construct_returned_value(''.join([
|
||||
|
@ -14,24 +14,24 @@ class I3barRenderer(Renderer):
|
||||
|
||||
@staticmethod
|
||||
def hlstyle(*args, **kwargs):
|
||||
# We don't need to explicitly reset attributes, so skip those calls
|
||||
# We don’t need to explicitly reset attributes, so skip those calls
|
||||
return ''
|
||||
|
||||
def hl(self, contents, fg=None, bg=None, attr=None):
|
||||
segment = {
|
||||
"full_text": contents,
|
||||
"separator": False,
|
||||
"separator_block_width": 0, # no seperators
|
||||
'full_text': contents,
|
||||
'separator': False,
|
||||
'separator_block_width': 0, # no seperators
|
||||
}
|
||||
|
||||
if fg is not None:
|
||||
if fg is not False and fg[1] is not False:
|
||||
segment['color'] = "#{0:06x}".format(fg[1])
|
||||
segment['color'] = '#{0:06x}'.format(fg[1])
|
||||
if bg is not None:
|
||||
if bg is not False and bg[1] is not False:
|
||||
segment['background_color'] = "#{0:06x}".format(bg[1])
|
||||
# i3bar "pseudo json" requires one line at a time
|
||||
return json.dumps(segment) + ",\n"
|
||||
segment['background_color'] = '#{0:06x}'.format(bg[1])
|
||||
# i3bar “pseudo json” requires one line at a time
|
||||
return json.dumps(segment) + ',\n'
|
||||
|
||||
|
||||
renderer = I3barRenderer
|
||||
|
@ -12,7 +12,7 @@ class PangoMarkupRenderer(Renderer):
|
||||
|
||||
@staticmethod
|
||||
def hlstyle(*args, **kwargs):
|
||||
# We don't need to explicitly reset attributes, so skip those calls
|
||||
# We don’t need to explicitly reset attributes, so skip those calls
|
||||
return ''
|
||||
|
||||
def hl(self, contents, fg=None, bg=None, attr=None):
|
||||
|
@ -82,7 +82,7 @@ class ShellRenderer(Renderer):
|
||||
|
||||
If an argument is None, the argument is ignored. If an argument is
|
||||
False, the argument is reset to the terminal defaults. If an argument
|
||||
is a valid color or attribute, it's added to the ANSI escape code.
|
||||
is a valid color or attribute, it’s added to the ANSI escape code.
|
||||
'''
|
||||
ansi = [0]
|
||||
if fg is not None:
|
||||
|
@ -13,7 +13,7 @@ class TmuxRenderer(Renderer):
|
||||
|
||||
def hlstyle(self, fg=None, bg=None, attr=None):
|
||||
'''Highlight a segment.'''
|
||||
# We don't need to explicitly reset attributes, so skip those calls
|
||||
# We don’t need to explicitly reset attributes, so skip those calls
|
||||
if not attr and not bg and not fg:
|
||||
return ''
|
||||
tmux_attr = []
|
||||
|
@ -9,10 +9,10 @@ from powerline.bindings.vim import vim_get_func, vim_getoption, environ, current
|
||||
from powerline.renderer import Renderer
|
||||
from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE
|
||||
from powerline.theme import Theme
|
||||
from powerline.lib.unicode import unichr
|
||||
from powerline.lib.unicode import unichr, register_strwidth_error
|
||||
|
||||
|
||||
vim_mode = vim_get_func('mode', rettype=str)
|
||||
vim_mode = vim_get_func('mode', rettype='unicode')
|
||||
if int(vim.eval('v:version')) >= 702:
|
||||
_vim_mode = vim_mode
|
||||
vim_mode = lambda: _vim_mode(1)
|
||||
@ -41,6 +41,8 @@ class VimRenderer(Renderer):
|
||||
super(VimRenderer, self).__init__(*args, **kwargs)
|
||||
self.hl_groups = {}
|
||||
self.prev_highlight = None
|
||||
self.strwidth_error_name = register_strwidth_error(self.strwidth)
|
||||
self.encoding = vim.eval('&encoding')
|
||||
|
||||
def shutdown(self):
|
||||
self.theme.shutdown()
|
||||
@ -71,11 +73,10 @@ class VimRenderer(Renderer):
|
||||
|
||||
if hasattr(vim, 'strwidth'):
|
||||
if sys.version_info < (3,):
|
||||
@staticmethod
|
||||
def strwidth(string):
|
||||
def strwidth(self, string):
|
||||
# Does not work with tabs, but neither is strwidth from default
|
||||
# renderer
|
||||
return vim.strwidth(string.encode('utf-8'))
|
||||
return vim.strwidth(string.encode(self.encoding, 'replace'))
|
||||
else:
|
||||
@staticmethod
|
||||
def strwidth(string):
|
||||
@ -101,6 +102,7 @@ class VimRenderer(Renderer):
|
||||
winnr=winnr,
|
||||
buffer=window.buffer,
|
||||
tabpage=current_tabpage(),
|
||||
encoding=self.encoding,
|
||||
)
|
||||
segment_info['tabnr'] = segment_info['tabpage'].number
|
||||
segment_info['bufnr'] = segment_info['buffer'].number
|
||||
@ -115,6 +117,7 @@ class VimRenderer(Renderer):
|
||||
segment_info=segment_info,
|
||||
matcher_info=(None if is_tabline else segment_info),
|
||||
)
|
||||
statusline = statusline.encode(self.encoding, self.strwidth_error_name)
|
||||
return statusline
|
||||
|
||||
def reset_highlight(self):
|
||||
@ -125,7 +128,7 @@ class VimRenderer(Renderer):
|
||||
|
||||
If an argument is None, the argument is ignored. If an argument is
|
||||
False, the argument is reset to the terminal defaults. If an argument
|
||||
is a valid color or attribute, it's added to the vim highlight group.
|
||||
is a valid color or attribute, it’s added to the vim highlight group.
|
||||
'''
|
||||
# In order not to hit E541 two consequent identical highlighting
|
||||
# specifiers may be squashed into one.
|
||||
@ -134,7 +137,7 @@ class VimRenderer(Renderer):
|
||||
return ''
|
||||
self.prev_highlight = (fg, bg, attr)
|
||||
|
||||
# We don't need to explicitly reset attributes in vim, so skip those
|
||||
# We don’t need to explicitly reset attributes in vim, so skip those
|
||||
# calls
|
||||
if not attr and not bg and not fg:
|
||||
return ''
|
||||
|
File diff suppressed because it is too large
Load Diff
26
powerline/segments/common/__init__.py
Normal file
26
powerline/segments/common/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
|
||||
# DEPRECATED MODULE. Do not add any segments below. Do not remove existing
|
||||
# segments as well until next major release.
|
||||
|
||||
|
||||
from powerline.segments.common.vcs import branch # NOQA
|
||||
from powerline.segments.common.sys import cpu_load_percent # NOQA
|
||||
from powerline.segments.common.sys import uptime # NOQA
|
||||
from powerline.segments.common.sys import system_load # NOQA
|
||||
from powerline.segments.common.net import hostname # NOQA
|
||||
from powerline.segments.common.net import external_ip # NOQA
|
||||
from powerline.segments.common.net import internal_ip # NOQA
|
||||
from powerline.segments.common.net import network_load # NOQA
|
||||
from powerline.segments.common.env import cwd # NOQA
|
||||
from powerline.segments.common.env import user # NOQA
|
||||
from powerline.segments.common.env import environment # NOQA
|
||||
from powerline.segments.common.env import virtualenv # NOQA
|
||||
from powerline.segments.common.bat import battery # NOQA
|
||||
from powerline.segments.common.wthr import weather # NOQA
|
||||
from powerline.segments.common.time import date # NOQA
|
||||
from powerline.segments.common.time import fuzzy_time # NOQA
|
||||
from powerline.segments.common.mail import email_imap_alert # NOQA
|
||||
from powerline.segments.common.players import now_playing # NOQA
|
222
powerline/segments/common/bat.py
Normal file
222
powerline/segments/common/bat.py
Normal file
@ -0,0 +1,222 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
from powerline.lib.shell import run_cmd
|
||||
|
||||
|
||||
# XXX Warning: module name must not be equal to the segment name as long as this
|
||||
# segment is imported into powerline.segments.common module.
|
||||
|
||||
|
||||
def _get_battery(pl):
|
||||
try:
|
||||
import dbus
|
||||
except ImportError:
|
||||
pl.debug('Not using DBUS+UPower as dbus is not available')
|
||||
else:
|
||||
try:
|
||||
bus = dbus.SystemBus()
|
||||
except Exception as e:
|
||||
pl.exception('Failed to connect to system bus: {0}', str(e))
|
||||
else:
|
||||
interface = 'org.freedesktop.UPower'
|
||||
try:
|
||||
up = bus.get_object(interface, '/org/freedesktop/UPower')
|
||||
except dbus.exceptions.DBusException as e:
|
||||
if getattr(e, '_dbus_error_name', '').endswidth('ServiceUnknown'):
|
||||
pl.debug('Not using DBUS+UPower as UPower is not available via dbus')
|
||||
else:
|
||||
pl.exception('Failed to get UPower service with dbus: {0}', str(e))
|
||||
else:
|
||||
devinterface = 'org.freedesktop.DBus.Properties'
|
||||
devtype_name = interface + '.Device'
|
||||
for devpath in up.EnumerateDevices(dbus_interface=interface):
|
||||
dev = bus.get_object(interface, devpath)
|
||||
devget = lambda what: dev.Get(
|
||||
devtype_name,
|
||||
what,
|
||||
dbus_interface=devinterface
|
||||
)
|
||||
if int(devget('Type')) != 2:
|
||||
pl.debug('Not using DBUS+UPower with {0}: invalid type', devpath)
|
||||
continue
|
||||
if not bool(devget('IsPresent')):
|
||||
pl.debug('Not using DBUS+UPower with {0}: not present', devpath)
|
||||
continue
|
||||
if not bool(devget('PowerSupply')):
|
||||
pl.debug('Not using DBUS+UPower with {0}: not a power supply', devpath)
|
||||
continue
|
||||
pl.debug('Using DBUS+UPower with {0}', devpath)
|
||||
return lambda pl: float(
|
||||
dbus.Interface(dev, dbus_interface=devinterface).Get(
|
||||
devtype_name,
|
||||
'Percentage'
|
||||
)
|
||||
)
|
||||
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'
|
||||
for linux_bat in os.listdir('/sys/class/power_supply'):
|
||||
cap_path = linux_bat_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)
|
||||
|
||||
def _get_capacity(pl):
|
||||
with open(cap_path, 'r') as f:
|
||||
return int(float(f.readline().split()[0]))
|
||||
|
||||
return _get_capacity
|
||||
pl.debug('Not using /sys/class/power_supply as no batteries were found')
|
||||
else:
|
||||
pl.debug('Not using /sys/class/power_supply: no directory')
|
||||
|
||||
try:
|
||||
from shutil import which # Python-3.3 and later
|
||||
except ImportError:
|
||||
pl.info('Using dumb “which” which only checks for file in /usr/bin')
|
||||
which = lambda f: (lambda fp: os.path.exists(fp) and fp)(os.path.join('/usr/bin', f))
|
||||
|
||||
if which('pmset'):
|
||||
pl.debug('Using pmset')
|
||||
|
||||
BATTERY_PERCENT_RE = re.compile(r'(\d+)%')
|
||||
|
||||
def _get_capacity(pl):
|
||||
battery_summary = run_cmd(pl, ['pmset', '-g', 'batt'])
|
||||
battery_percent = BATTERY_PERCENT_RE.search(battery_summary).group(1)
|
||||
return int(battery_percent)
|
||||
|
||||
return _get_capacity
|
||||
else:
|
||||
pl.debug('Not using pmset: executable not found')
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
# From http://stackoverflow.com/a/21083571/273566, reworked
|
||||
try:
|
||||
from win32com.client import GetObject
|
||||
except ImportError:
|
||||
pl.debug('Not using win32com.client as it is not available')
|
||||
else:
|
||||
try:
|
||||
wmi = GetObject('winmgmts:')
|
||||
except Exception as e:
|
||||
pl.exception('Failed to run GetObject from win32com.client: {0}', str(e))
|
||||
else:
|
||||
for battery in wmi.InstancesOf('Win32_Battery'):
|
||||
pl.debug('Using win32com.client with Win32_Battery')
|
||||
|
||||
def _get_capacity(pl):
|
||||
# http://msdn.microsoft.com/en-us/library/aa394074(v=vs.85).aspx
|
||||
return battery.EstimatedChargeRemaining
|
||||
|
||||
return _get_capacity
|
||||
pl.debug('Not using win32com.client as no batteries were found')
|
||||
|
||||
from ctypes import Structure, c_byte, c_ulong, windll, byref
|
||||
|
||||
class PowerClass(Structure):
|
||||
_fields_ = [
|
||||
('ACLineStatus', c_byte),
|
||||
('BatteryFlag', c_byte),
|
||||
('BatteryLifePercent', c_byte),
|
||||
('Reserved1', c_byte),
|
||||
('BatteryLifeTime', c_ulong),
|
||||
('BatteryFullLifeTime', c_ulong)
|
||||
]
|
||||
|
||||
def _get_capacity(pl):
|
||||
powerclass = PowerClass()
|
||||
result = windll.kernel32.GetSystemPowerStatus(byref(powerclass))
|
||||
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa372693(v=vs.85).aspx
|
||||
if result:
|
||||
return None
|
||||
return powerclass.BatteryLifePercent
|
||||
|
||||
if _get_capacity() is None:
|
||||
pl.debug('Not using GetSystemPowerStatus because it failed')
|
||||
else:
|
||||
pl.debug('Using GetSystemPowerStatus')
|
||||
|
||||
return _get_capacity
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def _get_capacity(pl):
|
||||
global _get_capacity
|
||||
|
||||
def _failing_get_capacity(pl):
|
||||
raise NotImplementedError
|
||||
|
||||
try:
|
||||
_get_capacity = _get_battery(pl)
|
||||
except NotImplementedError:
|
||||
_get_capacity = _failing_get_capacity
|
||||
except Exception as e:
|
||||
pl.exception('Exception while obtaining battery capacity getter: {0}', str(e))
|
||||
_get_capacity = _failing_get_capacity
|
||||
return _get_capacity(pl)
|
||||
|
||||
|
||||
def battery(pl, format='{capacity:3.0%}', steps=5, gamify=False, full_heart='O', empty_heart='O'):
|
||||
'''Return battery charge status.
|
||||
|
||||
:param str format:
|
||||
Percent format in case gamify is False.
|
||||
:param int steps:
|
||||
Number of discrete steps to show between 0% and 100% capacity if gamify
|
||||
is True.
|
||||
:param bool gamify:
|
||||
Measure in hearts (♥) instead of percentages. For full hearts
|
||||
``battery_full`` highlighting group is preferred, for empty hearts there
|
||||
is ``battery_empty``.
|
||||
:param str full_heart:
|
||||
Heart displayed for “full” part of battery.
|
||||
:param str empty_heart:
|
||||
Heart displayed for “used” part of battery. It is also displayed using
|
||||
another gradient level and highlighting group, so it is OK for it to be
|
||||
the same as full_heart as long as necessary highlighting groups are
|
||||
defined.
|
||||
|
||||
``battery_gradient`` and ``battery`` groups are used in any case, first is
|
||||
preferred.
|
||||
|
||||
Highlight groups used: ``battery_full`` or ``battery_gradient`` (gradient) or ``battery``, ``battery_empty`` or ``battery_gradient`` (gradient) or ``battery``.
|
||||
'''
|
||||
try:
|
||||
capacity = _get_capacity(pl)
|
||||
except NotImplementedError:
|
||||
pl.info('Unable to get battery capacity.')
|
||||
return None
|
||||
ret = []
|
||||
if gamify:
|
||||
denom = int(steps)
|
||||
numer = int(denom * capacity / 100)
|
||||
ret.append({
|
||||
'contents': full_heart * numer,
|
||||
'draw_inner_divider': False,
|
||||
'highlight_group': ['battery_full', 'battery_gradient', 'battery'],
|
||||
# Using zero as “nothing to worry about”: it is least alert color.
|
||||
'gradient_level': 0,
|
||||
})
|
||||
ret.append({
|
||||
'contents': empty_heart * (denom - numer),
|
||||
'draw_inner_divider': False,
|
||||
'highlight_group': ['battery_empty', 'battery_gradient', 'battery'],
|
||||
# Using a hundred as it is most alert color.
|
||||
'gradient_level': 100,
|
||||
})
|
||||
else:
|
||||
ret.append({
|
||||
'contents': format.format(capacity=(capacity / 100.0)),
|
||||
'highlight_group': ['battery_gradient', 'battery'],
|
||||
# Gradients are “least alert – most alert” by default, capacity has
|
||||
# the opposite semantics.
|
||||
'gradient_level': 100 - capacity,
|
||||
})
|
||||
return ret
|
168
powerline/segments/common/env.py
Normal file
168
powerline/segments/common/env.py
Normal file
@ -0,0 +1,168 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import os
|
||||
|
||||
from powerline.lib.unicode import out_u
|
||||
from powerline.theme import requires_segment_info
|
||||
from powerline.segments import Segment, with_docstring
|
||||
|
||||
|
||||
@requires_segment_info
|
||||
def environment(pl, segment_info, variable=None):
|
||||
'''Return the value of any defined environment variable
|
||||
|
||||
:param string variable:
|
||||
The environment variable to return if found
|
||||
'''
|
||||
return segment_info['environ'].get(variable, None)
|
||||
|
||||
|
||||
@requires_segment_info
|
||||
def virtualenv(pl, segment_info):
|
||||
'''Return the name of the current Python virtualenv.'''
|
||||
return os.path.basename(segment_info['environ'].get('VIRTUAL_ENV', '')) or None
|
||||
|
||||
|
||||
@requires_segment_info
|
||||
class CwdSegment(Segment):
|
||||
def argspecobjs(self):
|
||||
for obj in super(CwdSegment, self).argspecobjs():
|
||||
yield obj
|
||||
yield 'get_shortened_path', self.get_shortened_path
|
||||
|
||||
def omitted_args(self, name, method):
|
||||
if method is self.get_shortened_path:
|
||||
return (0, 1, 2)
|
||||
else:
|
||||
return super(CwdSegment, self).omitted_args(name, method)
|
||||
|
||||
def get_shortened_path(self, pl, segment_info, shorten_home=True, **kwargs):
|
||||
try:
|
||||
path = out_u(segment_info['getcwd']())
|
||||
except OSError as e:
|
||||
if e.errno == 2:
|
||||
# user most probably deleted the directory
|
||||
# this happens when removing files from Mercurial repos for example
|
||||
pl.warn('Current directory not found')
|
||||
return '[not found]'
|
||||
else:
|
||||
raise
|
||||
if shorten_home:
|
||||
home = segment_info['home']
|
||||
if home:
|
||||
home = out_u(home)
|
||||
if path.startswith(home):
|
||||
path = '~' + path[len(home):]
|
||||
return path
|
||||
|
||||
def __call__(self, pl, segment_info,
|
||||
dir_shorten_len=None,
|
||||
dir_limit_depth=None,
|
||||
use_path_separator=False,
|
||||
ellipsis='...',
|
||||
**kwargs):
|
||||
cwd = self.get_shortened_path(pl, segment_info, **kwargs)
|
||||
cwd_split = cwd.split(os.sep)
|
||||
cwd_split_len = len(cwd_split)
|
||||
cwd = [i[0:dir_shorten_len] if dir_shorten_len and i else i for i in cwd_split[:-1]] + [cwd_split[-1]]
|
||||
if dir_limit_depth and cwd_split_len > dir_limit_depth + 1:
|
||||
del(cwd[0:-dir_limit_depth])
|
||||
if ellipsis is not None:
|
||||
cwd.insert(0, ellipsis)
|
||||
ret = []
|
||||
if not cwd[0]:
|
||||
cwd[0] = '/'
|
||||
draw_inner_divider = not use_path_separator
|
||||
for part in cwd:
|
||||
if not part:
|
||||
continue
|
||||
if use_path_separator:
|
||||
part += os.sep
|
||||
ret.append({
|
||||
'contents': part,
|
||||
'divider_highlight_group': 'cwd:divider',
|
||||
'draw_inner_divider': draw_inner_divider,
|
||||
})
|
||||
ret[-1]['highlight_group'] = ['cwd:current_folder', 'cwd']
|
||||
if use_path_separator:
|
||||
ret[-1]['contents'] = ret[-1]['contents'][:-1]
|
||||
if len(ret) > 1 and ret[0]['contents'][0] == os.sep:
|
||||
ret[0]['contents'] = ret[0]['contents'][1:]
|
||||
return ret
|
||||
|
||||
|
||||
cwd = with_docstring(CwdSegment(),
|
||||
'''Return the current working directory.
|
||||
|
||||
Returns a segment list to create a breadcrumb-like effect.
|
||||
|
||||
:param int dir_shorten_len:
|
||||
shorten parent directory names to this length (e.g.
|
||||
:file:`/long/path/to/powerline` → :file:`/l/p/t/powerline`)
|
||||
:param int dir_limit_depth:
|
||||
limit directory depth to this number (e.g.
|
||||
:file:`/long/path/to/powerline` → :file:`⋯/to/powerline`)
|
||||
:param bool use_path_separator:
|
||||
Use path separator in place of soft divider.
|
||||
:param bool shorten_home:
|
||||
Shorten home directory to ``~``.
|
||||
:param str ellipsis:
|
||||
Specifies what to use in place of omitted directories. Use None to not
|
||||
show this subsegment at all.
|
||||
|
||||
Divider highlight group used: ``cwd:divider``.
|
||||
|
||||
Highlight groups used: ``cwd:current_folder`` or ``cwd``. It is recommended to define all highlight groups.
|
||||
''')
|
||||
|
||||
|
||||
try:
|
||||
import psutil
|
||||
|
||||
# psutil-2.0.0: psutil.Process.username is unbound method
|
||||
if callable(psutil.Process.username):
|
||||
def _get_user():
|
||||
return psutil.Process(os.getpid()).username()
|
||||
# pre psutil-2.0.0: psutil.Process.username has type property
|
||||
else:
|
||||
def _get_user():
|
||||
return psutil.Process(os.getpid()).username
|
||||
except ImportError:
|
||||
try:
|
||||
import pwd
|
||||
except ImportError:
|
||||
from getpass import getuser as _get_user
|
||||
else:
|
||||
def _get_user():
|
||||
return pwd.getpwuid(os.geteuid()).pw_name
|
||||
|
||||
|
||||
username = False
|
||||
# os.geteuid is not available on windows
|
||||
_geteuid = getattr(os, 'geteuid', lambda: 1)
|
||||
|
||||
|
||||
def user(pl, hide_user=None):
|
||||
'''Return the current user.
|
||||
|
||||
:param str hide_user:
|
||||
Omit showing segment for users with names equal to this string.
|
||||
|
||||
Highlights the user with the ``superuser`` if the effective user ID is 0.
|
||||
|
||||
Highlight groups used: ``superuser`` or ``user``. It is recommended to define all highlight groups.
|
||||
'''
|
||||
global username
|
||||
if username is False:
|
||||
username = _get_user()
|
||||
if username is None:
|
||||
pl.warn('Failed to get username')
|
||||
return None
|
||||
if username == hide_user:
|
||||
return None
|
||||
euid = _geteuid()
|
||||
return [{
|
||||
'contents': username,
|
||||
'highlight_group': ['user'] if euid != 0 else ['superuser', 'user'],
|
||||
}]
|
74
powerline/segments/common/mail.py
Normal file
74
powerline/segments/common/mail.py
Normal file
@ -0,0 +1,74 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import re
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from powerline.lib.threaded import KwThreadedSegment
|
||||
from powerline.segments import with_docstring
|
||||
|
||||
|
||||
_IMAPKey = namedtuple('Key', 'username password server port folder')
|
||||
|
||||
|
||||
class EmailIMAPSegment(KwThreadedSegment):
|
||||
interval = 60
|
||||
|
||||
@staticmethod
|
||||
def key(username, password, server='imap.gmail.com', port=993, folder='INBOX', **kwargs):
|
||||
return _IMAPKey(username, password, server, port, folder)
|
||||
|
||||
def compute_state(self, key):
|
||||
if not key.username or not key.password:
|
||||
self.warn('Username and password are not configured')
|
||||
return None
|
||||
try:
|
||||
import imaplib
|
||||
except imaplib.IMAP4.error as e:
|
||||
unread_count = str(e)
|
||||
else:
|
||||
mail = imaplib.IMAP4_SSL(key.server, key.port)
|
||||
mail.login(key.username, key.password)
|
||||
rc, message = mail.status(key.folder, '(UNSEEN)')
|
||||
unread_str = message[0].decode('utf-8')
|
||||
unread_count = int(re.search('UNSEEN (\d+)', unread_str).group(1))
|
||||
return unread_count
|
||||
|
||||
@staticmethod
|
||||
def render_one(unread_count, max_msgs=None, **kwargs):
|
||||
if not unread_count:
|
||||
return None
|
||||
elif type(unread_count) != int or not max_msgs:
|
||||
return [{
|
||||
'contents': str(unread_count),
|
||||
'highlight_group': ['email_alert'],
|
||||
}]
|
||||
else:
|
||||
return [{
|
||||
'contents': str(unread_count),
|
||||
'highlight_group': ['email_alert_gradient', 'email_alert'],
|
||||
'gradient_level': min(unread_count * 100.0 / max_msgs, 100),
|
||||
}]
|
||||
|
||||
|
||||
email_imap_alert = with_docstring(EmailIMAPSegment(),
|
||||
'''Return unread e-mail count for IMAP servers.
|
||||
|
||||
:param str username:
|
||||
login username
|
||||
:param str password:
|
||||
login password
|
||||
:param str server:
|
||||
e-mail server
|
||||
:param int port:
|
||||
e-mail server port
|
||||
:param str folder:
|
||||
folder to check for e-mails
|
||||
:param int max_msgs:
|
||||
Maximum number of messages. If there are more messages then max_msgs then it
|
||||
will use gradient level equal to 100, otherwise gradient level is equal to
|
||||
``100 * msgs_num / max_msgs``. If not present gradient is not computed.
|
||||
|
||||
Highlight groups used: ``email_alert_gradient`` (gradient), ``email_alert``.
|
||||
''')
|
287
powerline/segments/common/net.py
Normal file
287
powerline/segments/common/net.py
Normal file
@ -0,0 +1,287 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import re
|
||||
import os
|
||||
import socket
|
||||
|
||||
from powerline.lib.url import urllib_read
|
||||
from powerline.lib.threaded import ThreadedSegment, KwThreadedSegment
|
||||
from powerline.lib.monotonic import monotonic
|
||||
from powerline.lib.humanize_bytes import humanize_bytes
|
||||
from powerline.segments import with_docstring
|
||||
from powerline.theme import requires_segment_info
|
||||
|
||||
|
||||
@requires_segment_info
|
||||
def hostname(pl, segment_info, only_if_ssh=False, exclude_domain=False):
|
||||
'''Return the current hostname.
|
||||
|
||||
:param bool only_if_ssh:
|
||||
only return the hostname if currently in an SSH session
|
||||
:param bool exclude_domain:
|
||||
return the hostname without domain if there is one
|
||||
'''
|
||||
if only_if_ssh and not segment_info['environ'].get('SSH_CLIENT'):
|
||||
return None
|
||||
if exclude_domain:
|
||||
return socket.gethostname().split('.')[0]
|
||||
return socket.gethostname()
|
||||
|
||||
|
||||
def _external_ip(query_url='http://ipv4.icanhazip.com/'):
|
||||
return urllib_read(query_url).strip()
|
||||
|
||||
|
||||
class ExternalIpSegment(ThreadedSegment):
|
||||
interval = 300
|
||||
|
||||
def set_state(self, query_url='http://ipv4.icanhazip.com/', **kwargs):
|
||||
self.query_url = query_url
|
||||
super(ExternalIpSegment, self).set_state(**kwargs)
|
||||
|
||||
def update(self, old_ip):
|
||||
return _external_ip(query_url=self.query_url)
|
||||
|
||||
def render(self, ip, **kwargs):
|
||||
if not ip:
|
||||
return None
|
||||
return [{'contents': ip, 'divider_highlight_group': 'background:divider'}]
|
||||
|
||||
|
||||
external_ip = with_docstring(ExternalIpSegment(),
|
||||
'''Return external IP address.
|
||||
|
||||
:param str query_url:
|
||||
URI to query for IP address, should return only the IP address as a text string
|
||||
|
||||
Suggested URIs:
|
||||
|
||||
* http://ipv4.icanhazip.com/
|
||||
* http://ipv6.icanhazip.com/
|
||||
* http://icanhazip.com/ (returns IPv6 address if available, else IPv4)
|
||||
|
||||
Divider highlight group used: ``background:divider``.
|
||||
''')
|
||||
|
||||
|
||||
try:
|
||||
import netifaces
|
||||
except ImportError:
|
||||
def internal_ip(pl, interface='detect', ipv=4):
|
||||
return None
|
||||
else:
|
||||
_interface_starts = {
|
||||
'eth': 10, # Regular ethernet adapters : eth1
|
||||
'enp': 10, # Regular ethernet adapters, Gentoo : enp2s0
|
||||
'ath': 9, # Atheros WiFi adapters : ath0
|
||||
'wlan': 9, # Other WiFi adapters : wlan1
|
||||
'wlp': 9, # Other WiFi adapters, Gentoo : wlp5s0
|
||||
'teredo': 1, # miredo interface : teredo
|
||||
'lo': -10, # Loopback interface : lo
|
||||
}
|
||||
|
||||
_interface_start_re = re.compile(r'^([a-z]+?)(\d|$)')
|
||||
|
||||
def _interface_key(interface):
|
||||
match = _interface_start_re.match(interface)
|
||||
if match:
|
||||
try:
|
||||
base = _interface_starts[match.group(1)] * 100
|
||||
except KeyError:
|
||||
base = 500
|
||||
if match.group(2):
|
||||
return base - int(match.group(2))
|
||||
else:
|
||||
return base
|
||||
else:
|
||||
return 0
|
||||
|
||||
def internal_ip(pl, interface='detect', ipv=4):
|
||||
if interface == 'detect':
|
||||
try:
|
||||
interface = next(iter(sorted(netifaces.interfaces(), key=_interface_key, reverse=True)))
|
||||
except StopIteration:
|
||||
pl.info('No network interfaces found')
|
||||
return None
|
||||
addrs = netifaces.ifaddresses(interface)
|
||||
try:
|
||||
return addrs[netifaces.AF_INET6 if ipv == 6 else netifaces.AF_INET][0]['addr']
|
||||
except (KeyError, IndexError):
|
||||
return None
|
||||
|
||||
|
||||
internal_ip = with_docstring(internal_ip,
|
||||
'''Return internal IP address
|
||||
|
||||
Requires ``netifaces`` module to work properly.
|
||||
|
||||
:param str interface:
|
||||
Interface on which IP will be checked. Use ``detect`` to automatically
|
||||
detect interface. In this case interfaces with lower numbers will be
|
||||
preferred over interfaces with similar names. Order of preference based on
|
||||
names:
|
||||
|
||||
#. ``eth`` and ``enp`` followed by number or the end of string.
|
||||
#. ``ath``, ``wlan`` and ``wlp`` followed by number or the end of string.
|
||||
#. ``teredo`` followed by number or the end of string.
|
||||
#. Any other interface that is not ``lo*``.
|
||||
#. ``lo`` followed by number or the end of string.
|
||||
:param int ipv:
|
||||
4 or 6 for ipv4 and ipv6 respectively, depending on which IP address you
|
||||
need exactly.
|
||||
''')
|
||||
|
||||
|
||||
try:
|
||||
import psutil
|
||||
|
||||
def _get_bytes(interface):
|
||||
try:
|
||||
io_counters = psutil.net_io_counters(pernic=True)
|
||||
except AttributeError:
|
||||
io_counters = psutil.network_io_counters(pernic=True)
|
||||
if_io = io_counters.get(interface)
|
||||
if not if_io:
|
||||
return None
|
||||
return if_io.bytes_recv, if_io.bytes_sent
|
||||
|
||||
def _get_interfaces():
|
||||
io_counters = psutil.network_io_counters(pernic=True)
|
||||
for interface, data in io_counters.items():
|
||||
if data:
|
||||
yield interface, data.bytes_recv, data.bytes_sent
|
||||
except ImportError:
|
||||
def _get_bytes(interface):
|
||||
with open('/sys/class/net/{interface}/statistics/rx_bytes'.format(interface=interface), 'rb') as file_obj:
|
||||
rx = int(file_obj.read())
|
||||
with open('/sys/class/net/{interface}/statistics/tx_bytes'.format(interface=interface), 'rb') as file_obj:
|
||||
tx = int(file_obj.read())
|
||||
return (rx, tx)
|
||||
|
||||
def _get_interfaces():
|
||||
for interface in os.listdir('/sys/class/net'):
|
||||
x = _get_bytes(interface)
|
||||
if x is not None:
|
||||
yield interface, x[0], x[1]
|
||||
|
||||
|
||||
class NetworkLoadSegment(KwThreadedSegment):
|
||||
interfaces = {}
|
||||
replace_num_pat = re.compile(r'[a-zA-Z]+')
|
||||
|
||||
@staticmethod
|
||||
def key(interface='detect', **kwargs):
|
||||
return interface
|
||||
|
||||
def compute_state(self, interface):
|
||||
if interface == 'detect':
|
||||
proc_exists = getattr(self, 'proc_exists', None)
|
||||
if proc_exists is None:
|
||||
proc_exists = self.proc_exists = os.path.exists('/proc/net/route')
|
||||
if proc_exists:
|
||||
# Look for default interface in routing table
|
||||
with open('/proc/net/route', 'rb') as f:
|
||||
for line in f.readlines():
|
||||
parts = line.split()
|
||||
if len(parts) > 1:
|
||||
iface, destination = parts[:2]
|
||||
if not destination.replace(b'0', b''):
|
||||
interface = iface.decode('utf-8')
|
||||
break
|
||||
if interface == 'detect':
|
||||
# Choose interface with most total activity, excluding some
|
||||
# well known interface names
|
||||
interface, total = 'eth0', -1
|
||||
for name, rx, tx in _get_interfaces():
|
||||
base = self.replace_num_pat.match(name)
|
||||
if None in (base, rx, tx) or base.group() in ('lo', 'vmnet', 'sit'):
|
||||
continue
|
||||
activity = rx + tx
|
||||
if activity > total:
|
||||
total = activity
|
||||
interface = name
|
||||
|
||||
try:
|
||||
idata = self.interfaces[interface]
|
||||
try:
|
||||
idata['prev'] = idata['last']
|
||||
except KeyError:
|
||||
pass
|
||||
except KeyError:
|
||||
idata = {}
|
||||
if self.run_once:
|
||||
idata['prev'] = (monotonic(), _get_bytes(interface))
|
||||
self.shutdown_event.wait(self.interval)
|
||||
self.interfaces[interface] = idata
|
||||
|
||||
idata['last'] = (monotonic(), _get_bytes(interface))
|
||||
return idata.copy()
|
||||
|
||||
def render_one(self, idata, recv_format='DL {value:>8}', sent_format='UL {value:>8}', suffix='B/s', si_prefix=False, **kwargs):
|
||||
if not idata or 'prev' not in idata:
|
||||
return None
|
||||
|
||||
t1, b1 = idata['prev']
|
||||
t2, b2 = idata['last']
|
||||
measure_interval = t2 - t1
|
||||
|
||||
if None in (b1, b2):
|
||||
return None
|
||||
|
||||
r = []
|
||||
for i, key in zip((0, 1), ('recv', 'sent')):
|
||||
format = locals()[key + '_format']
|
||||
try:
|
||||
value = (b2[i] - b1[i]) / measure_interval
|
||||
except ZeroDivisionError:
|
||||
self.warn('Measure interval zero.')
|
||||
value = 0
|
||||
max_key = key + '_max'
|
||||
is_gradient = max_key in kwargs
|
||||
hl_groups = ['network_load_' + key, 'network_load']
|
||||
if is_gradient:
|
||||
hl_groups[:0] = (group + '_gradient' for group in hl_groups)
|
||||
r.append({
|
||||
'contents': format.format(value=humanize_bytes(value, suffix, si_prefix)),
|
||||
'divider_highlight_group': 'background:divider',
|
||||
'highlight_group': hl_groups,
|
||||
})
|
||||
if is_gradient:
|
||||
max = kwargs[max_key]
|
||||
if value >= max:
|
||||
r[-1]['gradient_level'] = 100
|
||||
else:
|
||||
r[-1]['gradient_level'] = value * 100.0 / max
|
||||
|
||||
return r
|
||||
|
||||
|
||||
network_load = with_docstring(NetworkLoadSegment(),
|
||||
'''Return the network load.
|
||||
|
||||
Uses the ``psutil`` module if available for multi-platform compatibility,
|
||||
falls back to reading
|
||||
:file:`/sys/class/net/{interface}/statistics/{rx,tx}_bytes`.
|
||||
|
||||
:param str interface:
|
||||
network interface to measure (use the special value "detect" to have powerline try to auto-detect the network interface)
|
||||
:param str suffix:
|
||||
string appended to each load string
|
||||
:param bool si_prefix:
|
||||
use SI prefix, e.g. MB instead of MiB
|
||||
:param str recv_format:
|
||||
format string, receives ``value`` as argument
|
||||
:param str sent_format:
|
||||
format string, receives ``value`` as argument
|
||||
:param float recv_max:
|
||||
maximum number of received bytes per second. Is only used to compute
|
||||
gradient level
|
||||
:param float sent_max:
|
||||
maximum number of sent bytes per second. Is only used to compute gradient
|
||||
level
|
||||
|
||||
Divider highlight group used: ``background:divider``.
|
||||
|
||||
Highlight groups used: ``network_load_sent_gradient`` (gradient) or ``network_load_recv_gradient`` (gradient) or ``network_load_gradient`` (gradient), ``network_load_sent`` or ``network_load_recv`` or ``network_load``.
|
||||
''')
|
270
powerline/segments/common/players.py
Normal file
270
powerline/segments/common/players.py
Normal file
@ -0,0 +1,270 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import sys
|
||||
|
||||
from powerline.lib.shell import asrun, run_cmd
|
||||
from powerline.lib.unicode import out_u
|
||||
from powerline.segments import Segment
|
||||
|
||||
|
||||
STATE_SYMBOLS = {
|
||||
'fallback': '',
|
||||
'play': '>',
|
||||
'pause': '~',
|
||||
'stop': 'X',
|
||||
}
|
||||
|
||||
|
||||
class NowPlayingSegment(Segment):
|
||||
def __call__(self, player='mpd', format='{state_symbol} {artist} - {title} ({total})', state_symbols=STATE_SYMBOLS, **kwargs):
|
||||
player_func = getattr(self, 'player_{0}'.format(player))
|
||||
stats = {
|
||||
'state': 'fallback',
|
||||
'album': None,
|
||||
'artist': None,
|
||||
'title': None,
|
||||
'elapsed': None,
|
||||
'total': None,
|
||||
}
|
||||
func_stats = player_func(**kwargs)
|
||||
if not func_stats:
|
||||
return None
|
||||
stats.update(func_stats)
|
||||
stats['state_symbol'] = state_symbols.get(stats['state'])
|
||||
return format.format(**stats)
|
||||
|
||||
@staticmethod
|
||||
def _convert_state(state):
|
||||
state = state.lower()
|
||||
if 'play' in state:
|
||||
return 'play'
|
||||
if 'pause' in state:
|
||||
return 'pause'
|
||||
if 'stop' in state:
|
||||
return 'stop'
|
||||
|
||||
@staticmethod
|
||||
def _convert_seconds(seconds):
|
||||
return '{0:.0f}:{1:02.0f}'.format(*divmod(float(seconds), 60))
|
||||
|
||||
def player_cmus(self, pl):
|
||||
'''Return cmus player information.
|
||||
|
||||
cmus-remote -Q returns data with multi-level information i.e.
|
||||
status playing
|
||||
file <file_name>
|
||||
tag artist <artist_name>
|
||||
tag title <track_title>
|
||||
tag ..
|
||||
tag n
|
||||
set continue <true|false>
|
||||
set repeat <true|false>
|
||||
set ..
|
||||
set n
|
||||
|
||||
For the information we are looking for we don’t really care if we’re on
|
||||
the tag level or the set level. The dictionary comprehension in this
|
||||
method takes anything in ignore_levels and brings the key inside that
|
||||
to the first level of the dictionary.
|
||||
'''
|
||||
now_playing_str = run_cmd(pl, ['cmus-remote', '-Q'])
|
||||
if not now_playing_str:
|
||||
return
|
||||
ignore_levels = ('tag', 'set',)
|
||||
now_playing = dict(((token[0] if token[0] not in ignore_levels else token[1],
|
||||
(' '.join(token[1:]) if token[0] not in ignore_levels else
|
||||
' '.join(token[2:]))) for token in [line.split(' ') for line in now_playing_str.split('\n')[:-1]]))
|
||||
state = self._convert_state(now_playing.get('status'))
|
||||
return {
|
||||
'state': state,
|
||||
'album': now_playing.get('album'),
|
||||
'artist': now_playing.get('artist'),
|
||||
'title': now_playing.get('title'),
|
||||
'elapsed': self._convert_seconds(now_playing.get('position', 0)),
|
||||
'total': self._convert_seconds(now_playing.get('duration', 0)),
|
||||
}
|
||||
|
||||
def player_mpd(self, pl, host='localhost', port=6600):
|
||||
try:
|
||||
import mpd
|
||||
except ImportError:
|
||||
now_playing = run_cmd(pl, ['mpc', 'current', '-f', '%album%\n%artist%\n%title%\n%time%', '-h', str(host), '-p', str(port)])
|
||||
if not now_playing:
|
||||
return
|
||||
now_playing = now_playing.split('\n')
|
||||
return {
|
||||
'album': now_playing[0],
|
||||
'artist': now_playing[1],
|
||||
'title': now_playing[2],
|
||||
'total': now_playing[3],
|
||||
}
|
||||
else:
|
||||
client = mpd.MPDClient()
|
||||
client.connect(host, port)
|
||||
now_playing = client.currentsong()
|
||||
if not now_playing:
|
||||
return
|
||||
status = client.status()
|
||||
client.close()
|
||||
client.disconnect()
|
||||
return {
|
||||
'state': status.get('state'),
|
||||
'album': now_playing.get('album'),
|
||||
'artist': now_playing.get('artist'),
|
||||
'title': now_playing.get('title'),
|
||||
'elapsed': self._convert_seconds(now_playing.get('elapsed', 0)),
|
||||
'total': self._convert_seconds(now_playing.get('time', 0)),
|
||||
}
|
||||
|
||||
def player_dbus(self, player_name, bus_name, player_path, iface_prop, iface_player):
|
||||
try:
|
||||
import dbus
|
||||
except ImportError:
|
||||
self.exception('Could not add {0} segment: requires dbus module', player_name)
|
||||
return
|
||||
bus = dbus.SessionBus()
|
||||
try:
|
||||
player = bus.get_object(bus_name, player_path)
|
||||
iface = dbus.Interface(player, iface_prop)
|
||||
info = iface.Get(iface_player, 'Metadata')
|
||||
status = iface.Get(iface_player, 'PlaybackStatus')
|
||||
except dbus.exceptions.DBusException:
|
||||
return
|
||||
if not info:
|
||||
return
|
||||
album = out_u(info.get('xesam:album'))
|
||||
title = out_u(info.get('xesam:title'))
|
||||
artist = info.get('xesam:artist')
|
||||
state = self._convert_state(status)
|
||||
if artist:
|
||||
artist = out_u(artist[0])
|
||||
return {
|
||||
'state': state,
|
||||
'album': album,
|
||||
'artist': artist,
|
||||
'title': title,
|
||||
'total': self._convert_seconds(info.get('mpris:length') / 1e6),
|
||||
}
|
||||
|
||||
def player_spotify_dbus(self, pl):
|
||||
return self.player_dbus(
|
||||
player_name='Spotify',
|
||||
bus_name='com.spotify.qt',
|
||||
player_path='/',
|
||||
iface_prop='org.freedesktop.DBus.Properties',
|
||||
iface_player='org.freedesktop.MediaPlayer2',
|
||||
)
|
||||
|
||||
def player_clementine(self, pl):
|
||||
return self.player_dbus(
|
||||
player_name='Clementine',
|
||||
bus_name='org.mpris.MediaPlayer2.clementine',
|
||||
player_path='/org/mpris/MediaPlayer2',
|
||||
iface_prop='org.freedesktop.DBus.Properties',
|
||||
iface_player='org.mpris.MediaPlayer2.Player',
|
||||
)
|
||||
|
||||
def player_spotify_apple_script(self, pl):
|
||||
status_delimiter = '-~`/='
|
||||
ascript = '''
|
||||
tell application "System Events"
|
||||
set process_list to (name of every process)
|
||||
end tell
|
||||
|
||||
if process_list contains "Spotify" then
|
||||
tell application "Spotify"
|
||||
if player state is playing or player state is paused then
|
||||
set track_name to name of current track
|
||||
set artist_name to artist of current track
|
||||
set album_name to album of current track
|
||||
set track_length to duration of current track
|
||||
set now_playing to "" & player state & "{0}" & album_name & "{0}" & artist_name & "{0}" & track_name & "{0}" & track_length
|
||||
return now_playing
|
||||
else
|
||||
return player state
|
||||
end if
|
||||
|
||||
end tell
|
||||
else
|
||||
return "stopped"
|
||||
end if
|
||||
'''.format(status_delimiter)
|
||||
|
||||
spotify = asrun(pl, ascript)
|
||||
if not asrun:
|
||||
return None
|
||||
|
||||
spotify_status = spotify.split(status_delimiter)
|
||||
state = self._convert_state(spotify_status[0])
|
||||
if state == 'stop':
|
||||
return None
|
||||
return {
|
||||
'state': state,
|
||||
'album': spotify_status[1],
|
||||
'artist': spotify_status[2],
|
||||
'title': spotify_status[3],
|
||||
'total': self._convert_seconds(int(spotify_status[4]))
|
||||
}
|
||||
|
||||
try:
|
||||
__import__('dbus')
|
||||
except ImportError:
|
||||
if sys.platform.startswith('darwin'):
|
||||
player_spotify = player_spotify_apple_script
|
||||
else:
|
||||
player_spotify = player_spotify_dbus
|
||||
else:
|
||||
player_spotify = player_spotify_dbus
|
||||
|
||||
def player_rhythmbox(self, pl):
|
||||
now_playing = run_cmd(pl, ['rhythmbox-client', '--no-start', '--no-present', '--print-playing-format', '%at\n%aa\n%tt\n%te\n%td'])
|
||||
if not now_playing:
|
||||
return
|
||||
now_playing = now_playing.split('\n')
|
||||
return {
|
||||
'album': now_playing[0],
|
||||
'artist': now_playing[1],
|
||||
'title': now_playing[2],
|
||||
'elapsed': now_playing[3],
|
||||
'total': now_playing[4],
|
||||
}
|
||||
|
||||
def player_rdio(self, pl):
|
||||
status_delimiter = '-~`/='
|
||||
ascript = '''
|
||||
tell application "System Events"
|
||||
set rdio_active to the count(every process whose name is "Rdio")
|
||||
if rdio_active is 0 then
|
||||
return
|
||||
end if
|
||||
end tell
|
||||
tell application "Rdio"
|
||||
set rdio_name to the name of the current track
|
||||
set rdio_artist to the artist of the current track
|
||||
set rdio_album to the album of the current track
|
||||
set rdio_duration to the duration of the current track
|
||||
set rdio_state to the player state
|
||||
set rdio_elapsed to the player position
|
||||
return rdio_name & "{0}" & rdio_artist & "{0}" & rdio_album & "{0}" & rdio_elapsed & "{0}" & rdio_duration & "{0}" & rdio_state
|
||||
end tell
|
||||
'''.format(status_delimiter)
|
||||
now_playing = asrun(pl, ascript)
|
||||
if not now_playing:
|
||||
return
|
||||
now_playing = now_playing.split('\n')
|
||||
if len(now_playing) != 6:
|
||||
return
|
||||
state = self._convert_state(now_playing[5])
|
||||
total = self._convert_seconds(now_playing[4])
|
||||
elapsed = self._convert_seconds(float(now_playing[3]) * float(now_playing[4]) / 100)
|
||||
return {
|
||||
'title': now_playing[0],
|
||||
'artist': now_playing[1],
|
||||
'album': now_playing[2],
|
||||
'elapsed': elapsed,
|
||||
'total': total,
|
||||
'state': state,
|
||||
'state_symbol': self.STATE_SYMBOLS.get(state)
|
||||
}
|
||||
now_playing = NowPlayingSegment()
|
174
powerline/segments/common/sys.py
Normal file
174
powerline/segments/common/sys.py
Normal file
@ -0,0 +1,174 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import os
|
||||
|
||||
from multiprocessing import cpu_count as _cpu_count
|
||||
|
||||
from powerline.lib.threaded import ThreadedSegment
|
||||
from powerline.lib import add_divider_highlight_group
|
||||
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):
|
||||
'''Return system load average.
|
||||
|
||||
Highlights using ``system_load_good``, ``system_load_bad`` and
|
||||
``system_load_ugly`` highlighting groups, depending on the thresholds
|
||||
passed to the function.
|
||||
|
||||
:param str format:
|
||||
format string, receives ``avg`` as an argument
|
||||
:param float threshold_good:
|
||||
threshold for gradient level 0: any normalized load average below this
|
||||
value will have this gradient level.
|
||||
:param float threshold_bad:
|
||||
threshold for gradient level 100: any normalized load average above this
|
||||
value will have this gradient level. Load averages between
|
||||
``threshold_good`` and ``threshold_bad`` receive gradient level that
|
||||
indicates relative position in this interval:
|
||||
(``100 * (cur-good) / (bad-good)``).
|
||||
Note: both parameters are checked against normalized load averages.
|
||||
:param bool track_cpu_count:
|
||||
if True powerline will continuously poll the system to detect changes
|
||||
in the number of CPUs.
|
||||
|
||||
Divider highlight group used: ``background:divider``.
|
||||
|
||||
Highlight groups used: ``system_load_gradient`` (gradient) or ``system_load``.
|
||||
'''
|
||||
global cpu_count
|
||||
try:
|
||||
cpu_num = cpu_count = _cpu_count() if cpu_count is None or track_cpu_count else cpu_count
|
||||
except NotImplementedError:
|
||||
pl.warn('Unable to get CPU count: method is not implemented')
|
||||
return None
|
||||
ret = []
|
||||
for avg in os.getloadavg():
|
||||
normalized = avg / cpu_num
|
||||
if normalized < threshold_good:
|
||||
gradient_level = 0
|
||||
elif normalized < threshold_bad:
|
||||
gradient_level = (normalized - threshold_good) * 100.0 / (threshold_bad - threshold_good)
|
||||
else:
|
||||
gradient_level = 100
|
||||
ret.append({
|
||||
'contents': format.format(avg=avg),
|
||||
'highlight_group': ['system_load_gradient', 'system_load'],
|
||||
'divider_highlight_group': 'background:divider',
|
||||
'gradient_level': gradient_level,
|
||||
})
|
||||
ret[0]['contents'] += ' '
|
||||
ret[1]['contents'] += ' '
|
||||
return ret
|
||||
|
||||
|
||||
try:
|
||||
import psutil
|
||||
|
||||
class CPULoadPercentSegment(ThreadedSegment):
|
||||
interval = 1
|
||||
|
||||
def update(self, old_cpu):
|
||||
return psutil.cpu_percent(interval=None)
|
||||
|
||||
def run(self):
|
||||
while not self.shutdown_event.is_set():
|
||||
try:
|
||||
self.update_value = psutil.cpu_percent(interval=self.interval)
|
||||
except Exception as e:
|
||||
self.exception('Exception while calculating cpu_percent: {0}', str(e))
|
||||
|
||||
def render(self, cpu_percent, format='{0:.0f}%', **kwargs):
|
||||
if not cpu_percent:
|
||||
return None
|
||||
return [{
|
||||
'contents': format.format(cpu_percent),
|
||||
'gradient_level': cpu_percent,
|
||||
'highlight_group': ['cpu_load_percent_gradient', 'cpu_load_percent'],
|
||||
}]
|
||||
except ImportError:
|
||||
class CPULoadPercentSegment(ThreadedSegment):
|
||||
interval = 1
|
||||
|
||||
@staticmethod
|
||||
def startup(**kwargs):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def start():
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def shutdown():
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def render(cpu_percent, pl, format='{0:.0f}%', **kwargs):
|
||||
pl.warn('Module “psutil” is not installed, thus CPU load is not available')
|
||||
return None
|
||||
|
||||
|
||||
cpu_load_percent = with_docstring(CPULoadPercentSegment(),
|
||||
'''Return the average CPU load as a percentage.
|
||||
|
||||
Requires the ``psutil`` module.
|
||||
|
||||
:param str format:
|
||||
Output format. Accepts measured CPU load as the first argument.
|
||||
|
||||
Highlight groups used: ``cpu_load_percent_gradient`` (gradient) or ``cpu_load_percent``.
|
||||
''')
|
||||
|
||||
|
||||
if os.path.exists('/proc/uptime'):
|
||||
def _get_uptime():
|
||||
with open('/proc/uptime', 'r') as f:
|
||||
return int(float(f.readline().split()[0]))
|
||||
elif 'psutil' in globals():
|
||||
from time import time
|
||||
|
||||
def _get_uptime():
|
||||
# psutil.BOOT_TIME is not subject to clock adjustments, but time() is.
|
||||
# Thus it is a fallback to /proc/uptime reading and not the reverse.
|
||||
return int(time() - psutil.BOOT_TIME)
|
||||
else:
|
||||
def _get_uptime():
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@add_divider_highlight_group('background:divider')
|
||||
def uptime(pl, days_format='{days:d}d', hours_format=' {hours:d}h', minutes_format=' {minutes:d}m', seconds_format=' {seconds:d}s', shorten_len=3):
|
||||
'''Return system uptime.
|
||||
|
||||
:param str days_format:
|
||||
day format string, will be passed ``days`` as the argument
|
||||
:param str hours_format:
|
||||
hour format string, will be passed ``hours`` as the argument
|
||||
:param str minutes_format:
|
||||
minute format string, will be passed ``minutes`` as the argument
|
||||
:param str seconds_format:
|
||||
second format string, will be passed ``seconds`` as the argument
|
||||
:param int shorten_len:
|
||||
shorten the amount of units (days, hours, etc.) displayed
|
||||
|
||||
Divider highlight group used: ``background:divider``.
|
||||
'''
|
||||
try:
|
||||
seconds = _get_uptime()
|
||||
except NotImplementedError:
|
||||
pl.warn('Unable to get uptime. You should install psutil module')
|
||||
return None
|
||||
minutes, seconds = divmod(seconds, 60)
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
days, hours = divmod(hours, 24)
|
||||
time_formatted = list(filter(None, [
|
||||
days_format.format(days=days) if days and days_format else None,
|
||||
hours_format.format(hours=hours) if hours and hours_format else None,
|
||||
minutes_format.format(minutes=minutes) if minutes and minutes_format else None,
|
||||
seconds_format.format(seconds=seconds) if seconds and seconds_format else None,
|
||||
]))[0:shorten_len]
|
||||
return ''.join(time_formatted).strip()
|
89
powerline/segments/common/time.py
Normal file
89
powerline/segments/common/time.py
Normal file
@ -0,0 +1,89 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def date(pl, format='%Y-%m-%d', istime=False):
|
||||
'''Return the current date.
|
||||
|
||||
:param str format:
|
||||
strftime-style date format string
|
||||
:param bool istime:
|
||||
If true then segment uses ``time`` highlight group.
|
||||
|
||||
Divider highlight group used: ``time:divider``.
|
||||
|
||||
Highlight groups used: ``time`` or ``date``.
|
||||
'''
|
||||
return [{
|
||||
'contents': datetime.now().strftime(format),
|
||||
'highlight_group': (['time'] if istime else []) + ['date'],
|
||||
'divider_highlight_group': 'time:divider' if istime else None,
|
||||
}]
|
||||
|
||||
|
||||
UNICODE_TEXT_TRANSLATION = {
|
||||
ord('\''): '’',
|
||||
ord('-'): '‐',
|
||||
}
|
||||
|
||||
|
||||
def fuzzy_time(pl, unicode_text=False):
|
||||
'''Display the current time as fuzzy time, e.g. "quarter past six".
|
||||
|
||||
:param bool unicode_text:
|
||||
If true then hyphenminuses (regular ASCII ``-``) and single quotes are
|
||||
replaced with unicode dashes and apostrophes.
|
||||
'''
|
||||
hour_str = ['twelve', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven']
|
||||
minute_str = {
|
||||
5: 'five past',
|
||||
10: 'ten past',
|
||||
15: 'quarter past',
|
||||
20: 'twenty past',
|
||||
25: 'twenty-five past',
|
||||
30: 'half past',
|
||||
35: 'twenty-five to',
|
||||
40: 'twenty to',
|
||||
45: 'quarter to',
|
||||
50: 'ten to',
|
||||
55: 'five to',
|
||||
}
|
||||
special_case_str = {
|
||||
(23, 58): 'round about midnight',
|
||||
(23, 59): 'round about midnight',
|
||||
(0, 0): 'midnight',
|
||||
(0, 1): 'round about midnight',
|
||||
(0, 2): 'round about midnight',
|
||||
(12, 0): 'noon',
|
||||
}
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
try:
|
||||
return special_case_str[(now.hour, now.minute)]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
hour = now.hour
|
||||
if now.minute > 32:
|
||||
if hour == 23:
|
||||
hour = 0
|
||||
else:
|
||||
hour += 1
|
||||
if hour > 11:
|
||||
hour = hour - 12
|
||||
hour = hour_str[hour]
|
||||
|
||||
minute = int(round(now.minute / 5.0) * 5)
|
||||
if minute == 60 or minute == 0:
|
||||
result = ' '.join([hour, 'o\'clock'])
|
||||
else:
|
||||
minute = minute_str[minute]
|
||||
result = ' '.join([minute, hour])
|
||||
|
||||
if unicode_text:
|
||||
result = result.translate(UNICODE_TEXT_TRANSLATION)
|
||||
|
||||
return result
|
33
powerline/segments/common/vcs.py
Normal file
33
powerline/segments/common/vcs.py
Normal file
@ -0,0 +1,33 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
from powerline.lib.vcs import guess, tree_status
|
||||
from powerline.theme import requires_segment_info, requires_filesystem_watcher
|
||||
|
||||
|
||||
@requires_filesystem_watcher
|
||||
@requires_segment_info
|
||||
def branch(pl, segment_info, create_watcher, status_colors=False):
|
||||
'''Return the current VCS branch.
|
||||
|
||||
:param bool status_colors:
|
||||
determines whether repository status will be used to determine highlighting. Default: False.
|
||||
|
||||
Highlight groups used: ``branch_clean``, ``branch_dirty``, ``branch``.
|
||||
'''
|
||||
name = segment_info['getcwd']()
|
||||
repo = guess(path=name, create_watcher=create_watcher)
|
||||
if repo is not None:
|
||||
branch = repo.branch()
|
||||
scol = ['branch']
|
||||
if status_colors:
|
||||
try:
|
||||
status = tree_status(repo, pl)
|
||||
except Exception as e:
|
||||
pl.exception('Failed to compute tree status: {0}', str(e))
|
||||
status = '?'
|
||||
scol.insert(0, 'branch_dirty' if status and status.strip() else 'branch_clean')
|
||||
return [{
|
||||
'contents': branch,
|
||||
'highlight_group': scol,
|
||||
}]
|
229
powerline/segments/common/wthr.py
Normal file
229
powerline/segments/common/wthr.py
Normal file
@ -0,0 +1,229 @@
|
||||
# vim:fileencoding=utf-8:noet
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import json
|
||||
|
||||
from powerline.lib.url import urllib_read, urllib_urlencode
|
||||
from powerline.lib.threaded import KwThreadedSegment
|
||||
from powerline.segments import with_docstring
|
||||
|
||||
|
||||
# XXX Warning: module name must not be equal to the segment name as long as this
|
||||
# segment is imported into powerline.segments.common module.
|
||||
|
||||
|
||||
# Weather condition code descriptions available at
|
||||
# http://developer.yahoo.com/weather/#codes
|
||||
weather_conditions_codes = (
|
||||
('tornado', 'stormy'), # 0
|
||||
('tropical_storm', 'stormy'), # 1
|
||||
('hurricane', 'stormy'), # 2
|
||||
('severe_thunderstorms', 'stormy'), # 3
|
||||
('thunderstorms', 'stormy'), # 4
|
||||
('mixed_rain_and_snow', 'rainy' ), # 5
|
||||
('mixed_rain_and_sleet', 'rainy' ), # 6
|
||||
('mixed_snow_and_sleet', 'snowy' ), # 7
|
||||
('freezing_drizzle', 'rainy' ), # 8
|
||||
('drizzle', 'rainy' ), # 9
|
||||
('freezing_rain', 'rainy' ), # 10
|
||||
('showers', 'rainy' ), # 11
|
||||
('showers', 'rainy' ), # 12
|
||||
('snow_flurries', 'snowy' ), # 13
|
||||
('light_snow_showers', 'snowy' ), # 14
|
||||
('blowing_snow', 'snowy' ), # 15
|
||||
('snow', 'snowy' ), # 16
|
||||
('hail', 'snowy' ), # 17
|
||||
('sleet', 'snowy' ), # 18
|
||||
('dust', 'foggy' ), # 19
|
||||
('fog', 'foggy' ), # 20
|
||||
('haze', 'foggy' ), # 21
|
||||
('smoky', 'foggy' ), # 22
|
||||
('blustery', 'foggy' ), # 23
|
||||
('windy', ), # 24
|
||||
('cold', 'day' ), # 25
|
||||
('clouds', 'cloudy'), # 26
|
||||
('mostly_cloudy_night', 'cloudy'), # 27
|
||||
('mostly_cloudy_day', 'cloudy'), # 28
|
||||
('partly_cloudy_night', 'cloudy'), # 29
|
||||
('partly_cloudy_day', 'cloudy'), # 30
|
||||
('clear_night', 'night' ), # 31
|
||||
('sun', 'sunny' ), # 32
|
||||
('fair_night', 'night' ), # 33
|
||||
('fair_day', 'day' ), # 34
|
||||
('mixed_rain_and_hail', 'rainy' ), # 35
|
||||
('hot', 'sunny' ), # 36
|
||||
('isolated_thunderstorms', 'stormy'), # 37
|
||||
('scattered_thunderstorms', 'stormy'), # 38
|
||||
('scattered_thunderstorms', 'stormy'), # 39
|
||||
('scattered_showers', 'rainy' ), # 40
|
||||
('heavy_snow', 'snowy' ), # 41
|
||||
('scattered_snow_showers', 'snowy' ), # 42
|
||||
('heavy_snow', 'snowy' ), # 43
|
||||
('partly_cloudy', 'cloudy'), # 44
|
||||
('thundershowers', 'rainy' ), # 45
|
||||
('snow_showers', 'snowy' ), # 46
|
||||
('isolated_thundershowers', 'rainy' ), # 47
|
||||
)
|
||||
# ('day', (25, 34)),
|
||||
# ('rainy', (5, 6, 8, 9, 10, 11, 12, 35, 40, 45, 47)),
|
||||
# ('cloudy', (26, 27, 28, 29, 30, 44)),
|
||||
# ('snowy', (7, 13, 14, 15, 16, 17, 18, 41, 42, 43, 46)),
|
||||
# ('stormy', (0, 1, 2, 3, 4, 37, 38, 39)),
|
||||
# ('foggy', (19, 20, 21, 22, 23)),
|
||||
# ('sunny', (32, 36)),
|
||||
# ('night', (31, 33))):
|
||||
weather_conditions_icons = {
|
||||
'day': 'DAY',
|
||||
'blustery': 'WIND',
|
||||
'rainy': 'RAIN',
|
||||
'cloudy': 'CLOUDS',
|
||||
'snowy': 'SNOW',
|
||||
'stormy': 'STORM',
|
||||
'foggy': 'FOG',
|
||||
'sunny': 'SUN',
|
||||
'night': 'NIGHT',
|
||||
'windy': 'WINDY',
|
||||
'not_available': 'NA',
|
||||
'unknown': 'UKN',
|
||||
}
|
||||
|
||||
temp_conversions = {
|
||||
'C': lambda temp: temp,
|
||||
'F': lambda temp: (temp * 9 / 5) + 32,
|
||||
'K': lambda temp: temp + 273.15,
|
||||
}
|
||||
|
||||
# Note: there are also unicode characters for units: ℃, ℉ and K
|
||||
temp_units = {
|
||||
'C': '°C',
|
||||
'F': '°F',
|
||||
'K': 'K',
|
||||
}
|
||||
|
||||
|
||||
class WeatherSegment(KwThreadedSegment):
|
||||
interval = 600
|
||||
default_location = None
|
||||
location_urls = {}
|
||||
|
||||
@staticmethod
|
||||
def key(location_query=None, **kwargs):
|
||||
return location_query
|
||||
|
||||
def get_request_url(self, location_query):
|
||||
try:
|
||||
return self.location_urls[location_query]
|
||||
except KeyError:
|
||||
if location_query is None:
|
||||
location_data = json.loads(urllib_read('http://freegeoip.net/json/'))
|
||||
location = ','.join((
|
||||
location_data['city'],
|
||||
location_data['region_name'],
|
||||
location_data['country_code']
|
||||
))
|
||||
self.info('Location returned by freegeoip is {0}', location)
|
||||
else:
|
||||
location = location_query
|
||||
query_data = {
|
||||
'q':
|
||||
'use "https://raw.githubusercontent.com/yql/yql-tables/master/weather/weather.bylocation.xml" as we;'
|
||||
'select * from we where location="{0}" and unit="c"'.format(location).encode('utf-8'),
|
||||
'format': 'json',
|
||||
}
|
||||
self.location_urls[location_query] = url = (
|
||||
'http://query.yahooapis.com/v1/public/yql?' + urllib_urlencode(query_data))
|
||||
return url
|
||||
|
||||
def compute_state(self, location_query):
|
||||
url = self.get_request_url(location_query)
|
||||
raw_response = urllib_read(url)
|
||||
if not raw_response:
|
||||
self.error('Failed to get response')
|
||||
return
|
||||
response = json.loads(raw_response)
|
||||
condition = response['query']['results']['weather']['rss']['channel']['item']['condition']
|
||||
condition_code = int(condition['code'])
|
||||
temp = float(condition['temp'])
|
||||
|
||||
try:
|
||||
icon_names = weather_conditions_codes[condition_code]
|
||||
except IndexError:
|
||||
if condition_code == 3200:
|
||||
icon_names = ('not_available',)
|
||||
self.warn('Weather is not available for location {0}', self.location)
|
||||
else:
|
||||
icon_names = ('unknown',)
|
||||
self.error('Unknown condition code: {0}', condition_code)
|
||||
|
||||
return (temp, icon_names)
|
||||
|
||||
def render_one(self, weather, icons=None, unit='C', temp_format=None, temp_coldest=-30, temp_hottest=40, **kwargs):
|
||||
if not weather:
|
||||
return None
|
||||
|
||||
temp, icon_names = weather
|
||||
|
||||
for icon_name in icon_names:
|
||||
if icons:
|
||||
if icon_name in icons:
|
||||
icon = icons[icon_name]
|
||||
break
|
||||
else:
|
||||
icon = weather_conditions_icons[icon_names[-1]]
|
||||
|
||||
temp_format = temp_format or ('{temp:.0f}' + temp_units[unit])
|
||||
converted_temp = temp_conversions[unit](temp)
|
||||
if temp <= temp_coldest:
|
||||
gradient_level = 0
|
||||
elif temp >= temp_hottest:
|
||||
gradient_level = 100
|
||||
else:
|
||||
gradient_level = (temp - temp_coldest) * 100.0 / (temp_hottest - temp_coldest)
|
||||
groups = ['weather_condition_' + icon_name for icon_name in icon_names] + ['weather_conditions', 'weather']
|
||||
return [
|
||||
{
|
||||
'contents': icon + ' ',
|
||||
'highlight_group': groups,
|
||||
'divider_highlight_group': 'background:divider',
|
||||
},
|
||||
{
|
||||
'contents': temp_format.format(temp=converted_temp),
|
||||
'highlight_group': ['weather_temp_gradient', 'weather_temp', 'weather'],
|
||||
'divider_highlight_group': 'background:divider',
|
||||
'gradient_level': gradient_level,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
weather = with_docstring(WeatherSegment(),
|
||||
'''Return weather from Yahoo! Weather.
|
||||
|
||||
Uses GeoIP lookup from http://freegeoip.net/ to automatically determine
|
||||
your current location. This should be changed if you’re in a VPN or if your
|
||||
IP address is registered at another location.
|
||||
|
||||
Returns a list of colorized icon and temperature segments depending on
|
||||
weather conditions.
|
||||
|
||||
:param str unit:
|
||||
temperature unit, can be one of ``F``, ``C`` or ``K``
|
||||
:param str location_query:
|
||||
location query for your current location, e.g. ``oslo, norway``
|
||||
:param dict icons:
|
||||
dict for overriding default icons, e.g. ``{'heavy_snow' : u'❆'}``
|
||||
:param str temp_format:
|
||||
format string, receives ``temp`` as an argument. Should also hold unit.
|
||||
:param float temp_coldest:
|
||||
coldest temperature. Any temperature below it will have gradient level equal
|
||||
to zero.
|
||||
:param float temp_hottest:
|
||||
hottest temperature. Any temperature above it will have gradient level equal
|
||||
to 100. Temperatures between ``temp_coldest`` and ``temp_hottest`` receive
|
||||
gradient level that indicates relative position in this interval
|
||||
(``100 * (cur-coldest) / (hottest-coldest)``).
|
||||
|
||||
Divider highlight group used: ``background:divider``.
|
||||
|
||||
Highlight groups used: ``weather_conditions`` or ``weather``, ``weather_temp_gradient`` (gradient) or ``weather``.
|
||||
Also uses ``weather_conditions_{condition}`` for all weather conditions supported by Yahoo.
|
||||
''')
|
@ -3,7 +3,8 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
|
||||
|
||||
from powerline.theme import requires_segment_info
|
||||
from powerline.segments import with_docstring
|
||||
from powerline.segments.common import CwdSegment
|
||||
from powerline.segments.common.env import CwdSegment
|
||||
from powerline.lib.unicode import out_u
|
||||
|
||||
|
||||
@requires_segment_info
|
||||
@ -136,7 +137,7 @@ class ShellCwdSegment(CwdSegment):
|
||||
def get_shortened_path(self, pl, segment_info, use_shortened_path=True, **kwargs):
|
||||
if use_shortened_path:
|
||||
try:
|
||||
return segment_info['shortened_path']
|
||||
return out_u(segment_info['shortened_path'])
|
||||
except KeyError:
|
||||
pass
|
||||
return super(ShellCwdSegment, self).get_shortened_path(pl, segment_info, **kwargs)
|
||||
|
@ -28,13 +28,11 @@ except ImportError:
|
||||
|
||||
|
||||
vim_funcs = {
|
||||
'virtcol': vim_get_func('virtcol', rettype=int),
|
||||
'virtcol': vim_get_func('virtcol', rettype='int'),
|
||||
'getpos': vim_get_func('getpos'),
|
||||
'fnamemodify': vim_get_func('fnamemodify'),
|
||||
'expand': vim_get_func('expand'),
|
||||
'bufnr': vim_get_func('bufnr', rettype=int),
|
||||
'line2byte': vim_get_func('line2byte', rettype=int),
|
||||
'line': vim_get_func('line', rettype=int),
|
||||
'fnamemodify': vim_get_func('fnamemodify', rettype='bytes'),
|
||||
'line2byte': vim_get_func('line2byte', rettype='int'),
|
||||
'line': vim_get_func('line', rettype='int'),
|
||||
}
|
||||
|
||||
vim_modes = {
|
||||
@ -135,8 +133,8 @@ def visual_range(pl, segment_info, CTRL_V_text='{rows} x {vcols}', v_text_onelin
|
||||
vcols Number of virtual columns in the selection
|
||||
========= =============================================================
|
||||
'''
|
||||
sline, scol, soff = [int(v) for v in vim_funcs['getpos']("v")[1:]]
|
||||
eline, ecol, eoff = [int(v) for v in vim_funcs['getpos'](".")[1:]]
|
||||
sline, scol, soff = [int(v) for v in vim_funcs['getpos']('v')[1:]]
|
||||
eline, ecol, eoff = [int(v) for v in vim_funcs['getpos']('.')[1:]]
|
||||
svcol = vim_funcs['virtcol']([sline, scol, soff])
|
||||
evcol = vim_funcs['virtcol']([eline, ecol, eoff])
|
||||
rows = abs(eline - sline) + 1
|
||||
@ -225,7 +223,7 @@ def file_scheme(pl, segment_info):
|
||||
name will look like :file:`zipfile:/path/to/archive.zip::file.txt`.
|
||||
``file_scheme`` segment will catch ``zipfile`` part here.
|
||||
'''
|
||||
name = buffer_name(segment_info['buffer'])
|
||||
name = buffer_name(segment_info)
|
||||
if not name:
|
||||
return None
|
||||
match = SCHEME_RE.match(name)
|
||||
@ -254,7 +252,7 @@ def file_directory(pl, segment_info, remove_scheme=True, shorten_user=True, shor
|
||||
Shorten all directories in :file:`/home/` to :file:`~user/` instead of
|
||||
:file:`/home/user/`. Does not work for files with scheme present.
|
||||
'''
|
||||
name = buffer_name(segment_info['buffer'])
|
||||
name = buffer_name(segment_info)
|
||||
if not name:
|
||||
return None
|
||||
match = SCHEME_RE.match(name)
|
||||
@ -271,7 +269,7 @@ def file_directory(pl, segment_info, remove_scheme=True, shorten_user=True, shor
|
||||
return None
|
||||
if shorten_home and file_directory.startswith('/home/'):
|
||||
file_directory = b'~' + file_directory[6:]
|
||||
file_directory = file_directory.decode('utf-8', 'powerline_vim_strtrans_error')
|
||||
file_directory = file_directory.decode(segment_info['encoding'], 'powerline_vim_strtrans_error')
|
||||
return file_directory + os.sep
|
||||
|
||||
|
||||
@ -286,7 +284,7 @@ def file_name(pl, segment_info, display_no_file=False, no_file_text='[No file]')
|
||||
|
||||
Highlight groups used: ``file_name_no_file`` or ``file_name``, ``file_name``.
|
||||
'''
|
||||
name = buffer_name(segment_info['buffer'])
|
||||
name = buffer_name(segment_info)
|
||||
if not name:
|
||||
if display_no_file:
|
||||
return [{
|
||||
@ -295,7 +293,7 @@ def file_name(pl, segment_info, display_no_file=False, no_file_text='[No file]')
|
||||
}]
|
||||
else:
|
||||
return None
|
||||
return os.path.basename(name).decode('utf-8', 'powerline_vim_strtrans_error')
|
||||
return os.path.basename(name).decode(segment_info['encoding'], 'powerline_vim_strtrans_error')
|
||||
|
||||
|
||||
@window_cached
|
||||
@ -306,7 +304,7 @@ def file_size(pl, suffix='B', si_prefix=False):
|
||||
string appended to the file size
|
||||
:param bool si_prefix:
|
||||
use SI prefix, e.g. MB instead of MiB
|
||||
:return: file size or None if the file isn't saved or if the size is too big to fit in a number
|
||||
:return: file size or None if the file isn’t saved or if the size is too big to fit in a number
|
||||
'''
|
||||
# Note: returns file size in &encoding, not in &fileencoding. But returned
|
||||
# size is updated immediately; and it is valid for any buffer
|
||||
@ -470,10 +468,13 @@ def modified_buffers(pl, text='+ ', join_str=','):
|
||||
:param str join_str:
|
||||
string to use for joining the modified buffer list
|
||||
'''
|
||||
buffer_len = vim_funcs['bufnr']('$')
|
||||
buffer_mod = [str(bufnr) for bufnr in range(1, buffer_len + 1) if int(getbufvar(bufnr, '&modified') or 0)]
|
||||
if buffer_mod:
|
||||
return text + join_str.join(buffer_mod)
|
||||
buffer_mod_text = join_str.join((
|
||||
str(buffer.number)
|
||||
for buffer in vim.buffers
|
||||
if int(vim_getbufoption({'buffer': buffer, 'bufnr': buffer.number}, 'modified'))
|
||||
))
|
||||
if buffer_mod_text:
|
||||
return text + buffer_mod_text
|
||||
return None
|
||||
|
||||
|
||||
@ -489,7 +490,7 @@ def branch(pl, segment_info, create_watcher, status_colors=False):
|
||||
|
||||
Divider highlight group used: ``branch:divider``.
|
||||
'''
|
||||
name = segment_info['buffer'].name
|
||||
name = buffer_name(segment_info)
|
||||
skip = not (name and (not vim_getbufoption(segment_info, 'buftype')))
|
||||
if not skip:
|
||||
repo = guess(path=name, create_watcher=create_watcher)
|
||||
@ -513,7 +514,7 @@ def file_vcs_status(pl, segment_info, create_watcher):
|
||||
|
||||
Highlight groups used: ``file_vcs_status``.
|
||||
'''
|
||||
name = segment_info['buffer'].name
|
||||
name = buffer_name(segment_info)
|
||||
skip = not (name and (not vim_getbufoption(segment_info, 'buftype')))
|
||||
if not skip:
|
||||
repo = guess(path=name, create_watcher=create_watcher)
|
||||
|
@ -10,7 +10,21 @@ from powerline.segments.vim import window_cached
|
||||
|
||||
|
||||
@window_cached
|
||||
def current_tag(pl):
|
||||
def current_tag(pl, flags='s'):
|
||||
'''Return tag that is near the cursor.
|
||||
|
||||
:param str flags:
|
||||
Specifies additional properties of the displayed tag. Supported values:
|
||||
|
||||
* s - display complete signature
|
||||
* f - display the full hierarchy of the tag
|
||||
* p - display the raw prototype
|
||||
|
||||
More info in the `official documentation`_ (search for
|
||||
“tagbar#currenttag”).
|
||||
|
||||
.. _`official documentation`: https://github.com/majutsushi/tagbar/blob/master/doc/tagbar.txt
|
||||
'''
|
||||
if not int(vim.eval('exists(":Tagbar")')):
|
||||
return
|
||||
return vim.eval('tagbar#currenttag("%s", "")')
|
||||
return None
|
||||
return vim.eval('tagbar#currenttag("%s", "", "{0}")'.format(flags))
|
||||
|
@ -163,6 +163,6 @@ class Theme(object):
|
||||
elif segment['align'] == 'c':
|
||||
segment['contents'] = segment['contents'].center(segment['width'])
|
||||
# We need to yield a copy of the segment, or else mode-dependent
|
||||
# segment contents can't be cached correctly e.g. when caching
|
||||
# segment contents can’t be cached correctly e.g. when caching
|
||||
# non-current window contents for vim statuslines
|
||||
yield segment.copy()
|
||||
|
@ -2,6 +2,7 @@
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
import sys
|
||||
import json
|
||||
|
||||
from itertools import count
|
||||
|
||||
@ -11,9 +12,6 @@ from powerline.bindings.vim import vim_get_func, vim_getvar
|
||||
from powerline import Powerline, FailedUnicode
|
||||
from powerline.lib import mergedicts
|
||||
|
||||
if not hasattr(vim, 'bindeval'):
|
||||
import json
|
||||
|
||||
|
||||
def _override_from(config, override_varname):
|
||||
try:
|
||||
@ -134,7 +132,7 @@ class VimPowerline(Powerline):
|
||||
set_pycmd(pycmd)
|
||||
|
||||
# pyeval() and vim.bindeval were both introduced in one patch
|
||||
if not hasattr(vim, 'bindeval') and can_replace_pyeval:
|
||||
if (not hasattr(vim, 'bindeval') and can_replace_pyeval) or pyeval == 'PowerlinePyeval':
|
||||
vim.command(('''
|
||||
function! PowerlinePyeval(e)
|
||||
{pycmd} powerline.do_pyeval()
|
||||
@ -148,23 +146,30 @@ class VimPowerline(Powerline):
|
||||
self.update_renderer()
|
||||
__main__.powerline = self
|
||||
|
||||
if (
|
||||
bool(int(vim.eval("has('gui_running') && argc() == 0")))
|
||||
and not vim.current.buffer.name
|
||||
and len(vim.windows) == 1
|
||||
):
|
||||
# Hack to show startup screen. Problems in GUI:
|
||||
# - Defining local value of &statusline option while computing global
|
||||
# value purges startup screen.
|
||||
# - Defining highlight group while computing statusline purges startup
|
||||
# screen.
|
||||
# This hack removes the “while computing statusline” part: both things
|
||||
# are defined, but they are defined right now.
|
||||
#
|
||||
# The above condition disables this hack if no GUI is running, Vim did
|
||||
# not open any files and there is only one window. Without GUI
|
||||
# everything works, in other cases startup screen is not shown.
|
||||
self.new_window()
|
||||
try:
|
||||
if (
|
||||
bool(int(vim.eval('has(\'gui_running\') && argc() == 0')))
|
||||
and not vim.current.buffer.name
|
||||
and len(vim.windows) == 1
|
||||
):
|
||||
# Hack to show startup screen. Problems in GUI:
|
||||
# - Defining local value of &statusline option while computing
|
||||
# global value purges startup screen.
|
||||
# - Defining highlight group while computing statusline purges
|
||||
# startup screen.
|
||||
# This hack removes the “while computing statusline” part: both
|
||||
# things are defined, but they are defined right now.
|
||||
#
|
||||
# The above condition disables this hack if no GUI is running,
|
||||
# Vim did not open any files and there is only one window.
|
||||
# Without GUI everything works, in other cases startup screen is
|
||||
# not shown.
|
||||
self.new_window()
|
||||
except UnicodeDecodeError:
|
||||
# vim.current.buffer.name may raise UnicodeDecodeError when using
|
||||
# Python-3*. Fortunately, this means that current buffer is not
|
||||
# empty buffer, so the above condition should be False.
|
||||
pass
|
||||
|
||||
# Cannot have this in one line due to weird newline handling (in :execute
|
||||
# context newline is considered part of the command in just the same cases
|
||||
@ -245,14 +250,15 @@ class VimPowerline(Powerline):
|
||||
def new_window(self):
|
||||
return self.render(*self.win_idx(None))
|
||||
|
||||
if not hasattr(vim, 'bindeval'):
|
||||
# Method for PowerlinePyeval function. Is here to reduce the number of
|
||||
# requirements to __main__ globals to just one powerline object
|
||||
# (previously it required as well vim and json)
|
||||
@staticmethod
|
||||
def do_pyeval():
|
||||
import __main__
|
||||
vim.command('return ' + json.dumps(eval(vim.eval('a:e'), __main__.__dict__)))
|
||||
@staticmethod
|
||||
def do_pyeval():
|
||||
'''Evaluate python string passed to PowerlinePyeval
|
||||
|
||||
Is here to reduce the number of requirements to __main__ globals to just
|
||||
one powerline object (previously it required as well vim and json).
|
||||
'''
|
||||
import __main__
|
||||
vim.command('return ' + json.dumps(eval(vim.eval('a:e'), __main__.__dict__)))
|
||||
|
||||
def setup_components(self, components):
|
||||
if components is None:
|
||||
|
@ -12,11 +12,11 @@ from select import select
|
||||
from signal import signal, SIGTERM
|
||||
from time import sleep
|
||||
from functools import partial
|
||||
from locale import getpreferredencoding
|
||||
from io import BytesIO
|
||||
|
||||
from powerline.shell import get_argparser, finish_args, ShellPowerline, write_output
|
||||
from powerline.lib.monotonic import monotonic
|
||||
from powerline.lib.encoding import get_preferred_output_encoding
|
||||
|
||||
|
||||
is_daemon = False
|
||||
@ -74,6 +74,7 @@ def render(args, environ, cwd):
|
||||
args.renderer_module,
|
||||
tuple(args.config) if args.config else None,
|
||||
tuple(args.theme_option) if args.theme_option else None,
|
||||
tuple(args.config_path) if args.config_path else None,
|
||||
)
|
||||
finish_args(args)
|
||||
powerline = None
|
||||
@ -143,7 +144,7 @@ def do_write(conn, result):
|
||||
pass
|
||||
|
||||
|
||||
encoding = getpreferredencoding() or 'UTF-8'
|
||||
encoding = get_preferred_output_encoding()
|
||||
|
||||
|
||||
def safe_bytes(o, encoding=encoding):
|
||||
@ -397,7 +398,7 @@ def main():
|
||||
daemonize()
|
||||
|
||||
if use_filesystem:
|
||||
# Create a locked pid file containing the daemon's PID
|
||||
# Create a locked pid file containing the daemon’s PID
|
||||
if lockpidfile() is None:
|
||||
if not args.quiet:
|
||||
sys.stderr.write(
|
||||
|
@ -8,14 +8,14 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
|
||||
import sys
|
||||
import os
|
||||
|
||||
from locale import getpreferredencoding
|
||||
|
||||
try:
|
||||
from powerline.shell import ShellPowerline, get_argparser, finish_args, write_output
|
||||
except ImportError:
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(os.path.realpath(__file__)))))
|
||||
from powerline.shell import ShellPowerline, get_argparser, finish_args, write_output
|
||||
|
||||
from powerline.lib.unicode import get_preferred_output_encoding
|
||||
|
||||
|
||||
if sys.version_info < (3,):
|
||||
write = sys.stdout.write
|
||||
@ -28,4 +28,4 @@ if __name__ == '__main__':
|
||||
finish_args(args)
|
||||
powerline = ShellPowerline(args, run_once=True)
|
||||
segment_info = {'args': args, 'environ': os.environ}
|
||||
write_output(args, powerline, segment_info, write, getpreferredencoding())
|
||||
write_output(args, powerline, segment_info, write, get_preferred_output_encoding())
|
||||
|
4
setup.py
4
setup.py
@ -54,7 +54,7 @@ else:
|
||||
|
||||
setup(
|
||||
name='powerline-status',
|
||||
version='1.0',
|
||||
version='1.1',
|
||||
description='The ultimate statusline/prompt utility.',
|
||||
long_description=README,
|
||||
classifiers=[
|
||||
@ -79,7 +79,7 @@ setup(
|
||||
author_email='kim.silkebaekken+vim@gmail.com',
|
||||
url='https://github.com/Lokaltog/powerline',
|
||||
license='MIT',
|
||||
# XXX Python 3 doesn't allow compiled C files to be included in the scripts
|
||||
# XXX Python 3 doesn’t allow compiled C files to be included in the scripts
|
||||
# list below. This is because Python 3 distutils tries to decode the file to
|
||||
# ASCII, and fails when powerline-client is a binary.
|
||||
#
|
||||
|
@ -42,7 +42,10 @@ def urllib_read(query_url):
|
||||
elif query_url.startswith('http://freegeoip.net/json/'):
|
||||
return '{"city": "Meppen", "region_code": "06", "region_name": "Niedersachsen", "areacode": "", "ip": "82.145.55.16", "zipcode": "49716", "longitude": 7.3167, "country_name": "Germany", "country_code": "DE", "metrocode": "", "latitude": 52.6833}'
|
||||
elif query_url.startswith('http://query.yahooapis.com/v1/public/'):
|
||||
return r'{"query":{"count":1,"created":"2013-03-02T13:20:22Z","lang":"en-US","results":{"weather":{"rss":{"version":"2.0","geo":"http://www.w3.org/2003/01/geo/wgs84_pos#","yweather":"http://xml.weather.yahoo.com/ns/rss/1.0","channel":{"title":"Yahoo! Weather - Russia, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","description":"Yahoo! Weather for Russia, RU","language":"en-us","lastBuildDate":"Sat, 02 Mar 2013 4:58 pm MSK","ttl":"60","location":{"city":"Russia","country":"Russia","region":""},"units":{"distance":"km","pressure":"mb","speed":"km/h","temperature":"C"},"wind":{"chill":"-9","direction":"0","speed":""},"atmosphere":{"humidity":"94","pressure":"1006.1","rising":"0","visibility":""},"astronomy":{"sunrise":"10:04 am","sunset":"7:57 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Russia, RU at 4:58 pm MSK","lat":"59.45","long":"108.83","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","pubDate":"Sat, 02 Mar 2013 4:58 pm MSK","condition":{"code":"30","date":"Sat, 02 Mar 2013 4:58 pm MSK","temp":"-9","text":"Partly Cloudy"},"description":"<img src=\"http://l.yimg.com/a/i/us/we/52/30.gif\"/><br />\n<b>Current Conditions:</b><br />\nPartly Cloudy, -9 C<BR />\n<BR /><b>Forecast:</b><BR />\nSat - Partly Cloudy. High: -9 Low: -19<br />\nSun - Partly Cloudy. High: -12 Low: -18<br />\n<br />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html\">Full Forecast at Yahoo! Weather</a><BR/><BR/>\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)<br/>","forecast":[{"code":"29","date":"2 Mar 2013","day":"Sat","high":"-9","low":"-19","text":"Partly Cloudy"},{"code":"30","date":"3 Mar 2013","day":"Sun","high":"-12","low":"-18","text":"Partly Cloudy"}],"guid":{"isPermaLink":"false","content":"RSXX1511_2013_03_03_7_00_MSK"}}}}}}}}'
|
||||
if 'Meppen' in query_url:
|
||||
return r'{"query":{"count":1,"created":"2013-03-02T13:20:22Z","lang":"en-US","results":{"weather":{"rss":{"version":"2.0","geo":"http://www.w3.org/2003/01/geo/wgs84_pos#","yweather":"http://xml.weather.yahoo.com/ns/rss/1.0","channel":{"title":"Yahoo! Weather - Russia, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","description":"Yahoo! Weather for Russia, RU","language":"en-us","lastBuildDate":"Sat, 02 Mar 2013 4:58 pm MSK","ttl":"60","location":{"city":"Russia","country":"Russia","region":""},"units":{"distance":"km","pressure":"mb","speed":"km/h","temperature":"C"},"wind":{"chill":"-9","direction":"0","speed":""},"atmosphere":{"humidity":"94","pressure":"1006.1","rising":"0","visibility":""},"astronomy":{"sunrise":"10:04 am","sunset":"7:57 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Russia, RU at 4:58 pm MSK","lat":"59.45","long":"108.83","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","pubDate":"Sat, 02 Mar 2013 4:58 pm MSK","condition":{"code":"30","date":"Sat, 02 Mar 2013 4:58 pm MSK","temp":"-9","text":"Partly Cloudy"},"description":"<img src=\"http://l.yimg.com/a/i/us/we/52/30.gif\"/><br />\n<b>Current Conditions:</b><br />\nPartly Cloudy, -9 C<BR />\n<BR /><b>Forecast:</b><BR />\nSat - Partly Cloudy. High: -9 Low: -19<br />\nSun - Partly Cloudy. High: -12 Low: -18<br />\n<br />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html\">Full Forecast at Yahoo! Weather</a><BR/><BR/>\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)<br/>","forecast":[{"code":"29","date":"2 Mar 2013","day":"Sat","high":"-9","low":"-19","text":"Partly Cloudy"},{"code":"30","date":"3 Mar 2013","day":"Sun","high":"-12","low":"-18","text":"Partly Cloudy"}],"guid":{"isPermaLink":"false","content":"RSXX1511_2013_03_03_7_00_MSK"}}}}}}}}'
|
||||
elif 'Moscow' in query_url:
|
||||
return r'{"query":{"count":1,"created":"2013-03-02T13:20:22Z","lang":"en-US","results":{"weather":{"rss":{"version":"2.0","geo":"http://www.w3.org/2003/01/geo/wgs84_pos#","yweather":"http://xml.weather.yahoo.com/ns/rss/1.0","channel":{"title":"Yahoo! Weather - Russia, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","description":"Yahoo! Weather for Russia, RU","language":"en-us","lastBuildDate":"Sat, 02 Mar 2013 4:58 pm MSK","ttl":"60","location":{"city":"Russia","country":"Russia","region":""},"units":{"distance":"km","pressure":"mb","speed":"km/h","temperature":"C"},"wind":{"chill":"-9","direction":"0","speed":""},"atmosphere":{"humidity":"94","pressure":"1006.1","rising":"0","visibility":""},"astronomy":{"sunrise":"10:04 am","sunset":"7:57 pm"},"image":{"title":"Yahoo! Weather","width":"142","height":"18","link":"http://weather.yahoo.com","url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-wea.gif"},"item":{"title":"Conditions for Russia, RU at 4:58 pm MSK","lat":"59.45","long":"108.83","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html","pubDate":"Sat, 02 Mar 2013 4:58 pm MSK","condition":{"code":"30","date":"Sat, 02 Mar 2013 4:58 pm MSK","temp":"19","text":"Partly Cloudy"},"description":"<img src=\"http://l.yimg.com/a/i/us/we/52/30.gif\"/><br />\n<b>Current Conditions:</b><br />\nPartly Cloudy, -9 C<BR />\n<BR /><b>Forecast:</b><BR />\nSat - Partly Cloudy. High: -9 Low: -19<br />\nSun - Partly Cloudy. High: -12 Low: -18<br />\n<br />\n<a href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Russia__RU/*http://weather.yahoo.com/forecast/RSXX1511_c.html\">Full Forecast at Yahoo! Weather</a><BR/><BR/>\n(provided by <a href=\"http://www.weather.com\" >The Weather Channel</a>)<br/>","forecast":[{"code":"29","date":"2 Mar 2013","day":"Sat","high":"-9","low":"-19","text":"Partly Cloudy"},{"code":"30","date":"3 Mar 2013","day":"Sun","high":"-12","low":"-18","text":"Partly Cloudy"}],"guid":{"isPermaLink":"false","content":"RSXX1511_2013_03_03_7_00_MSK"}}}}}}}}'
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
41
tests/run_daemon_tests.sh
Executable file
41
tests/run_daemon_tests.sh
Executable file
@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
FAILED=0
|
||||
export ADDRESS="powerline-ipc-test-$$"
|
||||
echo "Powerline address: $ADDRESS"
|
||||
if $PYTHON scripts/powerline-daemon -s$ADDRESS ; then
|
||||
sleep 1
|
||||
if ! ( \
|
||||
$PYTHON client/powerline.py --socket $ADDRESS -p/dev/null shell left | \
|
||||
grep 'file not found'
|
||||
) ; then
|
||||
echo "-p/dev/null argument ignored or not treated properly"
|
||||
FAILED=1
|
||||
fi
|
||||
if ( \
|
||||
$PYTHON client/powerline.py --socket $ADDRESS \
|
||||
-p$PWD/powerline/config_files shell left | \
|
||||
grep 'file not found'
|
||||
) ; then
|
||||
echo "-p/dev/null argument remembered while it should not"
|
||||
FAILED=1
|
||||
fi
|
||||
if ! ( \
|
||||
cd tests && \
|
||||
$PYTHON ../client/powerline.py --socket $ADDRESS \
|
||||
-p$PWD/../powerline/config_files shell left | \
|
||||
grep 'tests'
|
||||
) ; then
|
||||
echo "Output lacks string “tests”"
|
||||
FAILED=1
|
||||
fi
|
||||
else
|
||||
echo "Daemon exited with status $?"
|
||||
FAILED=1
|
||||
fi
|
||||
if $PYTHON scripts/powerline-daemon -s$ADDRESS -k ; then
|
||||
:
|
||||
else
|
||||
echo "powerline-daemon -k failed with exit code $?"
|
||||
FAILED=1
|
||||
fi
|
||||
exit $FAILED
|
@ -653,9 +653,9 @@ class TestVim(TestCase):
|
||||
window = vim_module.current.window
|
||||
window_id = 1
|
||||
winnr = window.number
|
||||
self.assertEqual(powerline.render(window, window_id, winnr), '%#Pl_3_8404992_4_192_underline#\xa0abc%#Pl_4_192_NONE_None_NONE#>>')
|
||||
self.assertEqual(powerline.render(window, window_id, winnr), b'%#Pl_3_8404992_4_192_underline#\xc2\xa0abc%#Pl_4_192_NONE_None_NONE#>>')
|
||||
vim_module._environ['TEST'] = 'def'
|
||||
self.assertEqual(powerline.render(window, window_id, winnr), '%#Pl_3_8404992_4_192_underline#\xa0def%#Pl_4_192_NONE_None_NONE#>>')
|
||||
self.assertEqual(powerline.render(window, window_id, winnr), b'%#Pl_3_8404992_4_192_underline#\xc2\xa0def%#Pl_4_192_NONE_None_NONE#>>')
|
||||
|
||||
def test_local_themes(self):
|
||||
# Regression test: VimPowerline.add_local_theme did not work properly.
|
||||
@ -682,7 +682,7 @@ class TestVim(TestCase):
|
||||
window = vim_module.current.window
|
||||
window_id = 1
|
||||
winnr = window.number
|
||||
self.assertEqual(powerline.render(window, window_id, winnr), '%#Pl_5_12583104_6_32896_NONE#\xa0\u201cbar\u201d%#Pl_6_32896_NONE_None_NONE#>>')
|
||||
self.assertEqual(powerline.render(window, window_id, winnr), b'%#Pl_5_12583104_6_32896_NONE#\xc2\xa0\xe2\x80\x9cbar\xe2\x80\x9d%#Pl_6_32896_NONE_None_NONE#>>')
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user