Add support for external commands

If you run "gertty --open <URL>" it will instruct a running
Gertty to open the change at that URL.

Change-Id: Ie82aa53f497717e7355646d6d6fd12473ececad0
This commit is contained in:
James E. Blair 2016-01-14 13:51:06 -08:00
parent 74877d2499
commit 1222a7f927
3 changed files with 71 additions and 1 deletions

View File

@ -47,6 +47,9 @@ servers:
# time it starts (so that it does not grow without bound). If you
# would like to log to a different location, you may specify it here.
# log-file: ~/.gertty.log
# Gertty listens on a unix domain socket for remote commands at
# ~/.gertty.sock. You may change the path here:
# socket: ~/.gertty.sock
# Gertty comes with two palettes defined internally. The default
# palette is suitable for use on a terminal with a dark background.

View File

@ -19,6 +19,7 @@ import dateutil
import logging
import os
import re
import socket
import subprocess
import sys
import textwrap
@ -256,6 +257,8 @@ class App(object):
self.error_queue = queue.Queue()
self.error_pipe = self.loop.watch_pipe(self._errorPipeInput)
self.logged_warnings = set()
self.command_pipe = self.loop.watch_pipe(self._commandPipeInput)
self.command_queue = queue.Queue()
warnings.showwarning = self._showWarning
@ -268,6 +271,9 @@ class App(object):
self.loop.screen.tty_signal_keys(start='undefined', stop='undefined')
#self.loop.screen.set_terminal_properties(colors=88)
self.startSocketListener()
if not disable_sync:
self.sync_thread = threading.Thread(target=self.sync.run, args=(self.sync_pipe,))
self.sync_thread.daemon = True
@ -294,6 +300,35 @@ class App(object):
self.popup(dialog)
def startSocketListener(self):
if os.path.exists(self.config.socket_path):
os.unlink(self.config.socket_path)
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.socket.bind(self.config.socket_path)
self.socket.listen(1)
self.socket_thread = threading.Thread(target=self._socketListener)
self.socket_thread.daemon = True
self.socket_thread.start()
def _socketListener(self):
while True:
try:
s, addr = self.socket.accept()
self.log.debug("Accepted socket connection %s" % (s,))
buf = ''
while True:
buf += s.recv(1)
if buf[-1] == '\n':
break
buf = buf.strip()
self.log.debug("Received %s from socket" % (buf,))
s.close()
parts = buf.split()
self.command_queue.put((parts[0], parts[1:]))
os.write(self.command_pipe, six.b('command\n'))
except Exception:
self.log.exception("Exception in socket handler")
def clearInputBuffer(self):
if self.input_buffer:
self.input_buffer = []
@ -598,7 +633,18 @@ class App(object):
if category == requestsexceptions.InsecureRequestWarning:
return
self.error_queue.put(('Warning', m))
os.write(self.error_pipe, 'error\n')
os.write(self.error_pipe, six.b('error\n'))
def _commandPipeInput(self, data=None):
(command, data) = self.command_queue.get()
if command == 'open':
url = data[0]
self.log.debug("Opening URL %s" % (url,))
result = self.parseInternalURL(url)
if result is not None:
self.openInternalURL(result)
else:
self.log.error("Unable to parse command %s with data %s" % (command, data))
def toggleHeldChange(self, change_key):
with self.db.getSession() as session:
@ -709,6 +755,21 @@ class PrintPaletteAction(argparse.Action):
print(attr)
sys.exit(0)
class OpenChangeAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
cf = config.Config(namespace.server, namespace.palette,
namespace.keymap, namespace.path)
url = values[0]
result = urlparse.urlparse(values[0])
if not url.startswith(cf.url):
print('Supplied URL must start with %s' % (cf.url,))
sys.exit(1)
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(cf.socket_path)
s.sendall('open %s\n' % url)
sys.exit(0)
def main():
parser = argparse.ArgumentParser(
description='Console client for Gerrit Code Review.')
@ -728,6 +789,9 @@ def main():
help='print the keymap command names to stdout')
parser.add_argument('--print-palette', nargs=0, action=PrintPaletteAction,
help='print the palette attribute names to stdout')
parser.add_argument('--open', nargs=1, action=OpenChangeAction,
metavar='URL',
help='open the given URL in a running Gertty')
parser.add_argument('--version', dest='version', action='version',
version=version(),
help='show Gertty\'s version')

View File

@ -47,6 +47,7 @@ class ConfigSchema(object):
'dburi': str,
v.Required('git-root'): str,
'log-file': str,
'socket': str,
'auth-type': str,
}
@ -173,6 +174,8 @@ class Config(object):
self.git_root = os.path.expanduser(server['git-root'])
self.dburi = server.get('dburi',
'sqlite:///' + os.path.expanduser('~/.gertty.db'))
socket_path = server.get('socket', '~/.gertty.sock')
self.socket_path = os.path.expanduser(socket_path)
log_file = server.get('log-file', '~/.gertty.log')
self.log_file = os.path.expanduser(log_file)