Beispiel #1
0
def command_debug_info():
    """
    Print debug info to submit with Issues.
    """
    def prepend_tab(strings):
        """
        Prepend tab to strings in list.
        """
        return list(map(lambda x: '\t%s' % x, strings))

    def output_break():
        """
        Generate output break.
        """
        return '-' * 25

    def format_tmux_resp(std_resp):
        """
        Format tmux command response for tmuxp stdout.
        """
        return '\n'.join([
            '\n'.join(prepend_tab(std_resp.stdout)),
            click.style('\n'.join(prepend_tab(std_resp.stderr)), fg='red'),
        ])

    output = [
        output_break(),
        'environment:\n%s' % '\n'.join(
            prepend_tab([
                'dist: %s' % platform.platform(),
                'arch: %s' % platform.machine(),
                'uname: %s' % '; '.join(platform.uname()[:3]),
                'version: %s' % platform.version(),
            ])),
        output_break(),
        'python version: %s' % ' '.join(sys.version.split('\n')),
        'system PATH: %s' % os.environ['PATH'],
        'tmux version: %s' % get_version(),
        'libtmux version: %s' % libtmux_version,
        'tmuxp version: %s' % __version__,
        'tmux path: %s' % which('tmux'),
        'tmuxp path: %s' % tmuxp_path,
        'shell: %s' % os.environ['SHELL'],
        output_break(),
        'tmux sessions:\n%s' % format_tmux_resp(tmux_cmd('list-sessions')),
        'tmux windows:\n%s' % format_tmux_resp(tmux_cmd('list-windows')),
        'tmux panes:\n%s' % format_tmux_resp(tmux_cmd('list-panes')),
        'tmux global options:\n%s' %
        format_tmux_resp(tmux_cmd('show-options', '-g')),
        'tmux window options:\n%s' %
        format_tmux_resp(tmux_cmd('show-window-options', '-g')),
    ]

    tmuxp_echo('\n'.join(output))
Beispiel #2
0
def command_debug_info():
    """
    Print debug info to submit with Issues.
    """
    def prepend_tab(strings):
        """
        Prepend tab to strings in list.
        """
        return list(map(lambda x: "\t%s" % x, strings))

    def output_break():
        """
        Generate output break.
        """
        return "-" * 25

    def format_tmux_resp(std_resp):
        """
        Format tmux command response for tmuxp stdout.
        """
        return "\n".join([
            "\n".join(prepend_tab(std_resp.stdout)),
            click.style("\n".join(prepend_tab(std_resp.stderr)), fg="red"),
        ])

    output = [
        output_break(),
        "environment:\n%s" % "\n".join(
            prepend_tab([
                "dist: %s" % platform.platform(),
                "arch: %s" % platform.machine(),
                "uname: %s" % "; ".join(platform.uname()[:3]),
                "version: %s" % platform.version(),
            ])),
        output_break(),
        "python version: %s" % " ".join(sys.version.split("\n")),
        "system PATH: %s" % os.environ["PATH"],
        "tmux version: %s" % get_version(),
        "libtmux version: %s" % libtmux_version,
        "tmuxp version: %s" % __version__,
        "tmux path: %s" % which("tmux"),
        "tmuxp path: %s" % tmuxp_path,
        "shell: %s" % os.environ["SHELL"],
        output_break(),
        "tmux sessions:\n%s" % format_tmux_resp(tmux_cmd("list-sessions")),
        "tmux windows:\n%s" % format_tmux_resp(tmux_cmd("list-windows")),
        "tmux panes:\n%s" % format_tmux_resp(tmux_cmd("list-panes")),
        "tmux global options:\n%s" %
        format_tmux_resp(tmux_cmd("show-options", "-g")),
        "tmux window options:\n%s" %
        format_tmux_resp(tmux_cmd("show-window-options", "-g")),
    ]

    tmuxp_echo("\n".join(output))
Beispiel #3
0
def load_workspace(
    config_file,
    socket_name=None,
    socket_path=None,
    colors=None,
    detached=False,
    answer_yes=False,
):
    """
    Load a tmux "workspace" session via tmuxp file.

    Parameters
    ----------
    config_file : str
        absolute path to config file
    socket_name : str, optional
        ``tmux -L <socket-name>``
    socket_path: str, optional
        ``tmux -S <socket-path>``
    colors : str, optional
        '-2'
            Force tmux to support 256 colors
    detached : bool
        Force detached state. default False.
    answer_yes : bool
        Assume yes when given prompt. default False.

    Notes
    -----

    tmuxp will check and load a configuration file. The file will use kaptan
    to load a JSON/YAML into a :py:obj:`dict`. Then :func:`config.expand` and
    :func:`config.trickle` will be used to expand any shorthands, template
    variables, or file paths relative to where the config/script is executed
    from.

    :func:`config.expand` accepts the directory of the config file, so the
    user's configuration can resolve absolute paths relative to where the
    config file is. In otherwords, if a config file at */var/moo/hi.yaml*
    has *./* in its configs, we want to be sure any file path with *./* is
    relative to */var/moo*, not the user's PWD.

    A :class:`libtmux.Server` object is created. No tmux server is started yet,
    just the object.

    The prepared configuration and the server object is passed into an instance
    of :class:`~tmuxp.workspacebuilder.WorkspaceBuilder`.

    A sanity check against :meth:`libtmux.common.which` is ran. It will raise
    an exception if tmux isn't found.

    If a tmux session under the same name as ``session_name`` in the tmuxp
    configuration exists, tmuxp offers to attach the session. Currently, tmuxp
    does not allow appending a workspace / incremental building on top of a
    current session (pull requests are welcome!).

    :meth:`~tmuxp.workspacebuilder.WorkspaceBuilder.build` will build the session in
    the background via using tmux's detached state (``-d``).

    After the session (workspace) is built, unless the user decided to load
    the session in the background via ``tmuxp -d`` (which is in the spirit
    of tmux's ``-d``), we need to prompt the user to attach the session.

    If the user is already inside a tmux client, which we detect via the
    ``TMUX`` environment variable bring present, we will prompt the user to
    switch their current client to it.

    If they're outside of tmux client - in a plain-old PTY - we will
    automatically ``attach``.

    If an exception is raised during the building of the workspace, tmuxp will
    prompt to cleanup (``$ tmux kill-session``) the session on the user's
    behalf. An exception raised during this process means it's not easy to
    predict how broken the session is.

    .. versionchanged:: tmux 2.6+

        In tmux 2.6, the way layout and proportion's work when interfacing
        with tmux in a detached state (outside of a client) changed. Since
        tmuxp builds workspaces in a detached state, the WorkspaceBuilder isn't
        able to rely on functionality requiring awarness of session geometry,
        e.g. ``set-layout``.

        Thankfully, tmux is able to defer commands to run after the user
        performs certain actions, such as loading a client via
        ``attach-session`` or ``switch-client``.

        Upon client switch, ``client-session-changed`` is triggered [1]_.

    References
    ----------
    .. [1] cmd-switch-client.c hook. GitHub repo for tmux.
       https://github.com/tmux/tmux/blob/2.6/cmd-switch-client.c#L132.
       Accessed April 8th, 2018.
    """
    # here we ensure COLS and LINES is set for percentage config
    try:
        curses.initscr()
    finally:
        curses.endwin()

    # get the canonical path, eliminating any symlinks
    config_file = os.path.realpath(config_file)

    # kaptan allows us to open a yaml or json file as a dict
    sconfig = kaptan.Kaptan()
    sconfig = sconfig.import_config(config_file).get()
    # shapes configurations relative to config / profile file location
    sconfig = config.expand(sconfig, os.path.dirname(config_file))
    # propagate config inheritance (e.g. session -> window, window -> pane)
    sconfig = config.trickle(sconfig)

    t = Server(  # create tmux server object
        socket_name=socket_name, socket_path=socket_path, colors=colors
    )

    which('tmux')  # raise exception if tmux not found

    try:  # load WorkspaceBuilder object for tmuxp config / tmux server
        builder = WorkspaceBuilder(sconf=sconfig, server=t)
    except exc.EmptyConfigException:
        click.echo('%s is empty or parsed no config data' % config_file, err=True)
        return

    session_name = sconfig['session_name']

    # if the session already exists, prompt the user to attach. tmuxp doesn't
    # support incremental session building or appending (yet, PR's welcome!)
    if builder.session_exists(session_name):
        if not detached and (
            answer_yes
            or click.confirm(
                '%s is already running. Attach?'
                % click.style(session_name, fg='green'),
                default=True,
            )
        ):
            _reattach(builder.session)
        return

    try:
        click.echo(
            click.style('[Loading] ', fg='green')
            + click.style(config_file, fg='blue', bold=True)
        )

        builder.build()  # load tmux session via workspace builder

        if 'TMUX' in os.environ:  # tmuxp ran from inside tmux
            if not detached and (
                answer_yes or click.confirm('Already inside TMUX, switch to session?')
            ):
                # unset TMUX, save it, e.g. '/tmp/tmux-1000/default,30668,0'
                tmux_env = os.environ.pop('TMUX')

                if has_gte_version('2.6'):
                    set_layout_hook(builder.session, 'client-session-changed')

                builder.session.switch_client()  # switch client to new session

                os.environ['TMUX'] = tmux_env  # set TMUX back again
                return builder.session
            else:  # session created in the background, from within tmux
                if has_gte_version('2.6'):  # prepare for both cases
                    set_layout_hook(builder.session, 'client-attached')
                    set_layout_hook(builder.session, 'client-session-changed')

                sys.exit('Session created in detached state.')
        else:  # tmuxp ran from inside tmux
            if has_gte_version('2.6'):
                # if attaching for first time
                set_layout_hook(builder.session, 'client-attached')

                # for cases where user switches client for first time
                set_layout_hook(builder.session, 'client-session-changed')

            if not detached:
                builder.session.attach_session()

    except exc.TmuxpException as e:
        import traceback

        click.echo(traceback.format_exc(), err=True)
        click.echo(e, err=True)

        choice = click.prompt(
            'Error loading workspace. (k)ill, (a)ttach, (d)etach?',
            value_proc=_validate_choices(['k', 'a', 'd']),
            default='k',
        )

        if choice == 'k':
            builder.session.kill_session()
            click.echo('Session killed.')
        elif choice == 'a':
            if 'TMUX' in os.environ:
                builder.session.switch_client()
            else:
                builder.session.attach_session()
        else:
            sys.exit()

    return builder.session
Beispiel #4
0
def test_which_no_bin_found():
    assert which('top')
    assert which('top', default_paths=[])
    assert not which('top', default_paths=[], append_env_path=False)
    assert not which('top', default_paths=['/'], append_env_path=False)
Beispiel #5
0
def test_which_no_tmuxp_found(monkeypatch):
    monkeypatch.setenv("PATH", "/")
    which('tmuxp')
    which('tmuxp', '/')
Beispiel #6
0
def load_workspace(config_file, args):
    """Build config workspace.

    :param config_file: full path to config file
    :param type: string

    """

    sconfig = kaptan.Kaptan()
    sconfig = sconfig.import_config(config_file).get()
    # expands configurations relative to config / profile file location
    sconfig = config.expand(sconfig, os.path.dirname(config_file))
    sconfig = config.trickle(sconfig)

    t = Server(socket_name=args.socket_name,
               socket_path=args.socket_path,
               colors=args.colors)

    try:
        builder = WorkspaceBuilder(sconf=sconfig, server=t)
    except exc.EmptyConfigException:
        logger.error('%s is empty or parsed no config data' % config_file)
        return

    which('tmux')

    try:
        logger.info('Loading %s.' % config_file)
        builder.build()

        if 'TMUX' in os.environ:
            if not args.detached and (
                    args.answer_yes or
                    prompt_yes_no('Already inside TMUX, switch to session?')):
                tmux_env = os.environ.pop('TMUX')
                builder.session.switch_client()

                os.environ['TMUX'] = tmux_env
                return
            else:
                sys.exit('Session created in detached state.')

        if not args.detached:
            builder.session.attach_session()
    except exc.TmuxSessionExists as e:
        if not args.detached and (args.answer_yes
                                  or prompt_yes_no('%s Attach?' % e)):
            if 'TMUX' in os.environ:
                builder.session.switch_client()

            else:
                builder.session.attach_session()
        return
    except exc.TmuxpException as e:
        import traceback

        print(traceback.format_exc())
        logger.error(e)

        choice = prompt_choices(
            'Error loading workspace. (k)ill, (a)ttach, (d)etach?',
            choices=['k', 'a', 'd'],
            default='k')

        if choice == 'k':
            builder.session.kill_session()
            print('Session killed.')
        elif choice == 'a':
            if 'TMUX' in os.environ:
                builder.session.switch_client()
            else:
                builder.session.attach_session()
        else:
            sys.exit()
Beispiel #7
0
def load_workspace(config_file,
                   socket_name=None,
                   socket_path=None,
                   colors=None,
                   attached=None,
                   detached=None,
                   answer_yes=False):
    """Build config workspace.

    :param config_file: full path to config file
    :param type: str

    """
    # get the canonical path, eliminating any symlinks
    config_file = os.path.realpath(config_file)

    sconfig = kaptan.Kaptan()
    sconfig = sconfig.import_config(config_file).get()
    # expands configurations relative to config / profile file location
    sconfig = config.expand(sconfig, os.path.dirname(config_file))
    sconfig = config.trickle(sconfig)

    t = Server(socket_name=socket_name, socket_path=socket_path, colors=colors)

    try:
        builder = WorkspaceBuilder(sconf=sconfig, server=t)
    except exc.EmptyConfigException:
        click.echo('%s is empty or parsed no config data' % config_file,
                   err=True)
        return

    which('tmux')

    def reattach(session):
        if 'TMUX' in os.environ:
            session.switch_client()

        else:
            session.attach_session()

    session_name = sconfig['session_name']
    if builder.session_exists(session_name):
        if not detached and (answer_yes or click.confirm(
                '%s is already running. Attach?' %
                click.style(session_name, fg='green'),
                default=True)):
            reattach(builder.session)
        return

    try:
        click.echo(
            click.style('[Loading] ', fg='green') +
            click.style(config_file, fg='blue', bold=True))
        builder.build()

        if 'TMUX' in os.environ:  # tmuxp ran from inside tmux
            if not detached and (
                    answer_yes or
                    click.confirm('Already inside TMUX, switch to session?')):
                tmux_env = os.environ.pop('TMUX')

                if has_gte_version('2.6'):
                    # if using -d from inside tmux session + switching inside
                    # https://github.com/tmux/tmux/blob/2.6/cmd-switch-client.c#L132
                    set_layout_hook(builder.session, 'client-session-changed')

                builder.session.switch_client()

                os.environ['TMUX'] = tmux_env
                return builder.session
            else:  # session created in the background, from within tmux
                if has_gte_version('2.6'):  # prepare for both cases
                    set_layout_hook(builder.session, 'client-attached')
                    set_layout_hook(builder.session, 'client-session-changed')

                sys.exit('Session created in detached state.')

        # below: tmuxp ran outside of tmux

        if has_gte_version('2.6'):
            # if attaching for first time
            set_layout_hook(builder.session, 'client-attached')

            # for cases where user switches client for first time
            set_layout_hook(builder.session, 'client-session-changed')

        if not detached:
            builder.session.attach_session()

    except exc.TmuxpException as e:
        import traceback

        click.echo(traceback.format_exc(), err=True)
        click.echo(e, err=True)

        choice = click.prompt(
            'Error loading workspace. (k)ill, (a)ttach, (d)etach?',
            value_proc=_validate_choices(['k', 'a', 'd']),
            default='k')

        if choice == 'k':
            builder.session.kill_session()
            click.echo('Session killed.')
        elif choice == 'a':
            if 'TMUX' in os.environ:
                builder.session.switch_client()
            else:
                builder.session.attach_session()
        else:
            sys.exit()

    return builder.session
Beispiel #8
0
def load_workspace(config_file,
                   socket_name=None,
                   socket_path=None,
                   colors=None,
                   attached=None,
                   detached=None,
                   answer_yes=False):
    """Build config workspace.

    :param config_file: full path to config file
    :param type: string

    """

    sconfig = kaptan.Kaptan()
    sconfig = sconfig.import_config(config_file).get()
    # expands configurations relative to config / profile file location
    sconfig = config.expand(sconfig, os.path.dirname(config_file))
    sconfig = config.trickle(sconfig)

    t = Server(socket_name=socket_name, socket_path=socket_path, colors=colors)

    try:
        builder = WorkspaceBuilder(sconf=sconfig, server=t)
    except exc.EmptyConfigException:
        click.echo('%s is empty or parsed no config data' % config_file,
                   err=True)
        return

    which('tmux')

    def reattach(session):
        if 'TMUX' in os.environ:
            session.switch_client()

        else:
            session.attach_session()

    session_name = sconfig['session_name']
    if builder.session_exists(session_name):
        if not detached and (answer_yes or click.confirm(
                '%s is already running. Attach?' %
                click.style(session_name, fg='green'),
                default=True)):
            reattach(builder.session)
        return

    try:
        click.echo(
            click.style('[Loading] ', fg='green') +
            click.style(config_file, fg='blue', bold=True))
        builder.build()

        if 'TMUX' in os.environ:
            if not detached and (
                    answer_yes or
                    click.confirm('Already inside TMUX, switch to session?')):
                tmux_env = os.environ.pop('TMUX')
                builder.session.switch_client()

                os.environ['TMUX'] = tmux_env
                return builder.session
            else:
                sys.exit('Session created in detached state.')

        if not detached:
            builder.session.attach_session()
    except exc.TmuxpException as e:
        import traceback

        click.echo(traceback.format_exc(), err=True)
        click.echo(e, err=True)

        choice = click.prompt(
            'Error loading workspace. (k)ill, (a)ttach, (d)etach?',
            value_proc=_validate_choices(['k', 'a', 'd']),
            default='k')

        if choice == 'k':
            builder.session.kill_session()
            click.echo('Session killed.')
        elif choice == 'a':
            if 'TMUX' in os.environ:
                builder.session.switch_client()
            else:
                builder.session.attach_session()
        else:
            sys.exit()

    return builder.session
Beispiel #9
0
def load_workspace(
    config_file,
    socket_name=None,
    socket_path=None,
    colors=None,
    detached=False,
    answer_yes=False,
):
    """
    Load a tmux "workspace" session via tmuxp file.

    Parameters
    ----------
    config_file : str
        absolute path to config file
    socket_name : str, optional
        ``tmux -L <socket-name>``
    socket_path: str, optional
        ``tmux -S <socket-path>``
    colors : str, optional
        '-2'
            Force tmux to support 256 colors
    detached : bool
        Force detached state. default False.
    answer_yes : bool
        Assume yes when given prompt. default False.

    Notes
    -----

    tmuxp will check and load a configuration file. The file will use kaptan
    to load a JSON/YAML into a :py:obj:`dict`. Then :func:`config.expand` and
    :func:`config.trickle` will be used to expand any shorthands, template
    variables, or file paths relative to where the config/script is executed
    from.

    :func:`config.expand` accepts the directory of the config file, so the
    user's configuration can resolve absolute paths relative to where the
    config file is. In otherwords, if a config file at */var/moo/hi.yaml*
    has *./* in its configs, we want to be sure any file path with *./* is
    relative to */var/moo*, not the user's PWD.

    A :class:`libtmux.Server` object is created. No tmux server is started yet,
    just the object.

    The prepared configuration and the server object is passed into an instance
    of :class:`~tmuxp.workspacebuilder.WorkspaceBuilder`.

    A sanity check against :meth:`libtmux.common.which` is ran. It will raise
    an exception if tmux isn't found.

    If a tmux session under the same name as ``session_name`` in the tmuxp
    configuration exists, tmuxp offers to attach the session. Currently, tmuxp
    does not allow appending a workspace / incremental building on top of a
    current session (pull requests are welcome!).

    :meth:`~tmuxp.workspacebuilder.WorkspaceBuilder.build` will build the session in
    the background via using tmux's detached state (``-d``).

    After the session (workspace) is built, unless the user decided to load
    the session in the background via ``tmuxp -d`` (which is in the spirit
    of tmux's ``-d``), we need to prompt the user to attach the session.

    If the user is already inside a tmux client, which we detect via the
    ``TMUX`` environment variable bring present, we will prompt the user to
    switch their current client to it.

    If they're outside of tmux client - in a plain-old PTY - we will
    automatically ``attach``.

    If an exception is raised during the building of the workspace, tmuxp will
    prompt to cleanup (``$ tmux kill-session``) the session on the user's
    behalf. An exception raised during this process means it's not easy to
    predict how broken the session is.

    .. versionchanged:: tmux 2.6+

        In tmux 2.6, the way layout and proportion's work when interfacing
        with tmux in a detached state (outside of a client) changed. Since
        tmuxp builds workspaces in a detached state, the WorkspaceBuilder isn't
        able to rely on functionality requiring awarness of session geometry,
        e.g. ``set-layout``.

        Thankfully, tmux is able to defer commands to run after the user
        performs certain actions, such as loading a client via
        ``attach-session`` or ``switch-client``.

        Upon client switch, ``client-session-changed`` is triggered [1]_.

    References
    ----------
    .. [1] cmd-switch-client.c hook. GitHub repo for tmux.
       https://github.com/tmux/tmux/blob/2.6/cmd-switch-client.c#L132.
       Accessed April 8th, 2018.
    """
    # get the canonical path, eliminating any symlinks
    config_file = os.path.realpath(config_file)

    # kaptan allows us to open a yaml or json file as a dict
    sconfig = kaptan.Kaptan()
    sconfig = sconfig.import_config(config_file).get()
    # shapes configurations relative to config / profile file location
    sconfig = config.expand(sconfig, os.path.dirname(config_file))
    # propagate config inheritance (e.g. session -> window, window -> pane)
    sconfig = config.trickle(sconfig)

    t = Server(  # create tmux server object
        socket_name=socket_name, socket_path=socket_path, colors=colors
    )

    which('tmux')  # raise exception if tmux not found

    try:  # load WorkspaceBuilder object for tmuxp config / tmux server
        builder = WorkspaceBuilder(sconf=sconfig, server=t)
    except exc.EmptyConfigException:
        click.echo('%s is empty or parsed no config data' % config_file, err=True)
        return

    session_name = sconfig['session_name']

    # if the session already exists, prompt the user to attach. tmuxp doesn't
    # support incremental session building or appending (yet, PR's welcome!)
    if builder.session_exists(session_name):
        if not detached and (
            answer_yes
            or click.confirm(
                '%s is already running. Attach?'
                % click.style(session_name, fg='green'),
                default=True,
            )
        ):
            _reattach(builder.session)
        return

    try:
        click.echo(
            click.style('[Loading] ', fg='green')
            + click.style(config_file, fg='blue', bold=True)
        )

        builder.build()  # load tmux session via workspace builder

        if 'TMUX' in os.environ:  # tmuxp ran from inside tmux
            if not detached and (
                answer_yes or click.confirm('Already inside TMUX, switch to session?')
            ):
                # unset TMUX, save it, e.g. '/tmp/tmux-1000/default,30668,0'
                tmux_env = os.environ.pop('TMUX')

                if has_gte_version('2.6'):
                    set_layout_hook(builder.session, 'client-session-changed')

                builder.session.switch_client()  # switch client to new session

                os.environ['TMUX'] = tmux_env  # set TMUX back again
                return builder.session
            else:  # session created in the background, from within tmux
                if has_gte_version('2.6'):  # prepare for both cases
                    set_layout_hook(builder.session, 'client-attached')
                    set_layout_hook(builder.session, 'client-session-changed')

                sys.exit('Session created in detached state.')
        else:  # tmuxp ran from inside tmux
            if has_gte_version('2.6'):
                # if attaching for first time
                set_layout_hook(builder.session, 'client-attached')

                # for cases where user switches client for first time
                set_layout_hook(builder.session, 'client-session-changed')

            if not detached:
                builder.session.attach_session()

    except exc.TmuxpException as e:
        import traceback

        click.echo(traceback.format_exc(), err=True)
        click.echo(e, err=True)

        choice = click.prompt(
            'Error loading workspace. (k)ill, (a)ttach, (d)etach?',
            value_proc=_validate_choices(['k', 'a', 'd']),
            default='k',
        )

        if choice == 'k':
            builder.session.kill_session()
            click.echo('Session killed.')
        elif choice == 'a':
            if 'TMUX' in os.environ:
                builder.session.switch_client()
            else:
                builder.session.attach_session()
        else:
            sys.exit()

    return builder.session
Beispiel #10
0
def load_workspace(config_file, args):
    """Build config workspace.

    :param config_file: full path to config file
    :param type: string

    """

    sconfig = kaptan.Kaptan()
    sconfig = sconfig.import_config(config_file).get()
    # expands configurations relative to config / profile file location
    sconfig = config.expand(sconfig, os.path.dirname(config_file))
    sconfig = config.trickle(sconfig)

    t = Server(
        socket_name=args.socket_name,
        socket_path=args.socket_path,
        colors=args.colors
    )

    try:
        builder = WorkspaceBuilder(sconf=sconfig, server=t)
    except exc.EmptyConfigException:
        logger.error('%s is empty or parsed no config data' % config_file)
        return

    which('tmux')

    try:
        logger.info('Loading %s.' % config_file)
        builder.build()

        if 'TMUX' in os.environ:
            if not args.detached and (args.answer_yes or prompt_yes_no(
                'Already inside TMUX, switch to session?'
            )):
                tmux_env = os.environ.pop('TMUX')
                builder.session.switch_client()

                os.environ['TMUX'] = tmux_env
                return
            else:
                sys.exit('Session created in detached state.')

        if not args.detached:
            builder.session.attach_session()
    except exc.TmuxSessionExists as e:
        if not args.detached and (
            args.answer_yes or prompt_yes_no('%s Attach?' % e)
        ):
            if 'TMUX' in os.environ:
                builder.session.switch_client()

            else:
                builder.session.attach_session()
        return
    except exc.TmuxpException as e:
        import traceback

        print(traceback.format_exc())
        logger.error(e)

        choice = prompt_choices(
            'Error loading workspace. (k)ill, (a)ttach, (d)etach?',
            choices=['k', 'a', 'd'],
            default='k'
        )

        if choice == 'k':
            builder.session.kill_session()
            print('Session killed.')
        elif choice == 'a':
            if 'TMUX' in os.environ:
                builder.session.switch_client()
            else:
                builder.session.attach_session()
        else:
            sys.exit()
Beispiel #11
0
def load_workspace(
    config_file, socket_name=None, socket_path=None, colors=None,
    attached=None, detached=None, answer_yes=False
):
    """Build config workspace.

    :param config_file: full path to config file
    :param type: string

    """

    sconfig = kaptan.Kaptan()
    sconfig = sconfig.import_config(config_file).get()
    # expands configurations relative to config / profile file location
    sconfig = config.expand(sconfig, os.path.dirname(config_file))
    sconfig = config.trickle(sconfig)

    t = Server(
        socket_name=socket_name,
        socket_path=socket_path,
        colors=colors
    )

    try:
        builder = WorkspaceBuilder(sconf=sconfig, server=t)
    except exc.EmptyConfigException:
        click.echo('%s is empty or parsed no config data' %
                   config_file, err=True)
        return

    which('tmux')

    def reattach(session):
        if 'TMUX' in os.environ:
            session.switch_client()

        else:
            session.attach_session()

    session_name = sconfig['session_name']
    if builder.session_exists(session_name):
        if not detached and (
            answer_yes or click.confirm(
                '%s is already running. Attach?' %
                click.style(session_name, fg='green'), default=True)
        ):
            reattach(builder.session)
        return

    try:
        click.echo(
            click.style('[Loading] ', fg='green') +
            click.style(config_file, fg='blue', bold=True))
        builder.build()

        if 'TMUX' in os.environ:
            if not detached and (answer_yes or click.confirm(
                'Already inside TMUX, switch to session?'
            )):
                tmux_env = os.environ.pop('TMUX')
                builder.session.switch_client()

                os.environ['TMUX'] = tmux_env
                return builder.session
            else:
                sys.exit('Session created in detached state.')

        if not detached:
            builder.session.attach_session()
    except exc.TmuxpException as e:
        import traceback

        click.echo(traceback.format_exc(), err=True)
        click.echo(e, err=True)

        choice = click.prompt(
            'Error loading workspace. (k)ill, (a)ttach, (d)etach?',
            value_proc=_validate_choices(['k', 'a', 'd']),
            default='k'
        )

        if choice == 'k':
            builder.session.kill_session()
            click.echo('Session killed.')
        elif choice == 'a':
            if 'TMUX' in os.environ:
                builder.session.switch_client()
            else:
                builder.session.attach_session()
        else:
            sys.exit()

    return builder.session
Beispiel #12
0
def load_workspace(
    config_file,
    socket_name=None,
    socket_path=None,
    tmux_config_file=None,
    new_session_name=None,
    colors=None,
    detached=False,
    answer_yes=False,
    append=False,
):
    """
    Load a tmux "workspace" session via tmuxp file.

    Parameters
    ----------
    config_file : str
        absolute path to config file
    socket_name : str, optional
        ``tmux -L <socket-name>``
    socket_path: str, optional
        ``tmux -S <socket-path>``
    new_session_name: str, options
        ``tmux new -s <new_session_name>``
    colors : str, optional
        '-2'
            Force tmux to support 256 colors
    detached : bool
        Force detached state. default False.
    answer_yes : bool
        Assume yes when given prompt to attach in new session.
        Default False.
    append : bool
       Assume current when given prompt to append windows in same session.
       Default False.

    Notes
    -----

    tmuxp will check and load a configuration file. The file will use kaptan
    to load a JSON/YAML into a :py:obj:`dict`. Then :func:`config.expand` and
    :func:`config.trickle` will be used to expand any shorthands, template
    variables, or file paths relative to where the config/script is executed
    from.

    :func:`config.expand` accepts the directory of the config file, so the
    user's configuration can resolve absolute paths relative to where the
    config file is. In otherwords, if a config file at */var/moo/hi.yaml*
    has *./* in its configs, we want to be sure any file path with *./* is
    relative to */var/moo*, not the user's PWD.

    A :class:`libtmux.Server` object is created. No tmux server is started yet,
    just the object.

    The prepared configuration and the server object is passed into an instance
    of :class:`~tmuxp.workspacebuilder.WorkspaceBuilder`.

    A sanity check against :meth:`libtmux.common.which` is ran. It will raise
    an exception if tmux isn't found.

    If a tmux session under the same name as ``session_name`` in the tmuxp
    configuration exists, tmuxp offers to attach the session. Currently, tmuxp
    does not allow appending a workspace / incremental building on top of a
    current session (pull requests are welcome!).

    :meth:`~tmuxp.workspacebuilder.WorkspaceBuilder.build` will build the session in
    the background via using tmux's detached state (``-d``).

    After the session (workspace) is built, unless the user decided to load
    the session in the background via ``tmuxp -d`` (which is in the spirit
    of tmux's ``-d``), we need to prompt the user to attach the session.

    If the user is already inside a tmux client, which we detect via the
    ``TMUX`` environment variable bring present, we will prompt the user to
    switch their current client to it.

    If they're outside of tmux client - in a plain-old PTY - we will
    automatically ``attach``.

    If an exception is raised during the building of the workspace, tmuxp will
    prompt to cleanup (``$ tmux kill-session``) the session on the user's
    behalf. An exception raised during this process means it's not easy to
    predict how broken the session is.

    .. versionchanged:: tmux 2.6+

        In tmux 2.6, the way layout and proportion's work when interfacing
        with tmux in a detached state (outside of a client) changed. Since
        tmuxp builds workspaces in a detached state, the WorkspaceBuilder isn't
        able to rely on functionality requiring awarness of session geometry,
        e.g. ``set-layout``.

        Thankfully, tmux is able to defer commands to run after the user
        performs certain actions, such as loading a client via
        ``attach-session`` or ``switch-client``.

        Upon client switch, ``client-session-changed`` is triggered [1]_.

    References
    ----------
    .. [1] cmd-switch-client.c hook. GitHub repo for tmux.

       https://github.com/tmux/tmux/blob/2.6/cmd-switch-client.c#L132.
       Accessed April 8th, 2018.
    """
    # get the canonical path, eliminating any symlinks
    config_file = os.path.realpath(config_file)

    tmuxp_echo(
        click.style("[Loading] ", fg="green") +
        click.style(config_file, fg="blue", bold=True))

    # kaptan allows us to open a yaml or json file as a dict
    sconfig = kaptan.Kaptan()
    sconfig = sconfig.import_config(config_file).get()
    # shapes configurations relative to config / profile file location
    sconfig = config.expand(sconfig, os.path.dirname(config_file))
    # Overwrite session name
    if new_session_name:
        sconfig["session_name"] = new_session_name
    # propagate config inheritance (e.g. session -> window, window -> pane)
    sconfig = config.trickle(sconfig)

    t = Server(  # create tmux server object
        socket_name=socket_name,
        socket_path=socket_path,
        config_file=tmux_config_file,
        colors=colors,
    )

    which("tmux")  # raise exception if tmux not found

    try:  # load WorkspaceBuilder object for tmuxp config / tmux server
        builder = WorkspaceBuilder(sconf=sconfig,
                                   plugins=load_plugins(sconfig),
                                   server=t)
    except exc.EmptyConfigException:
        tmuxp_echo("%s is empty or parsed no config data" % config_file,
                   err=True)
        return

    session_name = sconfig["session_name"]

    # if the session already exists, prompt the user to attach
    if builder.session_exists(session_name) and not append:
        if not detached and (answer_yes or click.confirm(
                "%s is already running. Attach?" %
                click.style(session_name, fg="green"),
                default=True,
        )):
            _reattach(builder)
        return

    try:
        if detached:
            _load_detached(builder)
            return _setup_plugins(builder)

        if append:
            if "TMUX" in os.environ:  # tmuxp ran from inside tmux
                _load_append_windows_to_current_session(builder)
            else:
                _load_attached(builder, detached)

            return _setup_plugins(builder)

        # append and answer_yes have no meaning if specified together
        elif answer_yes:
            _load_attached(builder, detached)
            return _setup_plugins(builder)

        if "TMUX" in os.environ:  # tmuxp ran from inside tmux
            msg = (
                "Already inside TMUX, switch to session? yes/no\n"
                "Or (a)ppend windows in the current active session?\n[y/n/a]")
            options = ["y", "n", "a"]
            choice = click.prompt(msg, value_proc=_validate_choices(options))

            if choice == "y":
                _load_attached(builder, detached)
            elif choice == "a":
                _load_append_windows_to_current_session(builder)
            else:
                _load_detached(builder)
        else:
            _load_attached(builder, detached)

    except exc.TmuxpException as e:
        import traceback

        tmuxp_echo(traceback.format_exc(), err=True)
        tmuxp_echo(e, err=True)

        choice = click.prompt(
            "Error loading workspace. (k)ill, (a)ttach, (d)etach?",
            value_proc=_validate_choices(["k", "a", "d"]),
            default="k",
        )

        if choice == "k":
            builder.session.kill_session()
            tmuxp_echo("Session killed.")
        elif choice == "a":
            _reattach(builder)
        else:
            sys.exit()

    return _setup_plugins(builder)
Beispiel #13
0
def test_which_no_bin_found():
    assert which('top')
    assert which('top', default_paths=[])
    assert not which('top', default_paths=[], append_env_path=False)
    assert not which('top', default_paths=['/'], append_env_path=False)
Beispiel #14
0
def test_which_no_bin_found():
    assert which("top")
    assert which("top", default_paths=[])
    assert not which("top", default_paths=[], append_env_path=False)
    assert not which("top", default_paths=["/"], append_env_path=False)