diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index ea7f7f95..1d820a35 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
+ python-version: [3.7, 3.8, 3.9, 3.11, 3.12]
steps:
- name: Checkout code
diff --git a/docs/source/powerline_autodoc.py b/docs/source/powerline_autodoc.py
index eba42edb..83996aea 100644
--- a/docs/source/powerline_autodoc.py
+++ b/docs/source/powerline_autodoc.py
@@ -3,11 +3,9 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct
import os
-from inspect import formatargspec
-
from sphinx.ext import autodoc
-from powerline.lint.inspect import getconfigargspec
+from powerline.lint.inspect import formatconfigargspec, getconfigargspec
from powerline.segments import Segment
from powerline.lib.unicode import unicode
@@ -28,7 +26,7 @@ class ThreadedDocumenter(autodoc.FunctionDocumenter):
def format_args(self):
argspec = getconfigargspec(self.object)
- return formatargspec(*argspec, formatvalue=formatvalue).replace('\\', '\\\\')
+ return formatconfigargspec(*argspec, formatvalue=formatvalue).replace('\\', '\\\\')
class Repr(object):
diff --git a/powerline/bindings/config.py b/powerline/bindings/config.py
index 31006330..eb6693b9 100644
--- a/powerline/bindings/config.py
+++ b/powerline/bindings/config.py
@@ -176,7 +176,7 @@ def init_tmux_environment(pl, args, set_tmux_environment=set_tmux_environment):
' ' * powerline.renderer.strwidth(left_dividers['hard'])))
-TMUX_VAR_RE = re.compile('\$(_POWERLINE_\w+)')
+TMUX_VAR_RE = re.compile(r'\$(_POWERLINE_\w+)')
def tmux_setup(pl, args):
diff --git a/powerline/lib/encoding.py b/powerline/lib/encoding.py
index 76a51d81..d5f42605 100644
--- a/powerline/lib/encoding.py
+++ b/powerline/lib/encoding.py
@@ -46,12 +46,12 @@ def get_preferred_output_encoding():
if hasattr(locale, 'LC_MESSAGES'):
return (
locale.getlocale(locale.LC_MESSAGES)[1]
- or locale.getdefaultlocale()[1]
+ or locale.getlocale()[1]
or 'ascii'
)
return (
- locale.getdefaultlocale()[1]
+ locale.getlocale()[1]
or 'ascii'
)
@@ -66,12 +66,12 @@ def get_preferred_input_encoding():
if hasattr(locale, 'LC_MESSAGES'):
return (
locale.getlocale(locale.LC_MESSAGES)[1]
- or locale.getdefaultlocale()[1]
+ or locale.getlocale()[1]
or 'latin1'
)
return (
- locale.getdefaultlocale()[1]
+ locale.getlocale()[1]
or 'latin1'
)
@@ -86,7 +86,7 @@ def get_preferred_arguments_encoding():
a problem.
'''
return (
- locale.getdefaultlocale()[1]
+ locale.getlocale()[1]
or 'latin1'
)
diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py
index 8c682718..76ba0fd5 100644
--- a/powerline/lint/__init__.py
+++ b/powerline/lint/__init__.py
@@ -41,7 +41,7 @@ def generate_json_config_loader(lhadproblem):
return load_json_config
-function_name_re = '^(\w+\.)*[a-zA-Z_]\w*$'
+function_name_re = r'^(\w+\.)*[a-zA-Z_]\w*$'
divider_spec = Spec().printable().len(
@@ -205,7 +205,7 @@ vim_colorscheme_spec = (Spec(
mode_translations_value_spec(),
).optional().context_message('Error while loading mode translations (key {key})'),
).context_message('Error while loading vim colorscheme'))
-shell_mode_spec = Spec().re('^(?:[\w\-]+|\.safe)$').copy
+shell_mode_spec = Spec().re(r'^(?:[\w\-]+|\.safe)$').copy
shell_colorscheme_spec = (Spec(
name=name_spec(),
groups=groups_spec(),
@@ -223,7 +223,7 @@ args_spec = Spec(
segment_module_spec = Spec().type(unicode).func(check_segment_module).optional().copy
exinclude_spec = Spec().re(function_name_re).func(check_exinclude_function).copy
segment_spec_base = Spec(
- name=Spec().re('^[a-zA-Z_]\w*$').optional(),
+ name=Spec().re(r'^[a-zA-Z_]\w*$').optional(),
function=Spec().re(function_name_re).func(check_segment_function).optional(),
exclude_modes=Spec().list(vim_mode_spec()).optional(),
include_modes=Spec().list(vim_mode_spec()).optional(),
diff --git a/powerline/lint/inspect.py b/powerline/lint/inspect.py
index 15bb6103..7ab720f8 100644
--- a/powerline/lint/inspect.py
+++ b/powerline/lint/inspect.py
@@ -1,7 +1,8 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
-from inspect import ArgSpec, getargspec
+from inspect import FullArgSpec, getfullargspec
+from itertools import zip_longest
from powerline.segments import Segment
@@ -33,7 +34,7 @@ def getconfigargspec(obj):
requires_filesystem_watcher = hasattr(obj, 'powerline_requires_filesystem_watcher')
for name, method in argspecobjs:
- argspec = getargspec(method)
+ argspec = getfullargspec(method)
omitted_args = get_omitted_args(name, method)
largs = len(argspec.args)
for i, arg in enumerate(reversed(argspec.args)):
@@ -60,4 +61,31 @@ def getconfigargspec(obj):
if arg not in args:
args.insert(0, arg)
- return ArgSpec(args=args, varargs=None, keywords=None, defaults=tuple(defaults))
+ return FullArgSpec(args=args, varargs=None, varkw=None, defaults=tuple(defaults), kwonlyargs=(), kwonlydefaults={}, annotations={})
+
+
+def formatconfigargspec(args, varargs=None, varkw=None, defaults=None,
+ kwonlyargs=(), kwonlydefaults={}, annotations={},
+ formatvalue=lambda value: '=' + repr(value)):
+ '''Format an argument spec from the values returned by getconfigargspec.
+
+ This is a specialized replacement for inspect.formatargspec, which has been
+ deprecated since Python 3.5 and was removed in Python 3.11. It supports
+ valid values for args, defaults and formatvalue; all other parameters are
+ expected to be either empty or None.
+ '''
+ assert varargs is None
+ assert varkw is None
+ assert not kwonlyargs
+ assert not kwonlydefaults
+ assert not annotations
+
+ specs = []
+ if defaults:
+ firstdefault = len(args) - len(defaults)
+ for i, arg in enumerate(args):
+ spec = arg
+ if defaults and i >= firstdefault:
+ spec += formatvalue(defaults[i - firstdefault])
+ specs.append(spec)
+ return '(' + ', '.join(specs) + ')'
diff --git a/powerline/lint/spec.py b/powerline/lint/spec.py
index 6a54441c..e39bfde0 100644
--- a/powerline/lint/spec.py
+++ b/powerline/lint/spec.py
@@ -34,7 +34,7 @@ class Spec(object):
.. note::
Methods that create the specifications return ``self``, so calls to them
- may be chained: ``Spec().type(unicode).re('^\w+$')``. This does not
+ may be chained: ``Spec().type(unicode).re('^\\w+$')``. This does not
apply to functions that *apply* specification like :py:meth`Spec.match`.
.. note::
@@ -585,7 +585,7 @@ class Spec(object):
msg_func
or (lambda value: 'String "{0}" is not an alphanumeric/underscore colon-separated identifier'.format(value))
)
- return self.re('^\w+(?::\w+)?$', msg_func)
+ return self.re(r'^\w+(?::\w+)?$', msg_func)
def oneof(self, collection, msg_func=None):
'''Describe value that is equal to one of the value in the collection
diff --git a/powerline/segments/common/mail.py b/powerline/segments/common/mail.py
index 82024921..07577a8d 100644
--- a/powerline/segments/common/mail.py
+++ b/powerline/segments/common/mail.py
@@ -1,6 +1,8 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
+import os
+
import re
from imaplib import IMAP4_SSL_PORT, IMAP4_SSL, IMAP4
@@ -17,9 +19,19 @@ class EmailIMAPSegment(KwThreadedSegment):
interval = 60
@staticmethod
- def key(username, password, server='imap.gmail.com', port=IMAP4_SSL_PORT, folder='INBOX', use_ssl=None, **kwargs):
+ def key(username='', password='', server='imap.gmail.com', port=IMAP4_SSL_PORT, username_variable='', password_variable='', server_variable='', port_variable='', folder='INBOX', use_ssl=None, **kwargs):
if use_ssl is None:
use_ssl = (port == IMAP4_SSL_PORT)
+ # catch if user set custom mail credential env variables
+ if username_variable:
+ username = os.environ[username_variable]
+ if password_variable:
+ password = os.environ[password_variable]
+ if server_variable:
+ server = os.environ[server_variable]
+ if port_variable:
+ port = os.environ[port_variable]
+
return _IMAPKey(username, password, server, port, folder, use_ssl)
def compute_state(self, key):
@@ -33,7 +45,7 @@ class EmailIMAPSegment(KwThreadedSegment):
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))
+ unread_count = int(re.search(r'UNSEEN (\d+)', unread_str).group(1))
return unread_count
@staticmethod
@@ -64,6 +76,14 @@ email_imap_alert = with_docstring(EmailIMAPSegment(),
e-mail server
:param int port:
e-mail server port
+:param str username_variable:
+ name of environment variable to check for login username
+:param str password_variable:
+ name of environment variable to check for login password
+:param str server_variable:
+ name of environment variable to check for email server
+:param str port_variable:
+ name of environment variable to check for email server port
:param str folder:
folder to check for e-mails
:param int max_msgs:
diff --git a/powerline/segments/common/time.py b/powerline/segments/common/time.py
index be727c9e..b7aaa0c9 100644
--- a/powerline/segments/common/time.py
+++ b/powerline/segments/common/time.py
@@ -1,7 +1,5 @@
# vim:fileencoding=utf-8:noet
-from __future__ import (unicode_literals, division, absolute_import, print_function)
-
-from datetime import datetime
+from datetime import datetime, timezone
def date(pl, format='%Y-%m-%d', istime=False, timezone=None):
@@ -45,11 +43,11 @@ UNICODE_TEXT_TRANSLATION = {
}
-def fuzzy_time(pl, format='{minute_str} {hour_str}', unicode_text=False, timezone=None, hour_str=['twelve', 'one', 'two', 'three', 'four',
+def fuzzy_time(pl, format=None, unicode_text=False, timezone=None, hour_str=['twelve', 'one', 'two', 'three', 'four',
'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven'], minute_str = {
- '0': 'o\'clock', '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'
+ '0': '{hour_str} o\'clock', '5': 'five past {hour_str}', '10': 'ten past {hour_str}','15': 'quarter past {hour_str}',
+ '20': 'twenty past {hour_str}', '25': 'twenty-five past {hour_str}', '30': 'half past {hour_str}', '35': 'twenty-five to {hour_str}',
+ '40': 'twenty to {hour_str}', '45': 'quarter to {hour_str}', '50': 'ten to {hour_str}', '55': 'five to {hour_str}'
}, special_case_str = {
'(23, 58)': 'round about midnight',
'(23, 59)': 'round about midnight',
@@ -62,8 +60,7 @@ def fuzzy_time(pl, format='{minute_str} {hour_str}', unicode_text=False, timezon
'''Display the current time as fuzzy time, e.g. "quarter past six".
:param string format:
- Format used to display the fuzzy time. (Ignored when a special time
- is displayed.)
+ (unused)
:param bool unicode_text:
If true then hyphenminuses (regular ASCII ``-``) and single quotes are
replaced with unicode dashes and apostrophes.
@@ -74,7 +71,9 @@ def fuzzy_time(pl, format='{minute_str} {hour_str}', unicode_text=False, timezon
Strings to be used to display the hour, starting with midnight.
(This list may contain 12 or 24 entries.)
:param dict minute_str:
- Dictionary mapping minutes to strings to be used to display them.
+ Dictionary mapping minutes to strings to be used to display them. Each entry may
+ optionally come with a format field "{hour_str}" to indicate the position of the
+ string used for the current hour.
:param dict special_case_str:
Special strings for special times.
@@ -100,7 +99,7 @@ def fuzzy_time(pl, format='{minute_str} {hour_str}', unicode_text=False, timezon
pass
hour = now.hour
- if now.minute >= 30:
+ if now.minute >= 32:
hour = hour + 1
hour = hour % len(hour_str)
@@ -115,7 +114,7 @@ def fuzzy_time(pl, format='{minute_str} {hour_str}', unicode_text=False, timezon
elif now.minute < mn and mn - now.minute < min_dis:
min_dis = mn - now.minute
min_pos = mn
- result = format.format(minute_str=minute_str[str(min_pos)], hour_str=hour_str[hour])
+ result = minute_str[str(min_pos)].format(hour_str=hour_str[hour])
if unicode_text:
result = result.translate(UNICODE_TEXT_TRANSLATION)
diff --git a/powerline/version.py b/powerline/version.py
index f380c80b..421844ee 100644
--- a/powerline/version.py
+++ b/powerline/version.py
@@ -1,7 +1,7 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
-__version__ = "2.8.3"
+__version__ = "2.8.4"
def get_version():
return __version__
diff --git a/tests/modules/lib/__init__.py b/tests/modules/lib/__init__.py
index f6df9204..89790584 100644
--- a/tests/modules/lib/__init__.py
+++ b/tests/modules/lib/__init__.py
@@ -1,7 +1,7 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
-import imp
+import types
import sys
@@ -48,7 +48,7 @@ def urllib_read(query_url):
elif query_url.startswith('https://freegeoip.app/json/'):
return '{"ip":"82.145.55.16","country_code":"DE","country_name":"Germany","region_code":"NI","region_name":"Lower Saxony","city":"Meppen","zip_code":"49716","time_zone":"Europe/Berlin","latitude":52.6833,"longitude":7.3167,"metro_code":0}'
elif query_url.startswith('http://geoip.nekudo.com/api/'):
- return '{"city":"Meppen","country":{"name":"Germany", "code":"DE"},"location":{"accuracy_radius":100,"latitude":52.6833,"longitude":7.3167,"time_zone":"Europe\/Berlin"},"ip":"82.145.55.16"}'
+ return r'{"city":"Meppen","country":{"name":"Germany", "code":"DE"},"location":{"accuracy_radius":100,"latitude":52.6833,"longitude":7.3167,"time_zone":"Europe\/Berlin"},"ip":"82.145.55.16"}'
elif query_url.startswith('http://query.yahooapis.com/v1/public/'):
if 'Meppen' in query_url or '52.6833' in query_url:
return r'{"query":{"count":1,"created":"2016-05-13T19:43:18Z","lang":"en-US","results":{"channel":{"units":{"distance":"mi","pressure":"in","speed":"mph","temperature":"C"},"title":"Yahoo! Weather - Meppen, NI, DE","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-674836/","description":"Yahoo! Weather for Meppen, NI, DE","language":"en-us","lastBuildDate":"Fri, 13 May 2016 09:43 PM CEST","ttl":"60","location":{"city":"Meppen","country":"Germany","region":" NI"},"wind":{"chill":"55","direction":"350","speed":"25"},"atmosphere":{"humidity":"57","pressure":"1004.0","rising":"0","visibility":"16.1"},"astronomy":{"sunrise":"5:35 am","sunset":"9:21 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 Meppen, NI, DE at 08:00 PM CEST","lat":"52.68993","long":"7.29115","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-674836/","pubDate":"Fri, 13 May 2016 08:00 PM CEST","condition":{"code":"23","date":"Fri, 13 May 2016 08:00 PM CEST","temp":"14","text":"Breezy"},"forecast":[{"code":"30","date":"13 May 2016","day":"Fri","high":"71","low":"48","text":"Partly Cloudy"},{"code":"28","date":"14 May 2016","day":"Sat","high":"54","low":"44","text":"Mostly Cloudy"},{"code":"11","date":"15 May 2016","day":"Sun","high":"55","low":"43","text":"Showers"},{"code":"28","date":"16 May 2016","day":"Mon","high":"54","low":"42","text":"Mostly Cloudy"},{"code":"28","date":"17 May 2016","day":"Tue","high":"57","low":"43","text":"Mostly Cloudy"},{"code":"12","date":"18 May 2016","day":"Wed","high":"62","low":"45","text":"Rain"},{"code":"28","date":"19 May 2016","day":"Thu","high":"63","low":"48","text":"Mostly Cloudy"},{"code":"28","date":"20 May 2016","day":"Fri","high":"67","low":"50","text":"Mostly Cloudy"},{"code":"30","date":"21 May 2016","day":"Sat","high":"71","low":"50","text":"Partly Cloudy"},{"code":"30","date":"22 May 2016","day":"Sun","high":"74","low":"54","text":"Partly Cloudy"}],"description":"\n
\nCurrent Conditions:\n
Breezy\n
\n
\nForecast:\n
Fri - Partly Cloudy. High: 71Low: 48\n
Sat - Mostly Cloudy. High: 54Low: 44\n
Sun - Showers. High: 55Low: 43\n
Mon - Mostly Cloudy. High: 54Low: 42\n
Tue - Mostly Cloudy. High: 57Low: 43\n
\n
\nFull Forecast at Yahoo! Weather\n
\n
\n(provided by The Weather Channel)\n
\n]]>","guid":{"isPermaLink":"false"}}}}}}'
@@ -100,7 +100,7 @@ def replace_module(name, new=None, **kwargs):
def new_module(name, **kwargs):
- module = imp.new_module(name)
+ module = types.ModuleType(name)
for k, v in kwargs.items():
setattr(module, k, v)
return module
diff --git a/tests/test_python/test_provided_config_files.py b/tests/test_python/test_provided_config_files.py
index fd8b16e6..ca08a6d5 100644
--- a/tests/test_python/test_provided_config_files.py
+++ b/tests/test_python/test_provided_config_files.py
@@ -111,7 +111,7 @@ class TestVimConfig(TestCase):
class TestConfig(TestCase):
def test_tmux(self):
from powerline.segments import common
- from imp import reload
+ from importlib import reload
reload(common)
from powerline.shell import ShellPowerline
with replace_attr(common, 'urllib_read', urllib_read):
@@ -165,7 +165,7 @@ class TestConfig(TestCase):
def test_wm(self):
from powerline.segments import common
- from imp import reload
+ from importlib import reload
reload(common)
from powerline import Powerline
with replace_attr(wthr, 'urllib_read', urllib_read):
diff --git a/tests/test_python/test_segments.py b/tests/test_python/test_segments.py
index c7a9870a..cc39fbac 100644
--- a/tests/test_python/test_segments.py
+++ b/tests/test_python/test_segments.py
@@ -843,17 +843,16 @@ class TestTime(TestCommon):
time = Args(hour=0, minute=45)
pl = Pl()
hour_str = ['12', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven']
- minute_str = {'0': 'o\'clock', '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'}
+ minute_str = {'0': '{hour_str} o\'clock', '5': 'five past {hour_str}', '10': 'ten past {hour_str}',
+ '15': 'quarter past {hour_str}', '20': 'twenty past {hour_str}', '25': 'twenty-five past {hour_str}',
+ '30': 'half past {hour_str}', '35': 'twenty-five to {hour_str}', '40': 'twenty to {hour_str}',
+ '45': 'quarter to {hour_str}', '50': 'ten to {hour_str}', '55': 'five to {hour_str}'}
special_case_str = {
'(23, 58)': '~ midnight',
'(23, 59)': '~ midnight',
'(0, 0)': 'midnight',
'(0, 1)': '~ midnight',
- '(0, 2)': '~ midnight',
- '(12, 0)': 'twelve o\'clock'}
+ '(0, 2)': '~ midnight'}
with replace_attr(self.module, 'datetime', Args(strptime=lambda timezone, fmt: Args(tzinfo=timezone), now=lambda tz: time)):
self.assertEqual(self.module.fuzzy_time(pl=pl, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'quarter to one')
self.assertEqual(self.module.fuzzy_time(pl=pl), 'quarter to one')
@@ -867,7 +866,7 @@ class TestTime(TestCommon):
self.assertEqual(self.module.fuzzy_time(pl=pl), 'twenty-five to twelve')
time.hour = 12
time.minute = 0
- self.assertEqual(self.module.fuzzy_time(pl=pl, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'twelve o\'clock')
+ self.assertEqual(self.module.fuzzy_time(pl=pl, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), '12 o\'clock')
self.assertEqual(self.module.fuzzy_time(pl=pl), 'noon')
time.hour = 11
time.minute = 33
@@ -875,7 +874,7 @@ class TestTime(TestCommon):
self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False), 'twenty-five to twelve')
time.hour = 12
time.minute = 0
- self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'twelve o\'clock')
+ self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), '12 o\'clock')
self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False), 'noon')
time.hour = 11
time.minute = 33
@@ -883,7 +882,7 @@ class TestTime(TestCommon):
self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True), 'twenty‐five to twelve')
time.hour = 12
time.minute = 0
- self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), 'twelve o’clock')
+ self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), '12 o’clock')
self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True),'noon')