diff --git a/.gitignore b/.gitignore index 7468a1a5..dbae1ed8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ dist build message.fail + +client/powerline diff --git a/client/powerline.c b/client/powerline.c new file mode 100644 index 00000000..a60743a7 --- /dev/null +++ b/client/powerline.c @@ -0,0 +1,116 @@ +/* vim:fileencoding=utf-8:noet + */ + +#include +#include +#include +#include +#include +#include +#include + +#define handle_error(msg) \ + do { perror(msg); exit(EXIT_FAILURE); } while (0) + +#ifndef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(expression) \ + ( \ + ({ long int __result; \ + do __result = (long int) (expression); \ + while (__result == -1L && errno == EINTR); \ + __result; })) +#endif + +extern char **environ; + +void do_write(int sd, const char *raw, int len) { + int written = 0, n = -1; + + while (written < len) { + n = TEMP_FAILURE_RETRY(write(sd, raw+written, len-written)); + if (n == -1) { + close(sd); + handle_error("write() failed"); + } + written += n; + } +} + +int main(int argc, char *argv[]) { + int sd = -1, i; + struct sockaddr_un server; + char address[50] = {}; + const char eof[2] = "\0\0"; + char buf[4096] = {}; + char *newargv[200] = {}; + char *wd = NULL; + char **envp; + + if (argc < 2) { printf("Must provide at least one argument.\n"); return EXIT_FAILURE; } + +#ifdef __APPLE__ + snprintf(address, 50, "/tmp/powerline-ipc-%d", getuid()); +#else + snprintf(address, 50, "powerline-ipc-%d", getuid()); +#endif + + sd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sd == -1) handle_error("socket() failed"); + + memset(&server, 0, sizeof(struct sockaddr_un)); // Clear + server.sun_family = AF_UNIX; +#ifdef __APPLE__ + strncpy(server.sun_path, address, strlen(address)); +#else + strncpy(server.sun_path+1, address, strlen(address)); +#endif + +#ifdef __APPLE__ + if (connect(sd, (struct sockaddr *) &server, sizeof(server.sun_family) + strlen(address)) < 0) { +#else + if (connect(sd, (struct sockaddr *) &server, sizeof(server.sun_family) + strlen(address)+1) < 0) { +#endif + close(sd); + // We failed to connect to the daemon, execute powerline instead + argc = (argc < 199) ? argc : 199; + for (i=1; i < argc; i++) newargv[i] = argv[i]; + newargv[0] = "powerline-render"; + newargv[argc] = NULL; + execvp("powerline-render", newargv); + } + + for (i = 1; i < argc; i++) { + do_write(sd, argv[i], strlen(argv[i])); + do_write(sd, eof, 1); + } + + for(envp=environ; *envp; envp++) { + do_write(sd, "--env=", 6); + do_write(sd, *envp, strlen(*envp)); + do_write(sd, eof, 1); + } + + wd = getcwd(NULL, 0); + if (wd != NULL) { + do_write(sd, "--cwd=", 6); + do_write(sd, wd, strlen(wd)); + free(wd); wd = NULL; + } + + do_write(sd, eof, 2); + + i = -1; + while (i != 0) { + i = TEMP_FAILURE_RETRY(read(sd, buf, 4096)); + if (i == -1) { + close(sd); + handle_error("read() failed"); + } + if (i > 0) + write(STDOUT_FILENO, buf, i) || 0; + } + + close(sd); + + return 0; +} diff --git a/client/powerline.py b/client/powerline.py new file mode 100755 index 00000000..89e56f8e --- /dev/null +++ b/client/powerline.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import sys +import socket +import os +import errno + +if len(sys.argv) < 2: + print('Must provide at least one argument.', file=sys.stderr) + raise SystemExit(1) + +platform = sys.platform.lower() +use_filesystem = 'darwin' in platform +# use_filesystem = True +del platform + +address = ('/tmp/powerline-ipc-%d' if use_filesystem else '\0powerline-ipc-%d') % os.getuid() + +sock = socket.socket(family=socket.AF_UNIX) + + +def eintr_retry_call(func, *args, **kwargs): + while True: + try: + return func(*args, **kwargs) + except EnvironmentError as e: + if getattr(e, 'errno', None) == errno.EINTR: + continue + raise + +try: + eintr_retry_call(sock.connect, address) +except Exception: + # Run the powerline renderer + args = ['powerline-render'] + sys.argv[1:] + os.execvp('powerline-render', args) + +fenc = sys.getfilesystemencoding() or 'utf-8' +if fenc == 'ascii': + fenc = 'utf-8' + +args = [x.encode(fenc) if isinstance(x, type('')) else x for x in sys.argv[1:]] + +try: + cwd = os.getcwd() +except EnvironmentError: + pass +else: + if isinstance(cwd, type('')): + cwd = cwd.encode(fenc) + args.append(b'--cwd=' + cwd) + + +env = (k + b'=' + v for k, v in os.environ.items()) +env = (x if isinstance(x, bytes) else x.encode(fenc, 'replace') for x in env) +args.extend((b'--env=' + x for x in env)) + +EOF = b'\0\0' + +for a in args: + eintr_retry_call(sock.sendall, a + EOF[0]) + +eintr_retry_call(sock.sendall, EOF) + +received = [] +while True: + r = sock.recv(4096) + if not r: + break + received.append(r) + +sock.close() + +print (b''.join(received)) diff --git a/client/powerline.sh b/client/powerline.sh new file mode 100755 index 00000000..a56bc296 --- /dev/null +++ b/client/powerline.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +ADDRESS="powerline-ipc-${UID:-`id -u`}" + +# Warning: env -0 does not work in busybox. Consider switching to parsing +# `set` output in this case +( + for argv in "$@" ; do + printf '%s\0' "$argv" + done + env -0 | sed 's/\(\x00\)\([^\x00]\)\|^/\1--env=\2/g' + printf -- '--cwd=%s\0' "$PWD" +) | socat -lf/dev/null -t 10 - abstract-client:"$ADDRESS" + +if test $? -ne 0 ; then + powerline-render "$@" +fi diff --git a/docs/source/installation/linux.rst b/docs/source/installation/linux.rst index 74e6f74f..e8ff6d72 100644 --- a/docs/source/installation/linux.rst +++ b/docs/source/installation/linux.rst @@ -30,6 +30,11 @@ Plugin installation project is currently unavailable on the PyPI due to a naming conflict with an unrelated project. +.. note:: If you are powerline developer you should be aware that ``pip install + --editable`` does not currently fully work. If you + install powerline this way you will be missing ``powerline`` executable and + need to symlink it. It will be located in ``scripts/powerline``. + Font installation ================= diff --git a/docs/source/installation/osx.rst b/docs/source/installation/osx.rst index aa029f11..c991eac7 100644 --- a/docs/source/installation/osx.rst +++ b/docs/source/installation/osx.rst @@ -26,6 +26,11 @@ Python package project is currently unavailable on the PyPI due to a naming conflict with an unrelated project. +.. note:: If you are powerline developer you should be aware that ``pip install + --editable`` does not currently fully work. If you + install powerline this way you will be missing ``powerline`` executable and + need to symlink it. It will be located in ``scripts/powerline``. + Vim installation ---------------- diff --git a/docs/source/installation/troubleshooting-common.rst b/docs/source/installation/troubleshooting-common.rst index bb688d7e..55345394 100644 --- a/docs/source/installation/troubleshooting-common.rst +++ b/docs/source/installation/troubleshooting-common.rst @@ -124,12 +124,7 @@ I am suffering bad lags before displaying shell prompt To get rid of these lags there currently are two options: -* Take ``powerline-daemon`` script and one of ``powerline-client`` - implementations from ``feature/daemon`` branch (all ``powerline-client`` - implementations leave in ``client`` folder: you need to either compile - ``powerline.c`` or install ``socat`` and use ``powerline.sh`` - (``powerline.py`` is much slower)). Fortunately this branch will be merged in - the future. +* Run ``powerline-daemon``. Powerline does not automatically start it for you. * Compile and install ``libzpython`` module that lives in https://bitbucket.org/ZyX_I/zpython. This variant is zsh-specific. diff --git a/docs/source/overview.rst b/docs/source/overview.rst index 9ddc798a..f50940a6 100644 --- a/docs/source/overview.rst +++ b/docs/source/overview.rst @@ -149,6 +149,19 @@ hand: ``powerline`` is installed and run just like any other plugin using Shell prompts ------------- +.. note:: + It is advised that you run ``powerline-daemon`` before using any of the + below solutions. To do this add + + .. code-block:: bash + + powerline-daemon -q + + just before sourcing powerline bindings script or running + ``powerline-setup``. Use ``|| true`` or equivalent if you run your + configuration with ``set -e`` because ``powerline-daemon`` will exit with + ``1`` if daemon is already running. + Bash prompt ^^^^^^^^^^^ @@ -227,6 +240,14 @@ is the absolute path to your Powerline installation directory:: powerline support. You may specify location of this script via ``$POWERLINE_CONFIG_COMMAND`` environment variable. +.. note:: + It is advised that you run ``powerline-daemon`` before adding the above line + to tmux.conf. To do so add:: + + run-shell "powerline-daemon -q" + + to :file:`.tmux.conf`. + IPython prompt -------------- diff --git a/powerline/bindings/bash/powerline.sh b/powerline/bindings/bash/powerline.sh index dabacfa9..501b48aa 100644 --- a/powerline/bindings/bash/powerline.sh +++ b/powerline/bindings/bash/powerline.sh @@ -54,13 +54,12 @@ _powerline_set_prompt() { _powerline_setup_prompt() { VIRTUAL_ENV_DISABLE_PROMPT=1 if test -z "${POWERLINE_COMMAND}" ; then - if which powerline-client &>/dev/null ; then - export POWERLINE_COMMAND=powerline-client - elif which powerline &>/dev/null ; then - export POWERLINE_COMMAND=powerline + if which powerline-config &>/dev/null ; then + export POWERLINE_COMMAND="$(powerline-config shell command)" else # `$0` is set to `-bash` when using SSH so that won't work - export POWERLINE_COMMAND="$(dirname "$BASH_SOURCE")/../../../scripts/powerline" + local powerline_dir="$(dirname "$BASH_SOURCE")/../../.." + export POWERLINE_COMMAND="$($powerline_dir/scripts/powerline-config shell command)" fi fi test "x$PROMPT_COMMAND" != "x${PROMPT_COMMAND%_powerline_set_prompt*}" || diff --git a/powerline/bindings/config.py b/powerline/bindings/config.py index 3591903a..4dd79cc2 100644 --- a/powerline/bindings/config.py +++ b/powerline/bindings/config.py @@ -6,11 +6,12 @@ from collections import namedtuple import os import subprocess import re +import sys -from powerline.config import TMUX_CONFIG_DIRECTORY +from powerline.config import POWERLINE_ROOT, TMUX_CONFIG_DIRECTORY from powerline.lib.config import ConfigLoader from powerline import generate_config_finder, load_config, create_logger, PowerlineLogger, finish_common_config -from powerline.lib.shell import run_cmd +from powerline.lib.shell import run_cmd, which TmuxVersionInfo = namedtuple('TmuxVersionInfo', ('major', 'minor', 'suffix')) @@ -110,6 +111,11 @@ def source_tmux_files(pl, args): version = get_tmux_version(pl) for fname, priority in sorted(get_tmux_configs(version), key=(lambda v: v[1])): run_tmux_command('source', fname) + if not os.environ.get('POWERLINE_COMMAND'): + cmd = deduce_command() + if cmd: + run_tmux_command('set-environment', '-g', 'POWERLINE_COMMAND', deduce_command()) + run_tmux_command('refresh-client') def create_powerline_logger(args): @@ -119,3 +125,44 @@ def create_powerline_logger(args): common_config = finish_common_config(config['common']) logger = create_logger(common_config) return PowerlineLogger(use_daemon_threads=True, logger=logger, ext='config') + + +def check_command(cmd): + if which(cmd): + return cmd + + +def deduce_command(): + '''Deduce which command to use for ``powerline`` + + Candidates: + + * ``powerline``. Present only when installed system-wide. + * ``{powerline_root}/scripts/powerline``. Present after ``pip install -e`` + was run and C client was compiled (in this case ``pip`` does not install + binary file). + * ``{powerline_root}/client/powerline.sh``. Useful when ``sh``, ``sed`` and + ``socat`` are present, but ``pip`` or ``setup.py`` was not run. + * ``{powerline_root}/client/powerline.py``. Like above, but when one of + ``sh``, ``sed`` and ``socat`` was not present. + * ``powerline-render``. Should not really ever be used. + * ``{powerline_root}/scripts/powerline-render``. Same. + ''' + return ( + None + or check_command('powerline') + or check_command(os.path.join(POWERLINE_ROOT, 'scripts', 'powerline')) + or ((which('sh') and which('sed') and which('socat')) + and check_command(os.path.join(POWERLINE_ROOT, 'client', 'powerline.sh'))) + or check_command(os.path.join(POWERLINE_ROOT, 'client', 'powerline.py')) + or check_command('powerline-render') + or check_command(os.path.join(POWERLINE_ROOT, 'scripts', 'powerline-render')) + ) + + +def shell_command(pl, args): + cmd = deduce_command() + if cmd: + print(cmd) + else: + sys.exit(1) diff --git a/powerline/bindings/fish/powerline-setup.fish b/powerline/bindings/fish/powerline-setup.fish index 35ec5763..89fe9778 100644 --- a/powerline/bindings/fish/powerline-setup.fish +++ b/powerline/bindings/fish/powerline-setup.fish @@ -20,12 +20,11 @@ function powerline-setup if test -z "$POWERLINE_NO_FISH_PROMPT$POWERLINE_NO_SHELL_PROMPT" if test -z "$POWERLINE_COMMAND" - if which powerline-client >/dev/null - set -g -x POWERLINE_COMMAND powerline-client - else if which powerline >/dev/null - set -g -x POWERLINE_COMMAND powerline + if false ;and which powerline-config >/dev/null + set -g -x POWERLINE_COMMAND (powerline-config shell command) else - set -g -x POWERLINE_COMMAND (dirname (status -f))/../../../scripts/powerline + set -l powerline_dir (dirname (status -f))/../../.. + set -g -x POWERLINE_COMMAND (eval $powerline_dir/scripts/powerline-config shell command) end end function --on-variable POWERLINE_COMMAND _powerline_update diff --git a/powerline/bindings/shell/powerline.sh b/powerline/bindings/shell/powerline.sh index 6f45ffde..ae5c035f 100644 --- a/powerline/bindings/shell/powerline.sh +++ b/powerline/bindings/shell/powerline.sh @@ -1,3 +1,4 @@ +_POWERLINE_SOURCED="$_" _powerline_columns_fallback() { if which stty >/dev/null ; then # Ksh does not have “local” built-in @@ -97,13 +98,11 @@ _powerline_set_set_jobs() { _powerline_set_command() { if test -z "${POWERLINE_COMMAND}" ; then - if which powerline-client >/dev/null ; then - export POWERLINE_COMMAND=powerline-client - elif which powerline >/dev/null ; then - export POWERLINE_COMMAND=powerline + if which powerline-config &>/dev/null ; then + export POWERLINE_COMMAND="$(powerline-config shell command)" else - # `$0` is set to `-bash` when using SSH so that won't work - export POWERLINE_COMMAND="$(dirname "$BASH_SOURCE")/../../../scripts/powerline" + local powerline_dir="$(dirname "$POWERLINE_SOURCED")/../../.." + export POWERLINE_COMMAND="$($powerline_dir/scripts/powerline-config shell command)" fi fi } diff --git a/powerline/bindings/tcsh/powerline.tcsh b/powerline/bindings/tcsh/powerline.tcsh index 931b9ab0..69f34a2e 100644 --- a/powerline/bindings/tcsh/powerline.tcsh +++ b/powerline/bindings/tcsh/powerline.tcsh @@ -11,12 +11,10 @@ if ! ( $?POWERLINE_NO_TCSH_TMUX_SUPPORT || $?POWERLINE_NO_SHELL_TMUX_SUPPORT ) t endif if ! ( $?POWERLINE_NO_TCSH_PROMPT || $?POWERLINE_NO_SHELL_PROMPT ) then if ! $?POWERLINE_COMMAND then - if ( { which powerline-client > /dev/null } ) then - setenv POWERLINE_COMMAND powerline-client - else if ( { which powerline > /dev/null } ) then - setenv POWERLINE_COMMAND powerline + if ( { which powerline-config > /dev/null } ) then + setenv POWERLINE_COMMAND "`powerline-config shell command`" else - setenv POWERLINE_COMMAND $POWERLINE_SOURCED:h:h:h:h:q/scripts/powerline + setenv POWERLINE_COMMAND "`$POWERLINE_SOURCED[2]:h:h:h:h:q/scripts/powerline-config shell command`" endif endif diff --git a/powerline/bindings/tmux/powerline.conf b/powerline/bindings/tmux/powerline.conf index a55b4856..81928706 100644 --- a/powerline/bindings/tmux/powerline.conf +++ b/powerline/bindings/tmux/powerline.conf @@ -1,4 +1,3 @@ -if-shell 'test -z "$POWERLINE_COMMAND"' 'if-shell "which powerline-client" "set-environment -g POWERLINE_COMMAND powerline-client" "set-environment -g POWERLINE_COMMAND powerline"' if-shell 'test -z "$POWERLINE_CONFIG_COMMAND"' 'set-environment -g POWERLINE_CONFIG_COMMAND powerline-config' # Don't version-check for this core functionality -- anything too old to diff --git a/powerline/bindings/zsh/powerline.zsh b/powerline/bindings/zsh/powerline.zsh index 096d0ff4..5439167a 100644 --- a/powerline/bindings/zsh/powerline.zsh +++ b/powerline/bindings/zsh/powerline.zsh @@ -1,3 +1,5 @@ +_POWERLINE_SOURCED="$0:A" + _powerline_columns_fallback() { if which stty &>/dev/null ; then local cols="$(stty size 2>/dev/null)" @@ -124,12 +126,11 @@ _powerline_setup_prompt() { zpython 'del _powerline_setup' else if test -z "${POWERLINE_COMMAND}" ; then - if which powerline-client &>/dev/null ; then - export POWERLINE_COMMAND=powerline-client - elif which powerline &>/dev/null ; then - export POWERLINE_COMMAND=powerline + if which powerline-config &>/dev/null ; then + export POWERLINE_COMMAND="$(powerline-config shell command)" else - export POWERLINE_COMMAND="$0:A:h:h:h:h/scripts/powerline" + local powerline_dir="$POWERLINE_SOURCED:h:h:h:h" + export POWERLINE_COMMAND="$($powerline_dir/scripts/powerline-config shell command)" fi fi diff --git a/powerline/config.py b/powerline/config.py index 53e530f4..c895da5e 100644 --- a/powerline/config.py +++ b/powerline/config.py @@ -3,6 +3,7 @@ from __future__ import absolute_import, unicode_literals, print_function import os -BINDINGS_DIRECTORY = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'bindings') +POWERLINE_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +BINDINGS_DIRECTORY = os.path.join(POWERLINE_ROOT, 'powerline', 'bindings') TMUX_CONFIG_DIRECTORY = os.path.join(BINDINGS_DIRECTORY, 'tmux') DEFAULT_SYSTEM_CONFIG_DIR = None diff --git a/powerline/lib/shell.py b/powerline/lib/shell.py index ea777770..4418c3d9 100644 --- a/powerline/lib/shell.py +++ b/powerline/lib/shell.py @@ -6,6 +6,7 @@ from subprocess import Popen, PIPE from locale import getlocale, getdefaultlocale, LC_MESSAGES from functools import partial import sys +import os if sys.platform.startswith('win32'): @@ -60,3 +61,71 @@ def readlines(cmd, cwd): with p.stdout: for line in p.stdout: yield line[:-1].decode(encoding) + + +try: + from shutil import which +except ImportError: + # shutil.which was added in python-3.3. Here is what was added: + # Lib/shutil.py, commit 5abe28a9c8fe701ba19b1db5190863384e96c798 + def which(cmd, mode=os.F_OK | os.X_OK, path=None): # NOQA + """Given a command, mode, and a PATH string, return the path which + conforms to the given mode on the PATH, or None if there is no such + file. + + `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result + of os.environ.get("PATH"), or can be overridden with a custom search + path. + + """ + # Check that a given file can be accessed with the correct mode. + # Additionally check that `file` is not a directory, as on Windows + # directories pass the os.access check. + def _access_check(fn, mode): + return (os.path.exists(fn) and os.access(fn, mode) + and not os.path.isdir(fn)) + + # If we're given a path with a directory part, look it up directly rather + # than referring to PATH directories. This includes checking relative to the + # current directory, e.g. ./script + if os.path.dirname(cmd): + if _access_check(cmd, mode): + return cmd + return None + + if path is None: + path = os.environ.get("PATH", os.defpath) + if not path: + return None + path = path.split(os.pathsep) + + if sys.platform == "win32": + # The current directory takes precedence on Windows. + if not os.curdir in path: + path.insert(0, os.curdir) + + # PATHEXT is necessary to check on Windows. + pathext = os.environ.get("PATHEXT", "").split(os.pathsep) + # See if the given file matches any of the expected path extensions. + # This will allow us to short circuit when given "python.exe". + # If it does match, only test that one, otherwise we have to try + # others. + if any(cmd.lower().endswith(ext.lower()) for ext in pathext): + files = [cmd] + else: + files = [cmd + ext for ext in pathext] + else: + # On other platforms you don't have things like PATHEXT to tell you + # what file suffixes are executable, so just pass on cmd as-is. + files = [cmd] + + seen = set() + for dir in path: + normdir = os.path.normcase(dir) + if not normdir in seen: + seen.add(normdir) + for thefile in files: + name = os.path.join(dir, thefile) + if _access_check(name, mode): + return name + return None diff --git a/powerline/shell.py b/powerline/shell.py index bffa1df8..8cfb2de6 100644 --- a/powerline/shell.py +++ b/powerline/shell.py @@ -1,5 +1,7 @@ # vim:fileencoding=utf-8:noet +import os + from powerline import Powerline from powerline.lib import mergedicts, parsedotval @@ -73,3 +75,26 @@ def finish_args(args): args.theme_option = {} if args.renderer_arg: args.renderer_arg = mergeargs((parsedotval(v) for v in args.renderer_arg)) + + +def write_output(args, powerline, segment_info, write): + if args.renderer_arg: + segment_info.update(args.renderer_arg) + if args.side.startswith('above'): + for line in powerline.render_above_lines( + width=args.width, + segment_info=segment_info, + mode=os.environ.get('_POWERLINE_MODE'), + ): + write(line) + write('\n') + args.side = args.side[len('above'):] + + if args.side: + rendered = powerline.render( + width=args.width, + side=args.side, + segment_info=segment_info, + mode=os.environ.get('_POWERLINE_MODE'), + ) + write(rendered) diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 00000000..f2ffc12a --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1 @@ +powerline diff --git a/scripts/powerline-config b/scripts/powerline-config index bcece7e6..2e636437 100755 --- a/scripts/powerline-config +++ b/scripts/powerline-config @@ -18,6 +18,11 @@ TMUX_ACTIONS = { } +SHELL_ACTIONS = { + 'command': config.shell_command, +} + + if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) subparsers = parser.add_subparsers() @@ -30,6 +35,14 @@ if __name__ == '__main__': help='If action is "source" then version-specific tmux configuration files are sourced.' ) + shell_parser = subparsers.add_parser('shell', help='Shell-specific commands') + shell_parser.add_argument( + 'function', + choices=tuple(SHELL_ACTIONS.values()), + type=(lambda v: SHELL_ACTIONS.get(v)), + help='If action is "command" then preferred powerline command is output', + ) + args = parser.parse_args() pl = config.create_powerline_logger(args) diff --git a/scripts/powerline-daemon b/scripts/powerline-daemon new file mode 100755 index 00000000..bc054680 --- /dev/null +++ b/scripts/powerline-daemon @@ -0,0 +1,409 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8:noet +from __future__ import (unicode_literals, division, absolute_import, print_function) + +import socket +import os +import errno +import sys +from argparse import ArgumentParser +from select import select +from signal import signal, SIGTERM +from time import sleep +from functools import partial +from locale import getpreferredencoding +from io import StringIO + +from powerline.shell import get_argparser, finish_args, ShellPowerline, write_output +from powerline.lib.monotonic import monotonic + +is_daemon = False +platform = sys.platform.lower() +use_filesystem = 'darwin' in platform +# use_filesystem = True +del platform + +if use_filesystem: + address = '/tmp/powerline-ipc-%d' + pidfile = address + '.pid' +else: + # Use the abstract namespace for sockets rather than the filesystem + # (Available only in linux) + address = '\0powerline-ipc-%d' +address = address % os.getuid() + + +class NonInteractiveArgParser(ArgumentParser): + def print_usage(self, file=None): + raise Exception(self.format_usage()) + + def print_help(self, file=None): + raise Exception(self.format_help()) + + def exit(self, status=0, message=None): + pass + + def error(self, message): + raise Exception(self.format_usage()) + + +parser = get_argparser(parser=NonInteractiveArgParser, description='powerline daemon') +parser.add_argument('--cwd', metavar='PATH') +parser.add_argument('--env', action='append') + +EOF = b'EOF\0\0' + +powerlines = {} +logger = None +config_loader = None +home = os.path.expanduser('~') + + +class PowerlineDaemon(ShellPowerline): + def get_log_handler(self): + if not is_daemon: + import logging + return logging.StreamHandler() + return super(PowerlineDaemon, self).get_log_handler() + + +def render(args): + global logger + global config_loader + environ = dict(((k, v) for k, v in (x.partition('=')[0::2] for x in args.env))) + cwd = environ.get('PWD', args.cwd or '/') + segment_info = { + 'getcwd': lambda: cwd, + 'home': environ.get('HOME', home), + 'environ': environ, + 'args': args, + } + key = ( + args.ext[0], + args.renderer_module, + tuple(args.config) if args.config else None, + tuple(args.theme_option) if args.theme_option else None, + tuple(args.renderer_arg) if args.renderer_arg else None, + ) + finish_args(args) + powerline = None + try: + powerline = powerlines[key] + except KeyError: + try: + powerline = powerlines[key] = PowerlineDaemon( + args, + logger=logger, + config_loader=config_loader, + run_once=False, + ) + if logger is None: + logger = powerline.logger + if config_loader is None: + config_loader = powerline.config_loader + except SystemExit: + # Somebody thought raising system exit was a good idea, + return '' + except Exception as e: + if powerline: + powerline.pl.exception('Failed to render {0}: {1}', str(key), str(e)) + else: + return 'Failed to render {0}: {1}'.format(str(key), str(e)) + s = StringIO() + write_output(args, powerline, segment_info, s.write) + s.seek(0) + return s.read() + + +def eintr_retry_call(func, *args, **kwargs): + while True: + try: + return func(*args, **kwargs) + except EnvironmentError as e: + if getattr(e, 'errno', None) == errno.EINTR: + continue + raise + + +def do_read(conn, timeout=2.0): + ''' Read data from the client. If the client fails to send data within + timeout seconds, abort. ''' + read = [] + end_time = monotonic() + timeout + while not read or not read[-1].endswith(b'\0\0'): + r, w, e = select((conn,), (), (conn,), timeout) + if e: + return + if monotonic() > end_time: + return + if not r: + continue + x = eintr_retry_call(conn.recv, 4096) + if x: + read.append(x) + else: + break + return b''.join(read) + + +def do_write(conn, result): + try: + eintr_retry_call(conn.sendall, result + b'\0') + except Exception: + pass + + +encoding = getpreferredencoding() or 'UTF-8' +if encoding.lower() == 'ascii': + encoding = 'UTF-8' + + +def safe_bytes(o, encoding=encoding): + '''Return bytes instance without ever throwing an exception.''' + try: + try: + # We are assuming that o is a unicode object + return o.encode(encoding, 'replace') + except Exception: + # Object may have defined __bytes__ (python 3) or __str__ method + # (python 2) + # This also catches problem with non_ascii_bytes.encode('utf-8') + # that first tries to decode to UTF-8 using ascii codec (and fails + # in this case) and then encode to given encoding: errors= argument + # is not used in the first stage. + return bytes(o) + except Exception as e: + return safe_bytes(str(e), encoding) + + +def do_render(req): + try: + args = [x.decode(encoding) for x in req.split(b'\0') if x] + args = parser.parse_args(args) + return safe_bytes(render(args)) + except Exception as e: + return safe_bytes(str(e)) + + +def do_one(sock, read_sockets, write_sockets, result_map): + r, w, e = select( + tuple(read_sockets) + (sock,), + tuple(write_sockets), + tuple(read_sockets) + tuple(write_sockets) + (sock,), + 60.0 + ) + + if sock in e: + # We cannot accept any more connections, so we exit + raise SystemExit(1) + + for s in e: + # Discard all broken connections to clients + s.close() + read_sockets.discard(s) + write_sockets.discard(s) + + for s in r: + if s == sock: + # A client wants to connect + conn, _ = eintr_retry_call(sock.accept) + read_sockets.add(conn) + else: + # A client has sent some data + read_sockets.discard(s) + req = do_read(s) + if req == EOF: + raise SystemExit(0) + elif req: + ans = do_render(req) + result_map[s] = ans + write_sockets.add(s) + else: + s.close() + + for s in w: + # A client is ready to receive the result + write_sockets.discard(s) + result = result_map.pop(s) + try: + do_write(s, result) + finally: + s.close() + + +def main_loop(sock): + sock.listen(1) + sock.setblocking(0) + + read_sockets, write_sockets = set(), set() + result_map = {} + try: + while True: + do_one(sock, read_sockets, write_sockets, result_map) + except KeyboardInterrupt: + raise SystemExit(0) + return 0 + + +def daemonize(stdin=os.devnull, stdout=os.devnull, stderr=os.devnull): + try: + pid = os.fork() + if pid > 0: + # exit first parent + sys.exit(0) + except OSError as e: + print ("fork #1 failed: %d (%s)" % (e.errno, e.strerror), file=sys.stderr) + sys.exit(1) + + # decouple from parent environment + os.chdir("/") + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent + sys.exit(0) + except OSError as e: + print ("fork #2 failed: %d (%s)" % (e.errno, e.strerror), file=sys.stderr) + sys.exit(1) + + # Redirect standard file descriptors. + si = file(stdin, 'r') + so = file(stdout, 'a+') + se = file(stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + global is_daemon + is_daemon = True + + +def check_existing(): + if use_filesystem: + # We cannot bind if the socket file already exists so remove it, we + # already have a lock on pidfile, so this should be safe. + try: + os.unlink(address) + except EnvironmentError: + pass + + sock = socket.socket(family=socket.AF_UNIX) + try: + sock.bind(address) + except socket.error as e: + if getattr(e, 'errno', None) == errno.EADDRINUSE: + return None + raise + return sock + + +def kill_daemon(): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + try: + try: + eintr_retry_call(sock.connect, address) + except socket.error: + return False + else: + eintr_retry_call(sock.sendall, EOF) + finally: + sock.close() + return True + + +def cleanup_lockfile(fd, *args): + try: + # Remove the directory entry for the lock file + os.unlink(pidfile) + # Close the file descriptor + os.close(fd) + except EnvironmentError: + pass + if args: + # Called in signal handler + raise SystemExit(1) + + +def lockpidfile(): + import fcntl + import atexit + import stat + fd = os.open(pidfile, os.O_WRONLY | os.O_CREAT, + stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) + try: + fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + except EnvironmentError: + os.close(fd) + return None + os.lseek(fd, 0, os.SEEK_SET) + os.ftruncate(fd, 0) + os.write(fd, ('%d' % os.getpid()).encode('ascii')) + os.fsync(fd) + cleanup = partial(cleanup_lockfile, fd) + signal(SIGTERM, cleanup) + atexit.register(cleanup) + return fd + + +def main(): + p = ArgumentParser(description= + 'Daemon to improve the performance of powerline') + p.add_argument('--quiet', '-q', action='store_true', help='Without other options: do not complain about already running powerline-daemon instance. Will still exit with 1. With `--kill\' and `--replace\': do not show any messages. With `--foreground\': ignored. Does not silence exceptions in any case.') + a = p.add_mutually_exclusive_group().add_argument + a('--kill', '-k', action='store_true', help='Kill an already running instance') + a('--foreground', '-f', action='store_true', help='Run in the foreground (dont daemonize)') + a('--replace', '-r', action='store_true', help='Replace an already running instance') + args = p.parse_args() + + if args.kill: + if kill_daemon(): + if not args.quiet: + print ('Kill command sent to daemon, if it does not die in a couple of seconds use kill to kill it') + raise SystemExit(0) + else: + if not args.quiet: + print ('No running daemon found') + raise SystemExit(1) + + if args.replace: + while kill_daemon(): + if not args.quiet: + print ('Kill command sent to daemon, waiting for daemon to exit, press Ctrl-C to terminate wait and exit') + sleep(2) + + if use_filesystem and not args.foreground: + # We must daemonize before creating the locked pidfile, unfortunately, + # this means further print statements are discarded + daemonize() + + if use_filesystem: + # Create a locked pid file containing the daemon's PID + if lockpidfile() is None: + if not args.quiet: + print ('The daemon is already running. Use %s -k to kill it.' % os.path.basename(sys.argv[0]), + file=sys.stderr) + raise SystemExit(1) + + # Bind to address or bail if we cannot bind + sock = check_existing() + if sock is None: + if not args.quiet: + print ('The daemon is already running. Use %s -k to kill it.' % os.path.basename(sys.argv[0]), + file=sys.stderr) + raise SystemExit(1) + + if args.foreground: + return main_loop(sock) + + if not use_filesystem: + # We daemonize on linux + daemonize() + + main_loop(sock) + + +if __name__ == '__main__': + main() diff --git a/scripts/powerline b/scripts/powerline-render similarity index 57% rename from scripts/powerline rename to scripts/powerline-render index 8b125217..bdedb1b7 100755 --- a/scripts/powerline +++ b/scripts/powerline-render @@ -5,10 +5,11 @@ import sys import os try: - from powerline.shell import ShellPowerline, get_argparser, finish_args + from powerline.shell import ShellPowerline, get_argparser, finish_args, write_output except ImportError: sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(os.path.realpath(__file__))))) - from powerline.shell import ShellPowerline, get_argparser, finish_args # NOQA + from powerline.shell import ShellPowerline, get_argparser, finish_args, write_output # NOQA + def write(output): try: @@ -16,28 +17,10 @@ def write(output): except UnicodeEncodeError: sys.stdout.write(output.encode('utf-8')) + if __name__ == '__main__': args = get_argparser(description=__doc__).parse_args() finish_args(args) powerline = ShellPowerline(args, run_once=True) segment_info = {'args': args, 'environ': os.environ} - if args.renderer_arg: - segment_info.update(args.renderer_arg) - if args.side.startswith('above'): - for line in powerline.render_above_lines( - width=args.width, - segment_info=segment_info, - mode=os.environ.get('_POWERLINE_MODE'), - ): - write(line) - sys.stdout.write('\n') - args.side = args.side[len('above'):] - - if args.side: - rendered = powerline.render( - width=args.width, - side=args.side, - segment_info=segment_info, - mode=os.environ.get('_POWERLINE_MODE'), - ) - write(rendered) + write_output(args, powerline, segment_info, write) diff --git a/setup.py b/setup.py index e341ceec..16561d85 100755 --- a/setup.py +++ b/setup.py @@ -3,16 +3,52 @@ from __future__ import unicode_literals import os import sys +import subprocess +import logging from setuptools import setup, find_packages -here = os.path.abspath(os.path.dirname(__file__)) +CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) try: - README = open(os.path.join(here, 'README.rst'), 'rb').read().decode('utf-8') + README = open(os.path.join(CURRENT_DIR, 'README.rst'), 'rb').read().decode('utf-8') except IOError: README = '' -old_python = sys.version_info < (2, 7) +OLD_PYTHON = sys.version_info < (2, 7) + + +def compile_client(): + '''Compile the C powerline-client script.''' + + if hasattr(sys, 'getwindowsversion'): + raise NotImplementedError() + else: + from distutils.ccompiler import new_compiler + compiler = new_compiler().compiler + subprocess.check_call(compiler + ['-O3', 'client/powerline.c', '-o', 'scripts/powerline']) + +try: + compile_client() +except Exception as e: + print('Compiling C version of powerline-client failed') + logging.exception(e) + # FIXME Catch more specific exceptions + import shutil + if hasattr(shutil, 'which'): + which = shutil.which + else: + sys.path.append(CURRENT_DIR) + from powerline.lib import which + 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 setup( name='Powerline', @@ -23,11 +59,31 @@ setup( author='Kim Silkebaekken', author_email='kim.silkebaekken+vim@gmail.com', url='https://github.com/Lokaltog/powerline', + # XXX Python 3 doesn't allow compiled C files to be included in the scripts + # list below. This is because Python 3 distutils tries to decode the file to + # ASCII, and fails when powerline-client is a binary. + # + # XXX Python 2 fucks up script contents*. Not using it to install scripts + # any longer. + # * Consider the following input: + # % alias hex1=$'hexdump -e \'"" 1/1 "%02X\n"\'' + # % diff <(hex1 ./scripts/powerline) <(hex1 ~/.local/bin/powerline) + # This will show output like + # 375c375 + # < 0D + # --- + # > 0A + # (repeated, with diff segment header numbers growing up). + # + # FIXME Current solution does not work with `pip install -e`. Still better + # then solution that is not working at all. scripts=[ - 'scripts/powerline', 'scripts/powerline-lint', + 'scripts/powerline-daemon', + 'scripts/powerline-render', 'scripts/powerline-config', - ], + ] + (['scripts/powerline'] if can_use_scripts else []), + data_files=(None if can_use_scripts else (('bin', ['scripts/powerline']),)), keywords='', packages=find_packages(exclude=('tests', 'tests.*')), include_package_data=True, @@ -38,5 +94,5 @@ setup( 'Sphinx', ], }, - test_suite='tests' if not old_python else None, + test_suite='tests' if not OLD_PYTHON else None, ) diff --git a/tests/test_shells/input.bash b/tests/test_shells/input.bash index 748e78fa..ff7ad797 100644 --- a/tests/test_shells/input.bash +++ b/tests/test_shells/input.bash @@ -1,8 +1,9 @@ -POWERLINE_COMMAND="$PWD/scripts/powerline -p $PWD/powerline/config_files" +export VIRTUAL_ENV= +source powerline/bindings/bash/powerline.sh +POWERLINE_COMMAND="$POWERLINE_COMMAND -p $PWD/powerline/config_files" POWERLINE_COMMAND="$POWERLINE_COMMAND -t default_leftonly.segment_data.hostname.args.only_if_ssh=false" POWERLINE_COMMAND="$POWERLINE_COMMAND -c ext.shell.theme=default_leftonly" -export VIRTUAL_ENV= -source powerline/bindings/bash/powerline.sh ; cd tests/shell/3rd +cd tests/shell/3rd cd .git cd .. VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" diff --git a/tests/test_shells/input.bb b/tests/test_shells/input.bb index d9ae1342..2d4fbc42 100644 --- a/tests/test_shells/input.bb +++ b/tests/test_shells/input.bb @@ -1,8 +1,9 @@ -POWERLINE_COMMAND="$PWD/scripts/powerline -p $PWD/powerline/config_files" +. powerline/bindings/shell/powerline.sh +POWERLINE_COMMAND="$POWERLINE_COMMAND -p $PWD/powerline/config_files" POWERLINE_COMMAND="$POWERLINE_COMMAND -t default_leftonly.segment_data.hostname.args.only_if_ssh=false" POWERLINE_COMMAND="$POWERLINE_COMMAND -c ext.shell.theme=default_leftonly" export VIRTUAL_ENV= -. powerline/bindings/shell/powerline.sh ; cd tests/shell/3rd +cd tests/shell/3rd cd .git cd .. VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" diff --git a/tests/test_shells/input.dash b/tests/test_shells/input.dash index d9ae1342..2d4fbc42 100644 --- a/tests/test_shells/input.dash +++ b/tests/test_shells/input.dash @@ -1,8 +1,9 @@ -POWERLINE_COMMAND="$PWD/scripts/powerline -p $PWD/powerline/config_files" +. powerline/bindings/shell/powerline.sh +POWERLINE_COMMAND="$POWERLINE_COMMAND -p $PWD/powerline/config_files" POWERLINE_COMMAND="$POWERLINE_COMMAND -t default_leftonly.segment_data.hostname.args.only_if_ssh=false" POWERLINE_COMMAND="$POWERLINE_COMMAND -c ext.shell.theme=default_leftonly" export VIRTUAL_ENV= -. powerline/bindings/shell/powerline.sh ; cd tests/shell/3rd +cd tests/shell/3rd cd .git cd .. VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" diff --git a/tests/test_shells/input.fish b/tests/test_shells/input.fish index 3e69ad5e..f8da144e 100644 --- a/tests/test_shells/input.fish +++ b/tests/test_shells/input.fish @@ -1,9 +1,10 @@ -set POWERLINE_COMMAND "$PWD/scripts/powerline -p $PWD/powerline/config_files" +set fish_function_path $fish_function_path "$PWD/powerline/bindings/fish" +powerline-setup +set POWERLINE_COMMAND "$POWERLINE_COMMAND -p $PWD/powerline/config_files" set POWERLINE_COMMAND "$POWERLINE_COMMAND -t default_leftonly.segment_data.hostname.args.only_if_ssh=false" set POWERLINE_COMMAND "$POWERLINE_COMMAND -c ext.shell.theme=default_leftonly" setenv VIRTUAL_ENV -set fish_function_path $fish_function_path "$PWD/powerline/bindings/fish" -powerline-setup ; cd tests/shell/3rd +cd tests/shell/3rd cd .git cd .. setenv VIRTUAL_ENV "$HOME/.virtenvs/some-virtual-environment" diff --git a/tests/test_shells/input.mksh b/tests/test_shells/input.mksh index a4a5928a..a2f0a7dd 100644 --- a/tests/test_shells/input.mksh +++ b/tests/test_shells/input.mksh @@ -1,8 +1,9 @@ -POWERLINE_COMMAND="$PWD/scripts/powerline -p $PWD/powerline/config_files" +. powerline/bindings/shell/powerline.sh +POWERLINE_COMMAND="$POWERLINE_COMMAND -p $PWD/powerline/config_files" POWERLINE_COMMAND="$POWERLINE_COMMAND -t default_leftonly.segment_data.hostname.args.only_if_ssh=false" POWERLINE_COMMAND="$POWERLINE_COMMAND -c ext.shell.theme=default_leftonly" export VIRTUAL_ENV= -. powerline/bindings/shell/powerline.sh ; cd tests/shell/3rd +cd tests/shell/3rd cd .git cd .. VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" diff --git a/tests/test_shells/input.tcsh b/tests/test_shells/input.tcsh index 4d48a465..0bc6b78a 100644 --- a/tests/test_shells/input.tcsh +++ b/tests/test_shells/input.tcsh @@ -1,6 +1,7 @@ -setenv POWERLINE_COMMAND $PWD:q/scripts/powerline" -p "$PWD:q/powerline/config_files" -t default_leftonly.segment_data.hostname.args.only_if_ssh=false -c ext.shell.theme=default_leftonly" +source powerline/bindings/tcsh/powerline.tcsh +setenv POWERLINE_COMMAND "$POWERLINE_COMMAND -p "$PWD:q/powerline/config_files" -t default_leftonly.segment_data.hostname.args.only_if_ssh=false -c ext.shell.theme=default_leftonly" unsetenv VIRTUAL_ENV -source powerline/bindings/tcsh/powerline.tcsh ; cd tests/shell/3rd +cd tests/shell/3rd cd .git cd .. setenv VIRTUAL_ENV $HOME:q"/.virtenvs/some-virtual-environment" diff --git a/tests/test_shells/input.zsh b/tests/test_shells/input.zsh index b40c3283..51095f5f 100644 --- a/tests/test_shells/input.zsh +++ b/tests/test_shells/input.zsh @@ -1,26 +1,21 @@ -unsetopt promptsp transientrprompt -POWERLINE_COMMAND=( $PWD/scripts/powerline -p $PWD/powerline/config_files ) -POWERLINE_COMMAND=( $POWERLINE_COMMAND -t default_leftonly.segment_data.hostname.args.only_if_ssh=false ) -POWERLINE_COMMAND=( $POWERLINE_COMMAND -c ext.shell.theme=default_leftonly ) +unsetopt promptsp notransientrprompt setopt interactivecomments # POWERLINE_CONFIG_PATH=$PWD/powerline/config_files # POWERLINE_THEME_CONFIG=( default_leftonly.segment_data.hostname.args.only_if_ssh=false ) # POWERLINE_CONFIG=( ext.shell.theme=default_leftonly ) POWERLINE_NO_ZSH_ZPYTHON=1 # TODO: make tests work with zsh/zpython +source powerline/bindings/zsh/powerline.zsh +POWERLINE_COMMAND=( $POWERLINE_COMMAND -p $PWD/powerline/config_files ) +POWERLINE_COMMAND=( $POWERLINE_COMMAND -t default_leftonly.segment_data.hostname.args.only_if_ssh=false ) +POWERLINE_COMMAND=( $POWERLINE_COMMAND -c ext.shell.theme=default_leftonly ) export VIRTUAL_ENV= -source powerline/bindings/zsh/powerline.zsh ; cd tests/shell/3rd +cd tests/shell/3rd cd .git cd .. VIRTUAL_ENV="$HOME/.virtenvs/some-virtual-environment" VIRTUAL_ENV= bash -c 'echo $$>pid ; while true ; do sleep 0.1s ; done' & false -select abc in def ghi jkl -do - echo $abc - break -done -1 kill `cat pid` ; sleep 1s cd "$DIR1" cd ../"$DIR2" @@ -36,5 +31,13 @@ POWERLINE_COMMAND=( $POWERLINE_COMMAND[1,4] ${${POWERLINE_COMMAND[5]}/_leftonly} echo abc false +POWERLINE_COMMAND=( $POWERLINE_COMMAND -t default.segment_data.hostname.display=false ) +POWERLINE_COMMAND=( $POWERLINE_COMMAND -t default.segment_data.user.display=false ) +select abc in def ghi jkl +do + echo $abc + break +done +1 true is the last line exit diff --git a/tests/test_shells/postproc.py b/tests/test_shells/postproc.py index ce68c9d5..f34fa81c 100755 --- a/tests/test_shells/postproc.py +++ b/tests/test_shells/postproc.py @@ -8,9 +8,10 @@ import sys import codecs -shell = sys.argv[1] -fname = os.path.join('tests', 'shell', shell + '.full.log') -new_fname = os.path.join('tests', 'shell', shell + '.log') +test_type = sys.argv[1] +shell = sys.argv[2] +fname = os.path.join('tests', 'shell', shell + '.' + test_type + '.full.log') +new_fname = os.path.join('tests', 'shell', shell + '.' + test_type + '.log') pid_fname = os.path.join('tests', 'shell', '3rd', 'pid') diff --git a/tests/test_shells/screenrc b/tests/test_shells/screenrc index d998652a..7c9674e5 100644 --- a/tests/test_shells/screenrc +++ b/tests/test_shells/screenrc @@ -1,3 +1,3 @@ width 1024 height 1 -logfile "tests/shell/${SH}.full.log" +logfile "tests/shell/${SH}.${TEST_TYPE}.full.log" diff --git a/tests/test_shells/test.sh b/tests/test_shells/test.sh index b0f30fed..f224e22f 100755 --- a/tests/test_shells/test.sh +++ b/tests/test_shells/test.sh @@ -3,17 +3,23 @@ FAILED=0 ONLY_SHELL="$1" check_screen_log() { - SH="$1" - if test -e tests/test_shells/${SH}.ok ; then - diff -u tests/test_shells/${SH}.ok tests/shell/${SH}.log + TEST_TYPE="$1" + SH="$2" + if test -e tests/test_shells/${SH}.${TEST_TYPE}.ok ; then + diff -a -u tests/test_shells/${SH}.${TEST_TYPE}.ok tests/shell/${SH}.${TEST_TYPE}.log + return $? + elif test -e tests/test_shells/${SH}.ok ; then + diff -a -u tests/test_shells/${SH}.ok tests/shell/${SH}.${TEST_TYPE}.log return $? else - cat tests/shell/${SH}.log + cat tests/shell/${SH}.${TEST_TYPE}.log return 1 fi } run_test() { + TEST_TYPE="$1" + shift SH="$1" SESNAME="powerline-shell-test-${SH}-$$" ARGS=( "$@" ) @@ -32,6 +38,7 @@ run_test() { fi fi + export TEST_TYPE export SH screen -L -c tests/test_shells/screenrc -d -m -S "$SESNAME" \ @@ -59,21 +66,21 @@ run_test() { while screen -S "$SESNAME" -X blankerprg "" > /dev/null ; do sleep 0.1s done - ./tests/test_shells/postproc.py ${SH} - if ! check_screen_log ${SH} ; then + ./tests/test_shells/postproc.py ${TEST_TYPE} ${SH} + if ! check_screen_log ${TEST_TYPE} ${SH} ; then echo '____________________________________________________________' # Repeat the diff to make it better viewable in travis output echo "Diff (cat -v):" echo '============================================================' - check_screen_log ${SH} | cat -v + check_screen_log ${TEST_TYPE} ${SH} | cat -v echo '____________________________________________________________' echo "Failed ${SH}. Full output:" echo '============================================================' - cat tests/shell/${SH}.full.log + cat tests/shell/${SH}.${TEST_TYPE}.full.log echo '____________________________________________________________' echo "Full output (cat -v):" echo '============================================================' - cat -v tests/shell/${SH}.full.log + cat -v tests/shell/${SH}.${TEST_TYPE}.full.log echo '____________________________________________________________' case ${SH} in *ksh) @@ -112,39 +119,61 @@ mkdir tests/shell/3rd/'(echo)' mkdir tests/shell/3rd/'$(echo)' mkdir tests/shell/3rd/'`echo`' -if ! run_test bash --norc --noprofile -i ; then - FAILED=1 -fi - -if ! run_test zsh -f -i ; then - FAILED=1 -fi - mkdir tests/shell/fish_home export XDG_CONFIG_HOME="$PWD/tests/shell/fish_home" -if ! run_test fish -i ; then - FAILED=1 -fi - -if ! run_test tcsh -f -i ; then - FAILED=1 -fi - -if ! run_test bb -i ; then - FAILED=1 -fi unset ENV -if ! run_test mksh -i ; then - FAILED=1 -fi +powerline-daemon -k || true +sleep 1s -if ! run_test dash -i ; then - # dash tests are not stable, see #931 - # FAILED=1 - true -fi +scripts/powerline-config shell command + +for TEST_TYPE in "daemon" "nodaemon" ; do + if test $TEST_TYPE == daemon ; then + sh -c 'echo $$ > tests/shell/daemon_pid; ./scripts/powerline-daemon -f &>tests/shell/daemon_log' & + fi + if ! run_test $TEST_TYPE bash --norc --noprofile -i ; then + FAILED=1 + fi + + if ! run_test $TEST_TYPE zsh -f -i ; then + FAILED=1 + fi + + if ! run_test $TEST_TYPE fish -i ; then + FAILED=1 + fi + + if ! run_test $TEST_TYPE tcsh -f -i ; then + FAILED=1 + fi + + if ! run_test $TEST_TYPE bb -i ; then + FAILED=1 + fi + + if ! run_test $TEST_TYPE mksh -i ; then + FAILED=1 + fi + + if ! run_test $TEST_TYPE dash -i ; then + # dash tests are not stable, see #931 + # FAILED=1 + true + fi + if test $TEST_TYPE == daemon ; then + ./scripts/powerline-daemon -k + wait $(cat tests/shell/daemon_pid) + if ! test -z "$(cat tests/shell/daemon_log)" ; then + echo '____________________________________________________________' + echo "Daemon log:" + echo '============================================================' + cat tests/shell/daemon_log + FAILED=1 + fi + fi +done test "x$ONLY_SHELL" = "x" && rm -r tests/shell exit $FAILED diff --git a/tests/test_shells/zsh.daemon.ok b/tests/test_shells/zsh.daemon.ok new file mode 100644 index 00000000..7469e23b Binary files /dev/null and b/tests/test_shells/zsh.daemon.ok differ diff --git a/tests/test_shells/zsh.ok b/tests/test_shells/zsh.nodaemon.ok similarity index 90% rename from tests/test_shells/zsh.ok rename to tests/test_shells/zsh.nodaemon.ok index c681e75e..daa84f40 100644 --- a/tests/test_shells/zsh.ok +++ b/tests/test_shells/zsh.nodaemon.ok @@ -6,15 +6,7 @@   HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  bash -c 'echo $$>pid ; while true ; do sleep 0.1s ; done' & [1] PID   HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  1  false -  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  1  1  select abc in def ghi jkl - select  do - select   echo $abc - select   break - select  done -1) def 2) ghi 3) jkl - Select variant  1 -def -  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  1  kill `cat pid` ; sleep 1s +  HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  1  1  kill `cat pid` ; sleep 1s [1] + terminated bash -c 'echo $$>pid ; while true ; do sleep 0.1s ; done'   HOSTNAME  USER   BRANCH  ⋯  tests  shell  3rd  cd "$DIR1"   HOSTNAME  USER   BRANCH  ⋯  shell  3rd  ^[[32m  cd ../"$DIR2" @@ -31,3 +23,13 @@ def  INSERT   HOSTNAME  USER  ⋯  tests  shell  3rd  echo abc abc  INSERT   HOSTNAME  USER  ⋯  tests  shell  3rd  false + INSERT   HOSTNAME  USER  ⋯  tests  shell  3rd  POWERLINE_COMMAND=( $POWERLINE_COMMAND -t default.segment_data.hostname.display=false ) + INSERT  USER  ⋯  tests  shell  3rd  POWERLINE_COMMAND=( $POWERLINE_COMMAND -t default.segment_data.user.display=false ) + INSERT  ⋯  tests  shell  3rd  select abc in def ghi jkl + select  do + select   echo $abc + select   break + select  done +1) def 2) ghi 3) jkl + Select variant  1 +def