Merge pull request #944 from ZyX-I/feature/daemon

Add powerline-daemon

Closes #827
Fixes #616
Closes #170
This commit is contained in:
Nikolai Aleksandrovich Pavlov 2014-08-02 22:37:03 +04:00
commit 1d696d3d4c
35 changed files with 1016 additions and 138 deletions

2
.gitignore vendored
View File

@ -9,3 +9,5 @@ dist
build
message.fail
client/powerline

116
client/powerline.c Normal file
View File

@ -0,0 +1,116 @@
/* vim:fileencoding=utf-8:noet
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#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;
}

77
client/powerline.py Executable file
View File

@ -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))

17
client/powerline.sh Executable file
View File

@ -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

View File

@ -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
=================

View File

@ -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
----------------

View File

@ -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.

View File

@ -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
--------------

View File

@ -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*}" ||

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

1
scripts/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
powerline

View File

@ -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)

409
scripts/powerline-daemon Executable file
View File

@ -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()

View File

@ -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)

View File

@ -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,
)

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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')

View File

@ -1,3 +1,3 @@
width 1024
height 1
logfile "tests/shell/${SH}.full.log"
logfile "tests/shell/${SH}.${TEST_TYPE}.full.log"

View File

@ -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

Binary file not shown.

View File

@ -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