# Copyright 2014 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import argparse import logging import os import sys import threading import urwid import db import config import gitrepo import mywid import sync import view.project_list palette=[('reversed', 'default,standout', ''), ('header', 'white,bold', 'dark blue'), ('error', 'light red', 'dark blue'), ('table-header', 'white,bold', ''), # Diff ('removed-line', 'dark red', ''), ('removed-word', 'light red', ''), ('added-line', 'dark green', ''), ('added-word', 'light green', ''), ('nonexistent', 'default', ''), ('reversed-removed-line', 'dark red,standout', ''), ('reversed-removed-word', 'light red,standout', ''), ('reversed-added-line', 'dark green,standout', ''), ('reversed-added-word', 'light green,standout', ''), ('reversed-nonexistent', 'default,standout', ''), ('draft-comment', 'default', 'dark gray'), ('comment', 'light gray', 'dark gray'), ('comment-name', 'white', 'dark gray'), # Change view ('change-data', 'light cyan', ''), ('change-header', 'light blue', ''), ('revision-name', 'light blue', ''), ('revision-commit', 'dark blue', ''), ('revision-comments', 'default', ''), ('revision-drafts', 'dark red', ''), ('reversed-revision-name', 'light blue,standout', ''), ('reversed-revision-commit', 'dark blue,standout', ''), ('reversed-revision-comments', 'default,standout', ''), ('reversed-revision-drafts', 'dark red,standout', ''), ('change-message-name', 'light blue', ''), ('change-message-header', 'dark blue', ''), # project list ('unreviewed-project', 'white', ''), ('subscribed-project', 'default', ''), ('unsubscribed-project', 'dark gray', ''), ('reversed-unreviewed-project', 'white,standout', ''), ('reversed-subscribed-project', 'default,standout', ''), ('reversed-unsubscribed-project', 'dark gray,standout', ''), # change list ('unreviewed-change', 'default', ''), ('reviewed-change', 'dark gray', ''), ('reversed-unreviewed-change', 'default,standout', ''), ('reversed-reviewed-change', 'dark gray,standout', ''), ] class StatusHeader(urwid.WidgetWrap): def __init__(self, app): super(StatusHeader, self).__init__(urwid.Columns([])) self.app = app self.title = urwid.Text(u'Start') self.error = urwid.Text('') self.offline = urwid.Text('') self.sync = urwid.Text(u'Sync: 0') self._w.contents.append((self.title, ('pack', None, False))) self._w.contents.append((urwid.Text(u''), ('weight', 1, False))) self._w.contents.append((self.error, ('pack', None, False))) self._w.contents.append((self.offline, ('pack', None, False))) self._w.contents.append((self.sync, ('pack', None, False))) def update(self, title=None, error=False, offline=None): if title: self.title.set_text(title) if error: self.error.set_text(('error', u'Error')) if offline is not None: if offline: self.error.set_text(u'Offline') else: self.error.set_text(u'') self.sync.set_text(u' Sync: %i' % self.app.sync.queue.qsize()) class App(object): def __init__(self, server=None, debug=False): self.server = server self.config = config.Config(server) if debug: level = logging.DEBUG else: level = logging.WARNING logging.basicConfig(filename=self.config.log_file, filemode='w', format='%(asctime)s %(message)s', level=level) self.log = logging.getLogger('gertty.App') self.log.debug("Starting") self.db = db.Database(self) self.sync = sync.Sync(self) self.screens = [] self.status = StatusHeader(self) self.header = urwid.AttrMap(self.status, 'header') screen = view.project_list.ProjectListView(self) self.status.update(title=screen.title) self.loop = urwid.MainLoop(screen, palette=palette, unhandled_input=self.unhandledInput) sync_pipe = self.loop.watch_pipe(self.refresh) #self.loop.screen.set_terminal_properties(colors=88) self.sync_thread = threading.Thread(target=self.sync.run, args=(sync_pipe,)) self.sync_thread.start() self.loop.run() def changeScreen(self, widget): self.status.update(title=widget.title) self.screens.append(self.loop.widget) self.loop.widget = widget def backScreen(self): if not self.screens: return widget = self.screens.pop() self.status.update(title=widget.title) self.loop.widget = widget self.refresh() def refresh(self, data=None): widget = self.loop.widget while isinstance(widget, urwid.Overlay): widget = widget.contents[0][0] widget.refresh() def popup(self, widget, relative_width=50, relative_height=25, min_width=20, min_height=8): overlay = urwid.Overlay(widget, self.loop.widget, 'center', ('relative', relative_width), 'middle', ('relative', relative_height), min_width=min_width, min_height=min_height) self.screens.append(self.loop.widget) self.loop.widget = overlay def help(self): if not hasattr(self.loop.widget, 'help'): return dialog = mywid.MessageDialog('Help', self.loop.widget.help) lines = self.loop.widget.help.split('\n') urwid.connect_signal(dialog, 'close', lambda button: self.backScreen()) self.popup(dialog, min_width=76, min_height=len(lines)+2) def unhandledInput(self, key): if key == 'esc': self.backScreen() elif key == 'f1': self.help() def getRepo(self, project_name): local_path = os.path.join(self.config.git_root, project_name) local_root = os.path.abspath(self.config.git_root) assert os.path.commonprefix((local_root, local_path)) == local_root return gitrepo.Repo(self.config.url+'p/'+project_name, local_path) if __name__ == '__main__': parser = argparse.ArgumentParser( description='Console client for Gerrit Code Review.') parser.add_argument('-d', dest='debug', action='store_true', help='enable debug logging') parser.add_argument('server', nargs='?', help='the server to use (as specified in config file)') args = parser.parse_args() g = App(args.server, args.debug)