diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 00000000..ea7f7f95 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,46 @@ +name: Build and Publish to PyPI + +on: + push: + branches: + - master + - develop + - feature/actions + pull_request: + branches: + - develop + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [2.7, 3.5, 3.6, 3.7, 3.8] + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Build + run: | + python setup.py sdist bdist_wheel + + - name: Publish + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.PYPI_TOKEN }} + packages_dir: dist/ diff --git a/.travis.yml b/.travis.yml index a1638de6..57ef8e23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,8 +26,7 @@ jobs: - stage: Latest Python python: "3.6" - stage: Intermediate versions - python: "3.3" + python: "3.5" - python: "3.4" - - python: "3.5" # vim: et diff --git a/README.rst b/README.rst index 7040e45d..0c37ac22 100644 --- a/README.rst +++ b/README.rst @@ -1,24 +1,24 @@ Powerline ========= -:Author: Kim Silkebækken (kim.silkebaekken+vim@gmail.com) -:Source: https://github.com/powerline/powerline -:Version: beta +.. image:: https://api.travis-ci.org/powerline/powerline.svg?branch=develop + :target: `travis-build-status`_ + :alt: Build status +.. _travis-build-status: https://travis-ci.org/powerline/powerline **Powerline is a statusline plugin for vim, and provides statuslines and prompts for several other applications, including zsh, bash, fish, tmux, IPython, Awesome, i3 and Qtile.** -* `Support forum`_ (powerline-support@googlegroups.com) -* `Development discussion`_ (powerline-dev@googlegroups.com) ++---------+---------------------------------------------------+ +| Author | Kim Silkebækken (kim.silkebaekken+vim@gmail.com) | ++---------+---------------------------------------------------+ +| Source | https://github.com/powerline/powerline | ++---------+---------------------------------------------------+ +| Version | beta | ++---------+---------------------------------------------------+ -.. image:: https://api.travis-ci.org/powerline/powerline.svg?branch=develop - :target: `travis-build-status`_ - :alt: Build status - -.. _travis-build-status: https://travis-ci.org/powerline/powerline -.. _`Support forum`: https://groups.google.com/forum/#!forum/powerline-support -.. _`Development discussion`: https://groups.google.com/forum/#!forum/powerline-dev +**Powerline does not support python2 anymore and powerline will stop working with python2 in the near future.** Features -------- @@ -29,8 +29,7 @@ Features config files, and a structured, object-oriented codebase with no mandatory third-party dependencies other than a Python interpreter. * **Stable and testable code base.** Using Python has allowed unit testing - of all the project code. The code is tested to work in Python 2.6+ and - Python 3. + of all the project code. The code is tested to work in Python 3.6+. * **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 @@ -60,6 +59,8 @@ Configuration Basic powerline configuration is done via `JSON` files located at `.config/powerline/`. It is a good idea to start by copying the default configuration located at `powerline_root/powerline/config_files/` to `.config/powerline/`. If you installed the powerline from the AUR or via pip, `powerline_root` should be `/usr/lib/python3.6/site-packages/` or something similar, depending on your python version. +If you installed powerline via apt-get 'powerline_root' should be '/usr/share/powerline/'. + This should yield you the following directory structure: :: diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index 412fb6a6..a2be2d57 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -117,10 +117,14 @@ segments that you may want to customize right away: so unless you’re on a VPN you probably won’t have to change the location query. + It is using OpenWeatherMap as a provider, which can be configured with a + personal API key. These can be generated `here + `_ + 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: + the weather segment and update it to include unit, location query or + api key arguments: .. code-block:: javascript @@ -129,7 +133,8 @@ segments that you may want to customize right away: "priority": 50, "args": { "unit": "F", - "location_query": "oslo, norway" + "location_query": "oslo, norway", + "weather_api_key": "your_api_key" } }, diff --git a/docs/source/develop/segments.rst b/docs/source/develop/segments.rst index 543ddd57..59549f62 100644 --- a/docs/source/develop/segments.rst +++ b/docs/source/develop/segments.rst @@ -528,10 +528,10 @@ i3wm in lemonbar bindings. ``workspace`` - dictionary containing the workspace name under the key ``"name"`` and - boolean values for the ``"visible"``, ``"urgent"`` and ``"focused"`` - keys, indicating the state of the workspace. Currently only provided by - the :py:func:`powerline.listers.i3wm.workspace_lister` lister. + the `i3-ipc` workspace object corresponding to this workspace. + Contains string attributes ``name`` and ``output``, as well as boolean + attributes for ``visible``, ``urgent`` and ``focused``. Currently only + provided by the :py:func:`powerline.listers.i3wm.workspace_lister` lister. Segment class ============= diff --git a/docs/source/usage/wm-widgets.rst b/docs/source/usage/wm-widgets.rst index 204c008c..bc63da63 100644 --- a/docs/source/usage/wm-widgets.rst +++ b/docs/source/usage/wm-widgets.rst @@ -77,8 +77,7 @@ to run with i3, simply ``exec`` this in the i3 config file and set the ``--i3`` exec python /path/to/powerline/bindings/lemonbar/powerline-lemonbar.py --i3 -Running the binding in i3-mode will require `i3ipc `_ -(or the outdated `i3-py `_). +Running the binding in i3-mode will require `i3ipc `_. See the `lemonbar documentation `_ for more information and options. diff --git a/powerline/bindings/tmux/powerline_tmux_1.9_plus.conf b/powerline/bindings/tmux/powerline_tmux_1.9_plus.conf index 7ab9a8b3..b1afaf4b 100644 --- a/powerline/bindings/tmux/powerline_tmux_1.9_plus.conf +++ b/powerline/bindings/tmux/powerline_tmux_1.9_plus.conf @@ -5,4 +5,5 @@ set-option -qg window-status-last-style "$_POWERLINE_ACTIVE_WINDOW_STATUS_COLOR" set-window-option -qg window-status-style "$_POWERLINE_WINDOW_STATUS_COLOR" set-window-option -qg window-status-activity-style "$_POWERLINE_ACTIVITY_STATUS_COLOR" set-window-option -qg window-status-bell-style "$_POWERLINE_BELL_STATUS_COLOR" +set -g status-right '#(env "$POWERLINE_COMMAND" $POWERLINE_COMMAND_ARGS tmux right --width=#{client_width} -R width_adjust=#{status-left-length} -R pane_id=#{pane_id} -R pane_current_path=#{q:pane_current_path})' # vim: ft=tmux diff --git a/powerline/bindings/tmux/powerline_tmux_2.1_plus.conf b/powerline/bindings/tmux/powerline_tmux_2.1_plus.conf index 01f6a252..16703b74 100644 --- a/powerline/bindings/tmux/powerline_tmux_2.1_plus.conf +++ b/powerline/bindings/tmux/powerline_tmux_2.1_plus.conf @@ -1,3 +1,3 @@ # Starting from tmux-2.1 escaping of dollar signs inside #() is harmful -set -qg status-left "#{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_FG]#[bg=$_POWERLINE_SESSION_PREFIX_BG]#[$_POWERLINE_SESSION_PREFIX_ATTR],#[fg=$_POWERLINE_SESSION_FG]#[bg=$_POWERLINE_SESSION_BG]#[$_POWERLINE_SESSION_ATTR]} #S #{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_BG],#[fg=$_POWERLINE_SESSION_BG]}#[bg=$_POWERLINE_BACKGROUND_BG]#[nobold]$_POWERLINE_LEFT_HARD_DIVIDER#(env $POWERLINE_COMMAND $POWERLINE_COMMAND_ARGS tmux left --width=#{client_width} -R width_adjust=#{status-right-length} -R pane_id=#{pane_id})" +set -qg status-left "#{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_FG]#[bg=$_POWERLINE_SESSION_PREFIX_BG]#[$_POWERLINE_SESSION_PREFIX_ATTR],#[fg=$_POWERLINE_SESSION_FG]#[bg=$_POWERLINE_SESSION_BG]#[$_POWERLINE_SESSION_ATTR]} #S #{?client_prefix,#[fg=$_POWERLINE_SESSION_PREFIX_BG],#[fg=$_POWERLINE_SESSION_BG]}#[bg=$_POWERLINE_BACKGROUND_BG]#[nobold]$_POWERLINE_LEFT_HARD_DIVIDER#(env $POWERLINE_COMMAND $POWERLINE_COMMAND_ARGS tmux left --width=#{client_width} -R width_adjust=#{status-right-length} -R pane_id=#{pane_id} -R pane_current_path=#{q:pane_current_path})" set -g window-status-format "#[$_POWERLINE_WINDOW_COLOR]$_POWERLINE_LEFT_HARD_DIVIDER_SPACES#I#{?window_flags,#F, } #[$_POWERLINE_WINDOW_DIVIDER_COLOR]$_POWERLINE_LEFT_SOFT_DIVIDER#[default]#W $_POWERLINE_LEFT_HARD_DIVIDER_SPACES" diff --git a/powerline/bindings/vim/plugin/powerline.vim b/powerline/bindings/vim/plugin/powerline.vim index 11ec05e9..b06a389b 100644 --- a/powerline/bindings/vim/plugin/powerline.vim +++ b/powerline/bindings/vim/plugin/powerline.vim @@ -26,14 +26,14 @@ if exists('g:powerline_pycmd') let s:pyeval = g:powerline_pyeval let s:has_python = 1 endif -elseif has('python') - let s:has_python = 1 - let s:pycmd = 'py' - let s:pyeval = get(g:, 'powerline_pyeval', 'pyeval') elseif has('python3') let s:has_python = 1 let s:pycmd = 'py3' let s:pyeval = get(g:, 'powerline_pyeval', 'py3eval') +elseif has('python') + let s:has_python = 1 + let s:pycmd = 'py' + let s:pyeval = get(g:, 'powerline_pyeval', 'pyeval') else let s:has_python = 0 endif diff --git a/powerline/bindings/wm/__init__.py b/powerline/bindings/wm/__init__.py index 646b701a..d2c6f30a 100644 --- a/powerline/bindings/wm/__init__.py +++ b/powerline/bindings/wm/__init__.py @@ -24,15 +24,6 @@ def i3_subscribe(conn, event, callback): :param func callback: Function to run on event. ''' - try: - import i3ipc - except ImportError: - import i3 - conn.Subscription(callback, event) - return - else: - pass - conn.on(event, callback) from threading import Thread @@ -57,12 +48,8 @@ def get_i3_connection(): ''' global conn if not conn: - try: - import i3ipc - except ImportError: - import i3 as conn - else: - conn = i3ipc.Connection() + import i3ipc + conn = i3ipc.Connection() return conn diff --git a/powerline/bindings/zsh/__init__.py b/powerline/bindings/zsh/__init__.py index 1037bba2..44f5c697 100644 --- a/powerline/bindings/zsh/__init__.py +++ b/powerline/bindings/zsh/__init__.py @@ -101,10 +101,7 @@ class Environment(object): return False -if hasattr(getattr(zsh, 'environ', None), '__contains__'): - environ = zsh.environ -else: - environ = Environment() +environ = Environment() if hasattr(zsh, 'expand') and zsh.expand('${:-}') == '': diff --git a/powerline/lib/vcs/git.py b/powerline/lib/vcs/git.py index 4175f739..bebc3117 100644 --- a/powerline/lib/vcs/git.py +++ b/powerline/lib/vcs/git.py @@ -179,19 +179,19 @@ except ImportError: return readlines(('git',) + args, directory) def stash(self): - return sum(1 for _ in self._gitcmd(self.directory, 'stash', 'list')) + return sum(1 for _ in self._gitcmd(self.directory, '--no-optional-locks', 'stash', 'list')) def do_status(self, directory, path): if path: try: - return next(self._gitcmd(directory, 'status', '--porcelain', '--ignored', '--', path))[:2] + return next(self._gitcmd(directory, '--no-optional-locks', 'status', '--porcelain', '--ignored', '--', path))[:2] except StopIteration: return None else: wt_column = ' ' index_column = ' ' untracked_column = ' ' - for line in self._gitcmd(directory, 'status', '--porcelain'): + for line in self._gitcmd(directory, '--no-optional-locks', 'status', '--porcelain'): if line[0] == '?': untracked_column = 'U' continue diff --git a/powerline/lint/spec.py b/powerline/lint/spec.py index 3111e14f..6a54441c 100644 --- a/powerline/lint/spec.py +++ b/powerline/lint/spec.py @@ -163,7 +163,7 @@ class Spec(object): '''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.update` and did not match any ``keyfunc`` provided via :py:meth:`Spec.unknown_spec`. :param msgfunc: diff --git a/powerline/listers/i3wm.py b/powerline/listers/i3wm.py index 0bbcfdc3..b1984e94 100644 --- a/powerline/listers/i3wm.py +++ b/powerline/listers/i3wm.py @@ -50,19 +50,14 @@ def workspace_lister(pl, segment_info, only_show=None, output=None): ( updated( segment_info, - output=w['output'], - workspace={ - 'name': w['name'], - 'visible': w['visible'], - 'urgent': w['urgent'], - 'focused': w['focused'], - }, + output=w.output, + workspace=w, ), { 'draw_inner_divider': None } ) for w in get_i3_connection().get_workspaces() - if (((not only_show or any(w[typ] for typ in only_show)) - and (not output or w['output'] == output))) + if (((not only_show or any(getattr(w, typ) for typ in only_show)) + and (not output or w.output == output))) ) diff --git a/powerline/renderer.py b/powerline/renderer.py index d1e3f1d4..31aca80e 100644 --- a/powerline/renderer.py +++ b/powerline/renderer.py @@ -251,7 +251,7 @@ class Renderer(object): for line in range(theme.get_line_number() - 1, 0, -1): yield self.render(side=None, line=line, **kwargs) - def render(self, mode=None, width=None, side=None, line=0, output_raw=False, output_width=False, segment_info=None, matcher_info=None): + def render(self, mode=None, width=None, side=None, line=0, output_raw=False, output_width=False, segment_info=None, matcher_info=None, hl_args=None): '''Render all segments. When a width is provided, low-priority segments are dropped one at @@ -286,6 +286,11 @@ class Renderer(object): :param matcher_info: Matcher information. Is processed in :py:meth:`get_segment_info` method. + :param dict hl_args: + Additional arguments to pass on the :py:meth:`hl` and + :py:meth`hlstyle` methods. They are ignored in the default + implementation, but renderer-specific overrides can make use of + them as run-time "configuration" information. ''' theme = self.get_theme(matcher_info) return self.do_render( @@ -297,6 +302,7 @@ class Renderer(object): output_width=output_width, segment_info=self.get_segment_info(segment_info, mode), theme=theme, + hl_args=hl_args ) def compute_divider_widths(self, theme): @@ -324,7 +330,7 @@ class Renderer(object): :return: Results of joining these segments. ''' - def do_render(self, mode, width, side, line, output_raw, output_width, segment_info, theme): + def do_render(self, mode, width, side, line, output_raw, output_width, segment_info, theme, hl_args): '''Like Renderer.render(), but accept theme in place of matcher_info ''' segments = list(theme.get_segments(side, line, segment_info, mode)) @@ -333,14 +339,16 @@ class Renderer(object): self._prepare_segments(segments, output_width or width) + hl_args = hl_args or dict() + if not width: # 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(self.hl_join([ segment['_rendered_hl'] - for segment in self._render_segments(theme, segments) - ]) + self.hlstyle(), segments, current_width, output_raw, output_width) + for segment in self._render_segments(theme, segments, hl_args) + ]) + self.hlstyle(**hl_args), segments, current_width, output_raw, output_width) divider_widths = self.compute_divider_widths(theme) @@ -394,10 +402,10 @@ class Renderer(object): rendered_highlighted = self.hl_join([ segment['_rendered_hl'] - for segment in self._render_segments(theme, segments) + for segment in self._render_segments(theme, segments, hl_args) ]) if rendered_highlighted: - rendered_highlighted += self.hlstyle() + rendered_highlighted += self.hlstyle(**hl_args) return construct_returned_value(rendered_highlighted, segments, current_width, output_raw, output_width) @@ -470,7 +478,7 @@ class Renderer(object): ret += segment_len return ret - def _render_segments(self, theme, segments, render_highlighted=True): + def _render_segments(self, theme, segments, hl_args, render_highlighted=True): '''Internal segment rendering method. This method loops through the segment array and compares the @@ -527,6 +535,10 @@ class Renderer(object): contents_highlighted = '' draw_divider = segment['draw_' + divider_type + '_divider'] + segment_hl_args = {} + segment_hl_args.update(segment['highlight']) + segment_hl_args.update(hl_args) + # XXX Make sure self.hl() calls are called in the same order # segments are displayed. This is needed for Vim renderer to work. if draw_divider: @@ -546,14 +558,14 @@ class Renderer(object): if side == 'left': if render_highlighted: - contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight']) - divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False) + contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args) + divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False, **hl_args) segment['_rendered_raw'] = contents_raw + divider_raw segment['_rendered_hl'] = contents_highlighted + divider_highlighted else: if render_highlighted: - divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False) - contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight']) + divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False, **hl_args) + contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args) segment['_rendered_raw'] = divider_raw + contents_raw segment['_rendered_hl'] = divider_highlighted + contents_highlighted else: @@ -562,7 +574,7 @@ class Renderer(object): else: contents_raw = contents_raw + outer_padding - contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight']) + contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args) segment['_rendered_raw'] = contents_raw segment['_rendered_hl'] = contents_highlighted prev_segment = segment @@ -576,7 +588,7 @@ class Renderer(object): ''' return string.translate(self.character_translations) - def hlstyle(fg=None, bg=None, attrs=None): + def hlstyle(fg=None, bg=None, attrs=None, **kwargs): '''Output highlight style string. Assuming highlighted string looks like ``{style}{contents}`` this method @@ -585,10 +597,10 @@ class Renderer(object): ''' raise NotImplementedError - def hl(self, contents, fg=None, bg=None, attrs=None): + def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs): '''Output highlighted chunk. This implementation just outputs :py:meth:`hlstyle` joined with ``contents``. ''' - return self.hlstyle(fg, bg, attrs) + (contents or '') + return self.hlstyle(fg, bg, attrs, **kwargs) + (contents or '') diff --git a/powerline/renderers/i3bar.py b/powerline/renderers/i3bar.py index 020a93d8..3eab61fa 100644 --- a/powerline/renderers/i3bar.py +++ b/powerline/renderers/i3bar.py @@ -17,7 +17,7 @@ class I3barRenderer(Renderer): # We don’t need to explicitly reset attributes, so skip those calls return '' - def hl(self, contents, fg=None, bg=None, attrs=None): + def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs): segment = { 'full_text': contents, 'separator': False, diff --git a/powerline/renderers/ipython/since_5.py b/powerline/renderers/ipython/since_5.py index 8a26da72..88c7625e 100644 --- a/powerline/renderers/ipython/since_5.py +++ b/powerline/renderers/ipython/since_5.py @@ -90,7 +90,7 @@ class IPythonPygmentsRenderer(IPythonRenderer): def hl_join(segments): return reduce(operator.iadd, segments, []) - def hl(self, contents, fg=None, bg=None, attrs=None): + def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs): '''Output highlighted chunk. This implementation outputs a list containing a single pair diff --git a/powerline/renderers/lemonbar.py b/powerline/renderers/lemonbar.py index f378f235..8156807b 100644 --- a/powerline/renderers/lemonbar.py +++ b/powerline/renderers/lemonbar.py @@ -21,7 +21,7 @@ class LemonbarRenderer(Renderer): # We don’t need to explicitly reset attributes, so skip those calls return '' - def hl(self, contents, fg=None, bg=None, attrs=None): + def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs): text = '' if fg is not None: diff --git a/powerline/renderers/pango_markup.py b/powerline/renderers/pango_markup.py index 1b7d624b..3c1a6755 100644 --- a/powerline/renderers/pango_markup.py +++ b/powerline/renderers/pango_markup.py @@ -15,7 +15,7 @@ class PangoMarkupRenderer(Renderer): # We don’t need to explicitly reset attributes, so skip those calls return '' - def hl(self, contents, fg=None, bg=None, attrs=None): + def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs): '''Highlight a segment.''' awesome_attr = [] if fg is not None: diff --git a/powerline/renderers/shell/__init__.py b/powerline/renderers/shell/__init__.py index ebb05019..d7dbf96b 100644 --- a/powerline/renderers/shell/__init__.py +++ b/powerline/renderers/shell/__init__.py @@ -105,7 +105,7 @@ class ShellRenderer(PromptRenderer): self.used_term_escape_style = self.term_escape_style return super(ShellRenderer, self).do_render(segment_info=segment_info, **kwargs) - def hlstyle(self, fg=None, bg=None, attrs=None): + def hlstyle(self, fg=None, bg=None, attrs=None, escape=True, **kwargs): '''Highlight a segment. If an argument is None, the argument is ignored. If an argument is @@ -162,7 +162,7 @@ class ShellRenderer(PromptRenderer): r = '\033Ptmux;' + r.replace('\033', '\033\033') + '\033\\' elif self.screen_escape: r = '\033P' + r.replace('\033', '\033\033') + '\033\\' - return self.escape_hl_start + r + self.escape_hl_end + return self.escape_hl_start + r + self.escape_hl_end if escape else r def get_theme(self, matcher_info): if not matcher_info: diff --git a/powerline/renderers/shell/bash.py b/powerline/renderers/shell/bash.py index 783bd501..5ccf206b 100644 --- a/powerline/renderers/shell/bash.py +++ b/powerline/renderers/shell/bash.py @@ -6,13 +6,91 @@ from powerline.renderers.shell import ShellRenderer class BashPromptRenderer(ShellRenderer): '''Powerline bash prompt segment renderer.''' - escape_hl_start = '\[' - escape_hl_end = '\]' + escape_hl_start = '\\[' + escape_hl_end = '\\]' character_translations = ShellRenderer.character_translations.copy() character_translations[ord('$')] = '\\$' character_translations[ord('`')] = '\\`' character_translations[ord('\\')] = '\\\\' + def do_render(self, side, line, width, output_width, output_raw, hl_args, **kwargs): + + # we are rendering the normal left prompt + if side == 'left' and line == 0 and width is not None: + + # we need left prompt's width to render the raw spacer + output_width = output_width or output_raw + + left = super(BashPromptRenderer, self).do_render( + side=side, + line=line, + output_width=output_width, + width=width, + output_raw=output_raw, + hl_args=hl_args, + **kwargs + ) + left_rendered = left[0] if output_width else left + + # we don't escape color sequences in the right prompt so we can do escaping as a whole + if hl_args: + hl_args = hl_args.copy() + hl_args.update({'escape': False}) + else: + hl_args = {'escape': False} + + right = super(BashPromptRenderer, self).do_render( + side='right', + line=line, + output_width=True, + width=width, + output_raw=output_raw, + hl_args=hl_args, + **kwargs + ) + + ret = [] + if right[-1] > 0: + # if the right prompt is not empty we embed it in the left prompt + # it must be escaped as a whole so readline doesn't see it + ret.append(''.join(( + left_rendered, + self.escape_hl_start, + '\033[s', # save the cursor position + '\033[{0}C'.format(width), # move to the right edge of the terminal + '\033[{0}D'.format(right[-1] - 1), # move back to the right prompt position + right[0], + '\033[u', # restore the cursor position + self.escape_hl_end + ))) + if output_raw: + ret.append(''.join(( + left[1], + ' ' * (width - left[-1] - right[-1]), + right[1] + ))) + else: + ret.append(left_rendered) + if output_raw: + ret.append(left[1]) + if output_width: + ret.append(left[-1]) + if len(ret) == 1: + return ret[0] + else: + return ret + + else: + return super(BashPromptRenderer, self).do_render( + side=side, + line=line, + width=width, + output_width=output_width, + output_raw=output_raw, + hl_args=hl_args, + **kwargs + ) + renderer = BashPromptRenderer diff --git a/powerline/renderers/tmux.py b/powerline/renderers/tmux.py index ee6c09c2..fc3282a1 100644 --- a/powerline/renderers/tmux.py +++ b/powerline/renderers/tmux.py @@ -38,7 +38,7 @@ class TmuxRenderer(Renderer): width = 10 return super(TmuxRenderer, self).render(width=width, segment_info=segment_info, **kwargs) - def hlstyle(self, fg=None, bg=None, attrs=None): + def hlstyle(self, fg=None, bg=None, attrs=None, **kwargs): '''Highlight a segment.''' # We don’t need to explicitly reset attributes, so skip those calls if not attrs and not bg and not fg: @@ -68,7 +68,9 @@ class TmuxRenderer(Renderer): r = self.segment_info.copy() if segment_info: r.update(segment_info) - if 'pane_id' in r: + if 'pane_current_path' in r: + r['getcwd'] = lambda: r['pane_current_path'] + elif 'pane_id' in r: varname = 'TMUX_PWD_' + str(r['pane_id']) if varname in r['environ']: r['getcwd'] = lambda: r['environ'][varname] diff --git a/powerline/renderers/vim.py b/powerline/renderers/vim.py index 281177ce..a92d51c2 100644 --- a/powerline/renderers/vim.py +++ b/powerline/renderers/vim.py @@ -123,7 +123,7 @@ class VimRenderer(Renderer): def reset_highlight(self): self.hl_groups.clear() - def hlstyle(self, fg=None, bg=None, attrs=None): + def hlstyle(self, fg=None, bg=None, attrs=None, **kwargs): '''Highlight a segment. If an argument is None, the argument is ignored. If an argument is diff --git a/powerline/segments/common/env.py b/powerline/segments/common/env.py index 61f516e2..bbfe3e25 100644 --- a/powerline/segments/common/env.py +++ b/powerline/segments/common/env.py @@ -19,20 +19,24 @@ def environment(pl, segment_info, variable=None): @requires_segment_info -def virtualenv(pl, segment_info, ignore_venv=False, ignore_conda=False): +def virtualenv(pl, segment_info, ignore_venv=False, ignore_conda=False, ignored_names=("venv", ".venv")): '''Return the name of the current Python or conda virtualenv. - + :param list ignored_names: + Names of venvs to ignore. Will then get the name of the venv by ascending to the parent directory :param bool ignore_venv: Whether to ignore virtual environments. Default is False. :param bool ignore_conda: Whether to ignore conda environments. Default is False. ''' - return ( - (not ignore_venv and - os.path.basename(segment_info['environ'].get('VIRTUAL_ENV', ''))) or - (not ignore_conda and - segment_info['environ'].get('CONDA_DEFAULT_ENV', '')) or - None) + if not ignore_venv: + for candidate in reversed(segment_info['environ'].get('VIRTUAL_ENV', '').split("/")): + if candidate and candidate not in ignored_names: + return candidate + if not ignore_conda: + for candidate in reversed(segment_info['environ'].get('CONDA_DEFAULT_ENV', '').split("/")): + if candidate and candidate not in ignored_names: + return candidate + return None @requires_segment_info diff --git a/powerline/segments/common/players.py b/powerline/segments/common/players.py index bd4e4ec2..f43db0c1 100644 --- a/powerline/segments/common/players.py +++ b/powerline/segments/common/players.py @@ -31,6 +31,8 @@ def _convert_state(state): def _convert_seconds(seconds): '''Convert seconds to minutes:seconds format''' + if isinstance(seconds, str): + seconds = seconds.replace(",",".") return '{0:.0f}:{1:02.0f}'.format(*divmod(float(seconds), 60)) diff --git a/powerline/segments/common/sys.py b/powerline/segments/common/sys.py index a83025bf..29a2459a 100644 --- a/powerline/segments/common/sys.py +++ b/powerline/segments/common/sys.py @@ -90,8 +90,6 @@ try: 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, @@ -150,7 +148,8 @@ else: @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): +def uptime(pl, days_format='{days:d}d', hours_format=' {hours:d}h', minutes_format=' {minutes:02d}m', + seconds_format=' {seconds:02d}s', shorten_len=3): '''Return system uptime. :param str days_format: @@ -175,9 +174,11 @@ def uptime(pl, days_format='{days:d}d', hours_format=' {hours:d}h', minutes_form 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] + days_format.format(days=days) if days_format else None, + hours_format.format(hours=hours) if hours_format else None, + minutes_format.format(minutes=minutes) if minutes_format else None, + seconds_format.format(seconds=seconds) if seconds_format else None, + ])) + first_non_zero = next((i for i, x in enumerate([days, hours, minutes, seconds]) if x != 0)) + time_formatted = time_formatted[first_non_zero:first_non_zero + shorten_len] return ''.join(time_formatted).strip() diff --git a/powerline/segments/common/time.py b/powerline/segments/common/time.py index 1e2207b3..be727c9e 100644 --- a/powerline/segments/common/time.py +++ b/powerline/segments/common/time.py @@ -4,22 +4,33 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct from datetime import datetime -def date(pl, format='%Y-%m-%d', istime=False): +def date(pl, format='%Y-%m-%d', istime=False, timezone=None): '''Return the current date. :param str format: strftime-style date format string :param bool istime: If true then segment uses ``time`` highlight group. + :param string timezone: + Specify a timezone to use as ``+HHMM`` or ``-HHMM``. + (Defaults to system defaults.) Divider highlight group used: ``time:divider``. Highlight groups used: ``time`` or ``date``. ''' + try: - contents = datetime.now().strftime(format) + tz = datetime.strptime(timezone, '%z').tzinfo if timezone else None + except ValueError: + tz = None + + nw = datetime.now(tz) + + try: + contents = nw.strftime(format) except UnicodeEncodeError: - contents = datetime.now().strftime(format.encode('utf-8')).decode('utf-8') + contents = nw.strftime(format.encode('utf-8')).decode('utf-8') return [{ 'contents': contents, @@ -34,59 +45,77 @@ UNICODE_TEXT_TRANSLATION = { } -def fuzzy_time(pl, unicode_text=False): +def fuzzy_time(pl, format='{minute_str} {hour_str}', 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' + }, 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', + }): + '''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.) :param bool unicode_text: - If true then hyphenminuses (regular ASCII ``-``) and single quotes are + 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', - } + :param string timezone: + Specify a timezone to use as ``+HHMM`` or ``-HHMM``. + (Defaults to system defaults.) + :param string list hour_str: + 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. + :param dict special_case_str: + Special strings for special times. - now = datetime.now() + Highlight groups used: ``fuzzy_time``. + ''' try: - return special_case_str[(now.hour, now.minute)] + tz = datetime.strptime(timezone, '%z').tzinfo if timezone else None + except ValueError: + tz = None + + now = datetime.now(tz) + + try: + # We don't want to enforce a special type of spaces/ alignment in the input + from ast import literal_eval + special_case_str = {literal_eval(x):special_case_str[x] for x in special_case_str} + result = special_case_str[(now.hour, now.minute)] + if unicode_text: + result = result.translate(UNICODE_TEXT_TRANSLATION) + return result 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] + if now.minute >= 30: + hour = hour + 1 + hour = hour % len(hour_str) - 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]) + min_dis = 100 + min_pos = 0 + + for mn in minute_str: + mn = int(mn) + if now.minute >= mn and now.minute - mn < min_dis: + min_dis = now.minute - mn + min_pos = mn + 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]) if unicode_text: result = result.translate(UNICODE_TEXT_TRANSLATION) diff --git a/powerline/segments/common/wthr.py b/powerline/segments/common/wthr.py index 1c6d0809..2c54cca9 100644 --- a/powerline/segments/common/wthr.py +++ b/powerline/segments/common/wthr.py @@ -2,76 +2,80 @@ from __future__ import (unicode_literals, division, absolute_import, print_function) import json +from collections import namedtuple from powerline.lib.url import urllib_read, urllib_urlencode from powerline.lib.threaded import KwThreadedSegment from powerline.segments import with_docstring +_WeatherKey = namedtuple('Key', 'location_query weather_api_key') + + # 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', 'windy' ), # 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))): +# https://openweathermap.org/weather-conditions +weather_conditions_codes = { + 200: ('stormy',), + 201: ('stormy',), + 202: ('stormy',), + 210: ('stormy',), + 211: ('stormy',), + 212: ('stormy',), + 221: ('stormy',), + 230: ('stormy',), + 231: ('stormy',), + 232: ('stormy',), + 300: ('rainy',), + 301: ('rainy',), + 302: ('rainy',), + 310: ('rainy',), + 311: ('rainy',), + 312: ('rainy',), + 313: ('rainy',), + 314: ('rainy',), + 321: ('rainy',), + 500: ('rainy',), + 501: ('rainy',), + 502: ('rainy',), + 503: ('rainy',), + 504: ('rainy',), + 511: ('snowy',), + 520: ('rainy',), + 521: ('rainy',), + 522: ('rainy',), + 531: ('rainy',), + 600: ('snowy',), + 601: ('snowy',), + 602: ('snowy',), + 611: ('snowy',), + 612: ('snowy',), + 613: ('snowy',), + 615: ('snowy',), + 616: ('snowy',), + 620: ('snowy',), + 621: ('snowy',), + 622: ('snowy',), + 701: ('foggy',), + 711: ('foggy',), + 721: ('foggy',), + 731: ('foggy',), + 741: ('foggy',), + 751: ('foggy',), + 761: ('foggy',), + 762: ('foggy',), + 771: ('foggy',), + 781: ('foggy',), + 800: ('sunny',), + 801: ('cloudy',), + 802: ('cloudy',), + 803: ('cloudy',), + 804: ('cloudy',), +} + weather_conditions_icons = { 'day': 'DAY', 'blustery': 'WIND', @@ -88,9 +92,9 @@ weather_conditions_icons = { } temp_conversions = { - 'C': lambda temp: temp, - 'F': lambda temp: (temp * 9 / 5) + 32, - 'K': lambda temp: temp + 273.15, + 'C': lambda temp: temp - 273.15, + 'F': lambda temp: (temp * 9 / 5) - 459.67, + 'K': lambda temp: temp, } # Note: there are also unicode characters for units: ℃, ℉ and K @@ -105,38 +109,37 @@ class WeatherSegment(KwThreadedSegment): interval = 600 default_location = None location_urls = {} + weather_api_key = "fbc9549d91a5e4b26c15be0dbdac3460" @staticmethod def key(location_query=None, **kwargs): - return location_query - - def get_request_url(self, location_query): try: - return self.location_urls[location_query] + weather_api_key = kwargs["weather_api_key"] + except KeyError: + weather_api_key = WeatherSegment.weather_api_key + return _WeatherKey(location_query, weather_api_key) + + def get_request_url(self, weather_key): + try: + return self.location_urls[weather_key] except KeyError: - if location_query is None: - location_data = json.loads(urllib_read('http://geoip.nekudo.com/api/')) - location = ','.join(( - location_data['city'], - location_data['country']['name'], - location_data['country']['code'] - )) - self.info('Location returned by nekudo 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 weather.forecast where woeid in' - ' (select woeid from geo.places(1) where text="{0}") and u="c"'.format(location).encode('utf-8'), - 'format': 'json', + "appid": weather_key.weather_api_key } + location_query = weather_key.location_query + if location_query is None: + location_data = json.loads(urllib_read('https://freegeoip.app/json/')) + query_data["lat"] = location_data["latitude"] + query_data["lon"] = location_data["longitude"] + else: + query_data["q"] = location_query self.location_urls[location_query] = url = ( - 'http://query.yahooapis.com/v1/public/yql?' + urllib_urlencode(query_data)) + "https://api.openweathermap.org/data/2.5/weather?" + + urllib_urlencode(query_data)) return url - def compute_state(self, location_query): - url = self.get_request_url(location_query) + def compute_state(self, weather_key): + url = self.get_request_url(weather_key) raw_response = urllib_read(url) if not raw_response: self.error('Failed to get response') @@ -144,22 +147,18 @@ class WeatherSegment(KwThreadedSegment): response = json.loads(raw_response) try: - condition = response['query']['results']['channel']['item']['condition'] - condition_code = int(condition['code']) - temp = float(condition['temp']) + condition = response['weather'][0] + condition_code = int(condition['id']) + temp = float(response['main']['temp']) except (KeyError, ValueError): - self.exception('Yahoo returned malformed or unexpected response: {0}', repr(raw_response)) + self.exception('OpenWeatherMap returned malformed or unexpected response: {0}', repr(raw_response)) return None 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) + icon_names = ('unknown',) + self.error('Unknown condition code: {0}', condition_code) return (temp, icon_names) @@ -179,12 +178,12 @@ class WeatherSegment(KwThreadedSegment): temp_format = temp_format or ('{temp:.0f}' + temp_units[unit]) converted_temp = temp_conversions[unit](temp) - if temp <= temp_coldest: + if converted_temp <= temp_coldest: gradient_level = 0 - elif temp >= temp_hottest: + elif converted_temp >= temp_hottest: gradient_level = 100 else: - gradient_level = (temp - temp_coldest) * 100.0 / (temp_hottest - temp_coldest) + gradient_level = (converted_temp - temp_coldest) * 100.0 / (temp_hottest - temp_coldest) groups = ['weather_condition_' + icon_name for icon_name in icon_names] + ['weather_conditions', 'weather'] return [ { @@ -202,9 +201,9 @@ class WeatherSegment(KwThreadedSegment): weather = with_docstring(WeatherSegment(), -'''Return weather from Yahoo! Weather. +'''Return weather from OpenWeatherMaps. -Uses GeoIP lookup from http://geoip.nekudo.com to automatically determine +Uses GeoIP lookup from https://freegeoip.app 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. @@ -231,5 +230,5 @@ weather conditions. 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. +Also uses ``weather_conditions_{condition}`` for all weather conditions supported by OpenWeatherMap. ''') diff --git a/powerline/segments/i3wm.py b/powerline/segments/i3wm.py index 3f508ee2..a6c0ee85 100644 --- a/powerline/segments/i3wm.py +++ b/powerline/segments/i3wm.py @@ -12,11 +12,11 @@ WORKSPACE_REGEX = re.compile(r'^[0-9]+: ?') def workspace_groups(w): group = [] - if w['focused']: + if w.focused: group.append('w_focused') - if w['urgent']: + if w.urgent: group.append('w_urgent') - if w['visible']: + if w.visible: group.append('w_visible') group.append('workspace') return group @@ -52,12 +52,12 @@ def workspaces(pl, segment_info, only_show=None, output=None, strip=0): return [ { - 'contents': w['name'][strip:], + 'contents': w.name[strip:], 'highlight_groups': workspace_groups(w) } for w in get_i3_connection().get_workspaces() - if ((not only_show or any(w[typ] for typ in only_show)) - and (not output or w['output'] == output)) + if ((not only_show or any(getattr(w, typ) for typ in only_show)) + and (not output or w.output == output)) ] @@ -80,7 +80,7 @@ def workspace(pl, segment_info, workspace=None, strip=False): try: w = next(( w for w in get_i3_connection().get_workspaces() - if w['name'] == workspace + if w.name == workspace )) except StopIteration: return None @@ -90,13 +90,13 @@ def workspace(pl, segment_info, workspace=None, strip=False): try: w = next(( w for w in get_i3_connection().get_workspaces() - if w['focused'] + if w.focused )) except StopIteration: return None return [{ - 'contents': format_name(w['name'], strip=strip), + 'contents': format_name(w.name, strip=strip), 'highlight_groups': workspace_groups(w) }] @@ -150,6 +150,6 @@ def scratchpad(pl, icons=SCRATCHPAD_ICONS): 'contents': icons.get(w.scratchpad_state, icons['changed']), 'highlight_groups': scratchpad_groups(w) } - for w in get_i3_connection().get_tree().descendents() + for w in get_i3_connection().get_tree().descendants() if w.scratchpad_state != 'none' ] diff --git a/powerline/version.py b/powerline/version.py index 01e89fa4..8b5195f1 100644 --- a/powerline/version.py +++ b/powerline/version.py @@ -4,7 +4,7 @@ from __future__ import (unicode_literals, division, absolute_import, print_funct import subprocess from traceback import print_exc -__version__ = "2.8.1" +__version__ = "2.8.2" def get_version(): try: diff --git a/powerline/vim.py b/powerline/vim.py index 603a6a50..2638c910 100644 --- a/powerline/vim.py +++ b/powerline/vim.py @@ -262,6 +262,7 @@ class VimPowerline(Powerline): def new_win_idx(self, window_id): r = None + for window in vim.windows: try: curwindow_id = window.vars['powerline_window_id'] @@ -302,7 +303,18 @@ class VimPowerline(Powerline): return self.render(window, window_id, winnr) def tabline(self): - return self.render(*self.win_idx(None), is_tabline=True) + r = self.win_idx(None) + + if r: + return self.render(*r, is_tabline=True) + else: + win = vim.current.window + r = ( + win, + win.vars.get('powerline_window_id', self.last_window_id), + win.number, + ) + return self.render(*r, is_tabline=True) def new_window(self): return self.render(*self.win_idx(None)) diff --git a/setup.py b/setup.py index 74779501..2571adad 100644 --- a/setup.py +++ b/setup.py @@ -46,14 +46,13 @@ except Exception as e: else: sys.path.append(CURRENT_DIR) from powerline.lib.shell import which + can_use_scripts = True if which('socat') and which('sed') and which('sh'): print('Using powerline.sh script instead of C version (requires socat, sed and sh)') shutil.copyfile('client/powerline.sh', 'scripts/powerline') - can_use_scripts = True else: print('Using powerline.py script instead of C version') shutil.copyfile('client/powerline.py', 'scripts/powerline') - can_use_scripts = True else: can_use_scripts = False @@ -71,10 +70,10 @@ setup( 'Natural Language :: English', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], diff --git a/tests/modules/lib/__init__.py b/tests/modules/lib/__init__.py index f3e36b81..f6df9204 100644 --- a/tests/modules/lib/__init__.py +++ b/tests/modules/lib/__init__.py @@ -44,15 +44,23 @@ def urllib_read(query_url): return '127.0.0.1' elif query_url.startswith('http://ipv6.icanhazip.com'): return '2001:4801:7818:6:abc5:ba2c:ff10:275f' + + 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"}' elif query_url.startswith('http://query.yahooapis.com/v1/public/'): - if 'Meppen' in query_url: + 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"}}}}}}' elif 'Moscow' in query_url: return r'{"query":{"count":1,"created":"2016-05-13T19:47:01Z","lang":"en-US","results":{"channel":{"units":{"distance":"mi","pressure":"in","speed":"mph","temperature":"C"},"title":"Yahoo! Weather - Moscow, Moscow Federal City, RU","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-2122265/","description":"Yahoo! Weather for Moscow, Moscow Federal City, RU","language":"en-us","lastBuildDate":"Fri, 13 May 2016 10:47 PM MSK","ttl":"60","location":{"city":"Moscow","country":"Russia","region":" Moscow Federal City"},"wind":{"chill":"45","direction":"80","speed":"11"},"atmosphere":{"humidity":"52","pressure":"993.0","rising":"0","visibility":"16.1"},"astronomy":{"sunrise":"4:19 am","sunset":"8:34 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 Moscow, Moscow Federal City, RU at 09:00 PM MSK","lat":"55.741638","long":"37.605061","link":"http://us.rd.yahoo.com/dailynews/rss/weather/Country__Country/*https://weather.yahoo.com/country/state/city-2122265/","pubDate":"Fri, 13 May 2016 09:00 PM MSK","condition":{"code":"33","date":"Fri, 13 May 2016 09:00 PM MSK","temp":"9","text":"Mostly Clear"},"forecast":[{"code":"30","date":"13 May 2016","day":"Fri","high":"62","low":"41","text":"Partly Cloudy"},{"code":"30","date":"14 May 2016","day":"Sat","high":"64","low":"43","text":"Partly Cloudy"},{"code":"30","date":"15 May 2016","day":"Sun","high":"63","low":"44","text":"Partly Cloudy"},{"code":"12","date":"16 May 2016","day":"Mon","high":"60","low":"47","text":"Rain"},{"code":"12","date":"17 May 2016","day":"Tue","high":"64","low":"48","text":"Rain"},{"code":"28","date":"18 May 2016","day":"Wed","high":"67","low":"48","text":"Mostly Cloudy"},{"code":"12","date":"19 May 2016","day":"Thu","high":"68","low":"49","text":"Rain"},{"code":"39","date":"20 May 2016","day":"Fri","high":"66","low":"50","text":"Scattered Showers"},{"code":"39","date":"21 May 2016","day":"Sat","high":"69","low":"49","text":"Scattered Showers"},{"code":"30","date":"22 May 2016","day":"Sun","high":"73","low":"50","text":"Partly Cloudy"}],"description":"\n
\nCurrent Conditions:\n
Mostly Clear\n
\n
\nForecast:\n
Fri - Partly Cloudy. High: 62Low: 41\n
Sat - Partly Cloudy. High: 64Low: 43\n
Sun - Partly Cloudy. High: 63Low: 44\n
Mon - Rain. High: 60Low: 47\n
Tue - Rain. High: 64Low: 48\n
\n
\nFull Forecast at Yahoo! Weather\n
\n
\n(provided by The Weather Channel)\n
\n]]>","guid":{"isPermaLink":"false"}}}}}}' - else: - raise NotImplementedError + elif query_url.startswith('https://api.openweathermap.org/data/2.5/'): + if 'Meppen' in query_url or '52.6833' in query_url: + return r'{"coord":{"lon":7.29,"lat":52.69},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"base":"stations","main":{"temp":293.15,"feels_like":295.16,"temp_min":293.15,"temp_max":295.37,"pressure":1018,"humidity":77},"visibility":10000,"wind":{"speed":1.12,"deg":126},"clouds":{"all":0},"dt":1600196220,"sys":{"type":1,"id":1871,"country":"DE","sunrise":1600146332,"sunset":1600191996},"timezone":7200,"id":2871845,"name":"Meppen","cod":200}' + elif 'Moscow' in query_url: + return r'{"coord":{"lon":37.62,"lat":55.75},"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}],"base":"stations","main":{"temp":283.15,"feels_like":280.78,"temp_min":283.15,"temp_max":284.26,"pressure":1019,"humidity":71},"visibility":10000,"wind":{"speed":3,"deg":330},"clouds":{"all":0},"dt":1600196224,"sys":{"type":1,"id":9029,"country":"RU","sunrise":1600138909,"sunset":1600184863},"timezone":10800,"id":524901,"name":"Moscow","cod":200}' + + raise NotImplementedError class Process(object): diff --git a/tests/test_python/test_listers.py b/tests/test_python/test_listers.py index a33f0333..f2368dfa 100644 --- a/tests/test_python/test_listers.py +++ b/tests/test_python/test_listers.py @@ -8,14 +8,16 @@ from tests.modules import TestCase class TestI3WM(TestCase): + workspaces = [ + Args(name='1: w1', output='LVDS1', focused=False, urgent=False, visible=False), + Args(name='2: w2', output='LVDS1', focused=False, urgent=False, visible=True), + Args(name='3: w3', output='HDMI1', focused=False, urgent=True, visible=True), + Args(name='4: w4', output='DVI01', focused=True, urgent=True, visible=True), + ] + @staticmethod def get_workspaces(): - return iter([ - {'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False}, - {'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True}, - {'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True}, - {'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True}, - ]) + return iter(TestI3WM.workspaces) @staticmethod def get_outputs(pl): @@ -46,42 +48,22 @@ class TestI3WM(TestCase): ({ 'a': 1, 'output': 'LVDS1', - 'workspace': { - 'name': '1: w1', - 'focused': False, - 'urgent': False, - 'visible': False - } + 'workspace': self.workspaces[0], }, {'draw_inner_divider': None}), ({ 'a': 1, 'output': 'LVDS1', - 'workspace': { - 'name': '2: w2', - 'focused': False, - 'urgent': False, - 'visible': True - } + 'workspace': self.workspaces[1], }, {'draw_inner_divider': None}), ({ 'a': 1, 'output': 'HDMI1', - 'workspace': { - 'name': '3: w3', - 'focused': False, - 'urgent': True, - 'visible': True - } + 'workspace': self.workspaces[2], }, {'draw_inner_divider': None}), ({ 'a': 1, 'output': 'DVI01', - 'workspace': { - 'name': '4: w4', - 'focused': True, - 'urgent': True, - 'visible': True - } + 'workspace': self.workspaces[3], }, {'draw_inner_divider': None}), ] ) @@ -92,22 +74,12 @@ class TestI3WM(TestCase): ({ 'a': 1, 'output': 'LVDS1', - 'workspace': { - 'name': '1: w1', - 'focused': False, - 'urgent': False, - 'visible': False - } + 'workspace': self.workspaces[0], }, {'draw_inner_divider': None}), ({ 'a': 1, 'output': 'LVDS1', - 'workspace': { - 'name': '2: w2', - 'focused': False, - 'urgent': False, - 'visible': True - } + 'workspace': self.workspaces[1], }, {'draw_inner_divider': None}), ] ) @@ -121,22 +93,12 @@ class TestI3WM(TestCase): ({ 'a': 1, 'output': 'LVDS1', - 'workspace': { - 'name': '1: w1', - 'focused': False, - 'urgent': False, - 'visible': False - } + 'workspace': self.workspaces[0], }, {'draw_inner_divider': None}), ({ 'a': 1, 'output': 'LVDS1', - 'workspace': { - 'name': '2: w2', - 'focused': False, - 'urgent': False, - 'visible': True - } + 'workspace': self.workspaces[1], }, {'draw_inner_divider': None}), ] ) @@ -151,42 +113,22 @@ class TestI3WM(TestCase): ({ 'a': 1, 'output': 'LVDS1', - 'workspace': { - 'name': '1: w1', - 'focused': False, - 'urgent': False, - 'visible': False - } + 'workspace': self.workspaces[0], }, {'draw_inner_divider': None}), ({ 'a': 1, 'output': 'LVDS1', - 'workspace': { - 'name': '2: w2', - 'focused': False, - 'urgent': False, - 'visible': True - } + 'workspace': self.workspaces[1], }, {'draw_inner_divider': None}), ({ 'a': 1, 'output': 'HDMI1', - 'workspace': { - 'name': '3: w3', - 'focused': False, - 'urgent': True, - 'visible': True - } + 'workspace': self.workspaces[2], }, {'draw_inner_divider': None}), ({ 'a': 1, 'output': 'DVI01', - 'workspace': { - 'name': '4: w4', - 'focused': True, - 'urgent': True, - 'visible': True - } + 'workspace': self.workspaces[3], }, {'draw_inner_divider': None}), ] ) @@ -201,22 +143,12 @@ class TestI3WM(TestCase): ({ 'a': 1, 'output': 'HDMI1', - 'workspace': { - 'name': '3: w3', - 'focused': False, - 'urgent': True, - 'visible': True - } + 'workspace': self.workspaces[2], }, {'draw_inner_divider': None}), ({ 'a': 1, 'output': 'DVI01', - 'workspace': { - 'name': '4: w4', - 'focused': True, - 'urgent': True, - 'visible': True - } + 'workspace': self.workspaces[3], }, {'draw_inner_divider': None}), ] ) diff --git a/tests/test_python/test_segments.py b/tests/test_python/test_segments.py index 8d6cbae1..1ee8cd0a 100644 --- a/tests/test_python/test_segments.py +++ b/tests/test_python/test_segments.py @@ -687,6 +687,10 @@ class TestEnv(TestCommon): self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), 'ghi') self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), None) self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None) + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignored_names=["aaa"]), "ghi") + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignored_names=["ghi"]), "def") + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignored_names=["def", "ghi"]), "abc") + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignored_names=["abc", "def", "ghi"]), None) segment_info['environ'].pop('VIRTUAL_ENV') self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), None) @@ -696,6 +700,7 @@ class TestEnv(TestCommon): with replace_env('CONDA_DEFAULT_ENV', 'foo') as segment_info: self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), 'foo') + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignored_names=["foo"]), None) self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_conda=True), None) self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), 'foo') self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None) @@ -718,6 +723,9 @@ class TestEnv(TestCommon): self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True), None) self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info, ignore_venv=True, ignore_conda=True), None) + with replace_env('VIRTUAL_ENV', '/abc/def/venv') as segment_info: + self.assertEqual(self.module.virtualenv(pl=pl, segment_info=segment_info), 'def') + def test_environment(self): pl = Pl() variable = 'FOO' @@ -819,9 +827,11 @@ class TestTime(TestCommon): def test_date(self): pl = Pl() - with replace_attr(self.module, 'datetime', Args(now=lambda: Args(strftime=lambda fmt: fmt))): + with replace_attr(self.module, 'datetime', Args(strptime=lambda timezone, fmt: Args(tzinfo=timezone), now=lambda tz:Args(strftime=lambda fmt: fmt + (tz if tz else '')))): self.assertEqual(self.module.date(pl=pl), [{'contents': '%Y-%m-%d', 'highlight_groups': ['date'], 'divider_highlight_group': None}]) + self.assertEqual(self.module.date(pl=pl, timezone='+0900'), [{'contents': '%Y-%m-%d+0900', 'highlight_groups': ['date'], 'divider_highlight_group': None}]) self.assertEqual(self.module.date(pl=pl, format='%H:%M', istime=True), [{'contents': '%H:%M', 'highlight_groups': ['time', 'date'], 'divider_highlight_group': 'time:divider'}]) + self.assertEqual(self.module.date(pl=pl, format='%H:%M', istime=True, timezone='-0900'), [{'contents': '%H:%M-0900', 'highlight_groups': ['time', 'date'], 'divider_highlight_group': 'time:divider'}]) unicode_date = self.module.date(pl=pl, format='\u231a', istime=True) expected_unicode_date = [{'contents': '\u231a', 'highlight_groups': ['time', 'date'], 'divider_highlight_group': 'time:divider'}] if python_implementation() == 'PyPy' and sys.version_info >= (3,): @@ -832,23 +842,49 @@ class TestTime(TestCommon): def test_fuzzy_time(self): time = Args(hour=0, minute=45) pl = Pl() - with replace_attr(self.module, 'datetime', Args(now=lambda: time)): + 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'} + special_case_str = { + '(23, 58)': '~ midnight', + '(23, 59)': '~ midnight', + '(0, 0)': 'midnight', + '(0, 1)': '~ midnight', + '(0, 2)': '~ midnight', + '(12, 0)': 'twelve o\'clock'} + 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') time.hour = 23 time.minute = 59 + self.assertEqual(self.module.fuzzy_time(pl=pl, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str), '~ midnight') self.assertEqual(self.module.fuzzy_time(pl=pl), 'round about midnight') + time.hour = 11 time.minute = 33 + self.assertEqual(self.module.fuzzy_time(pl=pl, hour_str=hour_str, minute_str=minute_str, special_case_str=special_case_str),'twenty-five to 12') self.assertEqual(self.module.fuzzy_time(pl=pl), 'twenty-five to twelve') - time.minute = 60 - self.assertEqual(self.module.fuzzy_time(pl=pl), 'twelve o\'clock') + 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), 'noon') + time.hour = 11 time.minute = 33 + 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), 'twenty-five to 12') self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False), 'twenty-five to twelve') - time.minute = 60 - self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=False), 'twelve o\'clock') + 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), 'noon') + time.hour = 11 time.minute = 33 + 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), 'twenty‐five to 12') self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True), 'twenty‐five to twelve') - time.minute = 60 - self.assertEqual(self.module.fuzzy_time(pl=pl, unicode_text=True), 'twelve o’clock') + 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),'noon') class TestSys(TestCommon): @@ -857,10 +893,10 @@ class TestSys(TestCommon): def test_uptime(self): pl = Pl() with replace_attr(self.module, '_get_uptime', lambda: 259200): - self.assertEqual(self.module.uptime(pl=pl), [{'contents': '3d', 'divider_highlight_group': 'background:divider'}]) + self.assertEqual(self.module.uptime(pl=pl), [{'contents': '3d 0h 00m', 'divider_highlight_group': 'background:divider'}]) with replace_attr(self.module, '_get_uptime', lambda: 93784): - self.assertEqual(self.module.uptime(pl=pl), [{'contents': '1d 2h 3m', 'divider_highlight_group': 'background:divider'}]) - self.assertEqual(self.module.uptime(pl=pl, shorten_len=4), [{'contents': '1d 2h 3m 4s', 'divider_highlight_group': 'background:divider'}]) + self.assertEqual(self.module.uptime(pl=pl), [{'contents': '1d 2h 03m', 'divider_highlight_group': 'background:divider'}]) + self.assertEqual(self.module.uptime(pl=pl, shorten_len=4), [{'contents': '1d 2h 03m 04s', 'divider_highlight_group': 'background:divider'}]) with replace_attr(self.module, '_get_uptime', lambda: 65536): self.assertEqual(self.module.uptime(pl=pl), [{'contents': '18h 12m 16s', 'divider_highlight_group': 'background:divider'}]) self.assertEqual(self.module.uptime(pl=pl, shorten_len=2), [{'contents': '18h 12m', 'divider_highlight_group': 'background:divider'}]) @@ -919,46 +955,47 @@ class TestWthr(TestCommon): pl = Pl() with replace_attr(self.module, 'urllib_read', urllib_read): self.assertEqual(self.module.weather(pl=pl), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_sunny', 'weather_conditions', 'weather'], 'contents': 'SUN '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '20°C', 'gradient_level': 71.42857142857143} ]) self.assertEqual(self.module.weather(pl=pl, temp_coldest=0, temp_hottest=100), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 14.0} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_sunny', 'weather_conditions', 'weather'], 'contents': 'SUN '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '20°C', 'gradient_level': 20} ]) self.assertEqual(self.module.weather(pl=pl, temp_coldest=-100, temp_hottest=-50), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 100} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_sunny', 'weather_conditions', 'weather'], 'contents': 'SUN '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '20°C', 'gradient_level': 100} ]) - self.assertEqual(self.module.weather(pl=pl, icons={'blustery': 'o'}), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'o '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854} - ]) - self.assertEqual(self.module.weather(pl=pl, icons={'windy': 'x'}), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'x '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854} + self.assertEqual(self.module.weather(pl=pl, icons={'sunny': 'o'}), [ + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_sunny', 'weather_conditions', 'weather'], 'contents': 'o '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '20°C', 'gradient_level': 71.42857142857143} ]) + # Test is disabled as no request has more than 1 weather condition associated currently + # self.assertEqual(self.module.weather(pl=pl, icons={'windy': 'x'}), [ + # {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'x '}, + # {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854} + # ]) self.assertEqual(self.module.weather(pl=pl, unit='F'), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '57°F', 'gradient_level': 62.857142857142854} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_sunny', 'weather_conditions', 'weather'], 'contents': 'SUN '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '68°F', 'gradient_level': 100} ]) self.assertEqual(self.module.weather(pl=pl, unit='K'), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '287K', 'gradient_level': 62.857142857142854} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_sunny', 'weather_conditions', 'weather'], 'contents': 'SUN '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '293K', 'gradient_level': 100} ]) self.assertEqual(self.module.weather(pl=pl, temp_format='{temp:.1e}C'), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '1.4e+01C', 'gradient_level': 62.857142857142854} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_sunny', 'weather_conditions', 'weather'], 'contents': 'SUN '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '2.0e+01C', 'gradient_level': 71.42857142857143} ]) with replace_attr(self.module, 'urllib_read', urllib_read): self.module.weather.startup(pl=pl, location_query='Meppen,06,DE') self.assertEqual(self.module.weather(pl=pl), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_blustery', 'weather_condition_windy', 'weather_conditions', 'weather'], 'contents': 'WINDY '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '14°C', 'gradient_level': 62.857142857142854} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_sunny', 'weather_conditions', 'weather'], 'contents': 'SUN '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '20°C', 'gradient_level': 71.42857142857143} ]) self.assertEqual(self.module.weather(pl=pl, location_query='Moscow,RU'), [ - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_fair_night', 'weather_condition_night', 'weather_conditions', 'weather'], 'contents': 'NIGHT '}, - {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '9°C', 'gradient_level': 55.714285714285715} + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_condition_sunny', 'weather_conditions', 'weather'], 'contents': 'SUN '}, + {'divider_highlight_group': 'background:divider', 'highlight_groups': ['weather_temp_gradient', 'weather_temp', 'weather'], 'contents': '10°C', 'gradient_level': 57.142857142857146} ]) self.module.weather.shutdown() @@ -967,10 +1004,10 @@ class TestI3WM(TestCase): @staticmethod def get_workspaces(): return iter([ - {'name': '1: w1', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': False}, - {'name': '2: w2', 'output': 'LVDS1', 'focused': False, 'urgent': False, 'visible': True}, - {'name': '3: w3', 'output': 'HDMI1', 'focused': False, 'urgent': True, 'visible': True}, - {'name': '4: w4', 'output': 'DVI01', 'focused': True, 'urgent': True, 'visible': True}, + Args(name='1: w1', output='LVDS1', focused = False, urgent = False, visible = False), + Args(name='2: w2', output='LVDS1', focused = False, urgent = False, visible = True), + Args(name='3: w3', output='HDMI1', focused = False, urgent = True, visible = True), + Args(name='4: w4', output='DVI01', focused = True, urgent = True, visible = True), ]) def test_workspaces(self): @@ -1056,7 +1093,7 @@ class TestI3WM(TestCase): def get_tree(self): return self - def descendents(self): + def descendants(self): nodes_unfocused = [Args(focused = False)] nodes_focused = [Args(focused = True)] diff --git a/tests/test_shells/inputs/bash b/tests/test_shells/inputs/bash index 1b68b6f6..b0d14785 100644 --- a/tests/test_shells/inputs/bash +++ b/tests/test_shells/inputs/bash @@ -4,6 +4,7 @@ set_theme_option() { set_theme() { export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1" } +set_theme_option default.segment_data.hostname.args.only_if_ssh false set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false ABOVE_LEFT='[{ "left": [ @@ -40,7 +41,9 @@ VIRTUAL_ENV= bgscript.sh & waitpid.sh false kill `cat pid` ; sleep 1s +set_theme_option default.segment_data.hostname.display false set_theme_option default_leftonly.segment_data.hostname.display false +set_theme_option default.segment_data.user.display false set_theme_option default_leftonly.segment_data.user.display false echo ' abc @@ -56,14 +59,15 @@ cd ../'$(echo)' cd ../'`echo`' cd ../'«Unicode!»' (exit 42)|(exit 43) -set_theme_option default_leftonly.segments.above "$ABOVE_LEFT" +set_theme default +set_theme_option default.segments.above "$ABOVE_LEFT" export DISPLAYED_ENV_VAR=foo unset DISPLAYED_ENV_VAR -set_theme_option default_leftonly.segments.above "$ABOVE_FULL" +set_theme_option default.segments.above "$ABOVE_FULL" export DISPLAYED_ENV_VAR=foo unset DISPLAYED_ENV_VAR -set_theme_option default_leftonly.segments.above -set_theme_option default_leftonly.dividers.left.hard \$ABC +set_theme_option default.segments.above +set_theme_option default.dividers.left.hard \$ABC false true is the last line exit diff --git a/tests/test_shells/outputs/bash.daemon.ok b/tests/test_shells/outputs/bash.daemon.ok index 89907c83..7b8cd6cb 100644 --- a/tests/test_shells/outputs/bash.daemon.ok +++ b/tests/test_shells/outputs/bash.daemon.ok @@ -7,7 +7,9 @@   HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false   HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s [1]+ Terminated bgscript.sh +  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default.segment_data.hostname.display false   HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false + USER   BRANCH  …  tmp  shell  3rd  set_theme_option default.segment_data.user.display false  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false   BRANCH  …  tmp  shell  3rd  echo '                                    abc @@ -27,16 +29,17 @@ def   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'   BRANCH  …  shell  3rd  «Unicode!»  (exit 42)|(exit 43) -  BRANCH  …  shell  3rd  «Unicode!»  42  43  set_theme_option default_leftonly.segments.above "$ABOVE_LEFT" -  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo +  BRANCH  …  shell  3rd  «Unicode!»  42  43  set_theme default + …  shell  3rd  «Unicode!»     BRANCH set_theme_option default.segments.above "$ABOVE_LEFT" + …  shell  3rd  «Unicode!»     BRANCH export DISPLAYED_ENV_VAR=foo  foo   -  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR -  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above "$ABOVE_FULL" + …  shell  3rd  «Unicode!»     BRANCH unset DISPLAYED_ENV_VAR + …  shell  3rd  «Unicode!»     BRANCH set_theme_option default.segments.above "$ABOVE_FULL"                                                                                                                                                                                                                                                                                                              -  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo + …  shell  3rd  «Unicode!»     BRANCH export DISPLAYED_ENV_VAR=foo                                                                                                                                                                                                                                                                                                        foo  -  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR + …  shell  3rd  «Unicode!»     BRANCH unset DISPLAYED_ENV_VAR                                                                                                                                                                                                                                                                                                              -  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above -  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC -  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse + …  shell  3rd  «Unicode!»     BRANCH set_theme_option default.segments.above + …  shell  3rd  «Unicode!»     BRANCH set_theme_option default.dividers.left.hard \$ABC + …  shell  3rd  «Unicode!» $ABC   BRANCH false diff --git a/tests/test_shells/outputs/bash.nodaemon.ok b/tests/test_shells/outputs/bash.nodaemon.ok index c65dcc14..458c1a18 100644 --- a/tests/test_shells/outputs/bash.nodaemon.ok +++ b/tests/test_shells/outputs/bash.nodaemon.ok @@ -7,7 +7,9 @@   HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false   HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s [1]+ Terminated bgscript.sh +  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default.segment_data.hostname.display false   HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false + USER   BRANCH  …  tmp  shell  3rd  set_theme_option default.segment_data.user.display false  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false   BRANCH  …  tmp  shell  3rd  echo '    abc @@ -27,16 +29,17 @@ def   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'   BRANCH  …  shell  3rd  «Unicode!»  (exit 42)|(exit 43) -  BRANCH  …  shell  3rd  «Unicode!»  42  43  set_theme_option default_leftonly.segments.above "$ABOVE_LEFT" -  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo +  BRANCH  …  shell  3rd  «Unicode!»  42  43  set_theme default + …  shell  3rd  «Unicode!»     BRANCH set_theme_option default.segments.above "$ABOVE_LEFT" + …  shell  3rd  «Unicode!»     BRANCH export DISPLAYED_ENV_VAR=foo  foo   -  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR -  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above "$ABOVE_FULL" + …  shell  3rd  «Unicode!»     BRANCH unset DISPLAYED_ENV_VAR + …  shell  3rd  «Unicode!»     BRANCH set_theme_option default.segments.above "$ABOVE_FULL"                                                                                                                                                                                                                                                                                                              -  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo + …  shell  3rd  «Unicode!»     BRANCH export DISPLAYED_ENV_VAR=foo                                                                                                                                                                                                                                                                                                        foo  -  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR + …  shell  3rd  «Unicode!»     BRANCH unset DISPLAYED_ENV_VAR                                                                                                                                                                                                                                                                                                              -  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above -  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC -  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse + …  shell  3rd  «Unicode!»     BRANCH set_theme_option default.segments.above + …  shell  3rd  «Unicode!»     BRANCH set_theme_option default.dividers.left.hard \$ABC + …  shell  3rd  «Unicode!» $ABC   BRANCH false