Skip to content

Commit

Permalink
(Non-working) shell implementation, as per #41
Browse files Browse the repository at this point in the history
  • Loading branch information
joerick committed Aug 24, 2016
1 parent 2002a88 commit 92c1109
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 1 deletion.
41 changes: 40 additions & 1 deletion tbtool/__main__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python

from docopt import docopt
import os, textwrap, shutil, filecmp, subprocess, sys, logging, fnmatch
import os, textwrap, shutil, filecmp, subprocess, sys, logging, fnmatch, threading
from .get_terminal_size import get_terminal_size
import paramiko
from .appdirs import AppDirs

Expand All @@ -23,6 +24,41 @@ def exec_command(self, command):
if code != 0:
raise SSHSession.RemoteCommandError(command, code, output, error_output)

def shell(self, command=None):
'''
runs a command, piping stdin to the remote shell and piping stdout and stderr
to the shell.
'''

channel = self.client.get_transport().open_session()

if sys.stdout.isatty() and not command:
terminal_size = get_terminal_size()

channel.get_pty(
term=os.environ.get('TERM', 'vt100'),
width=terminal_size.columns,
height=terminal_size.lines,
)

channel.exec_command(command or 'bash')

stdin = channel.makefile('wb')
stdout = channel.makefile('r')
stderr = channel.makefile_stderr('r')

threading.Thread(target=self.pipe, args=(sys.stdin, stdin)).start()
threading.Thread(target=self.pipe, args=(stdout, sys.stdout)).start()
threading.Thread(target=self.pipe, args=(stderr, sys.stderr)).start()

def pipe(self, from_file, to_file):
with to_file:
while True:
data = from_file.read()
if data == '':
break
to_file.write(data)

This comment has been minimized.

Copy link
@joerick

joerick Aug 26, 2016

Author Member

Just realised why this isn't working – read() is waiting for EOF before it returns. Should use recv() on the channel instead, or read() in non-blocking mode


put_dir_default_ignore_patterns = ['venv', 'local_settings.json', '.git', '*.pyc']

def put_dir(self, source, target, ignore_patterns=put_dir_default_ignore_patterns):
Expand Down Expand Up @@ -123,6 +159,9 @@ def _run(app_path, extra_env=None):
def simulate(app_path):
_run(app_path)

def shell(hostname, command):
session = SSHSession(hostname)
session.shell(command)

def run(app_path, hostname):
print 'tbtool: Connecting to Pi...'
Expand Down
101 changes: 101 additions & 0 deletions tbtool/get_terminal_size.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""This is a backport of shutil.get_terminal_size from Python 3.3.
The original implementation is in C, but here we use the ctypes and
fcntl modules to create a pure Python version of os.get_terminal_size.
"""

import os
import struct
import sys

from collections import namedtuple

__all__ = ["get_terminal_size"]


terminal_size = namedtuple("terminal_size", "columns lines")

try:
from ctypes import windll, create_string_buffer

_handles = {
0: windll.kernel32.GetStdHandle(-10),
1: windll.kernel32.GetStdHandle(-11),
2: windll.kernel32.GetStdHandle(-12),
}

def _get_terminal_size(fd):
columns = lines = 0

try:
handle = _handles[fd]
csbi = create_string_buffer(22)
res = windll.kernel32.GetConsoleScreenBufferInfo(handle, csbi)
if res:
res = struct.unpack("hhhhHhhhhhh", csbi.raw)
left, top, right, bottom = res[5:9]
columns = right - left + 1
lines = bottom - top + 1
except Exception:
pass

return terminal_size(columns, lines)

except ImportError:
import fcntl
import termios

def _get_terminal_size(fd):
try:
res = fcntl.ioctl(fd, termios.TIOCGWINSZ, b"\x00" * 4)
lines, columns = struct.unpack("hh", res)
except Exception:
columns = lines = 0

return terminal_size(columns, lines)


def get_terminal_size(fallback=(80, 24)):
"""Get the size of the terminal window.
For each of the two dimensions, the environment variable, COLUMNS
and LINES respectively, is checked. If the variable is defined and
the value is a positive integer, it is used.
When COLUMNS or LINES is not defined, which is the common case,
the terminal connected to sys.__stdout__ is queried
by invoking os.get_terminal_size.
If the terminal size cannot be successfully queried, either because
the system doesn't support querying, or because we are not
connected to a terminal, the value given in fallback parameter
is used. Fallback defaults to (80, 24) which is the default
size used by many terminal emulators.
The value returned is a named tuple of type os.terminal_size.
"""
# Try the environment first
try:
columns = int(os.environ["COLUMNS"])
except (KeyError, ValueError):
columns = 0

try:
lines = int(os.environ["LINES"])
except (KeyError, ValueError):
lines = 0

# Only query if necessary
if columns <= 0 or lines <= 0:
try:
size = _get_terminal_size(sys.__stdout__.fileno())
except (NameError, OSError):
size = terminal_size(*fallback)

if columns <= 0:
columns = size.columns
if lines <= 0:
lines = size.lines

return terminal_size(columns, lines)

0 comments on commit 92c1109

Please sign in to comment.