Пример #1
0
class Tui(object):
    signals = ['close']

    def __init__(self, controller, style):
        # Shared objects to help event handling.
        self.events = Queue()
        self.lock = Lock()

        self.view = MainWindow(controller)
        self.screen = raw_display.Screen()
        self.screen.set_terminal_properties(256)

        self.loop = MainLoop(widget=self.view,
                             palette=style,
                             screen=self.screen,
                             unhandled_input=Tui.exit_handler,
                             pop_ups=True)

        self.pipe = self.loop.watch_pipe(self.update_ui)
        self.loop.set_alarm_in(0.1, self.update_timer, self.view.logo.timer)

        connect_signal(self.view.issues_table, 'refresh',
                       lambda source: self.loop.draw_screen())
        connect_signal(self.view.stat_table, 'refresh',
                       lambda source: self.loop.draw_screen())

    def update_ui(self, _):
        while True:
            try:
                event = self.events.get_nowait()
                if hasattr(self, event['fn']):
                    getattr(self, event['fn'])(**event['kwargs'])
            except Exception:
                break

    def update_timer(self, loop, timer):
        if timer.update():
            loop.set_alarm_in(0.1, self.update_timer, timer)

    def new_fuzz_job(self, ident, cost, sut, fuzzer, batch):
        self.view.job_table.add_fuzz_job(ident, fuzzer, sut, cost, batch)

    def new_reduce_job(self, ident, cost, sut, issue_id, size):
        self.view.job_table.add_reduce_job(ident, sut, cost, issue_id, size)

    def new_update_job(self, ident, cost, sut):
        self.view.job_table.add_update_job(ident, sut)

    def new_validate_job(self, ident, cost, sut, issue_id):
        self.view.job_table.add_validate_job(ident, sut, issue_id)

    def remove_job(self, ident):
        self.view.job_table.remove_job(ident)

    def activate_job(self, ident):
        self.view.job_table.activate_job(ident)

    def job_progress(self, ident, progress):
        self.view.job_table.job_progress(ident, progress)

    def update_load(self, load):
        self.view.logo.load.set_completion(load)

    def update_fuzz_stat(self):
        self.view.stat_table.update()

    def new_issue(self, ident, issue):
        # Do shiny animation if a new issue has received.
        self.view.logo.do_animate = True
        self.loop.set_alarm_at(time.time() + 5,
                               callback=self.view.logo.stop_animation)
        self.loop.set_alarm_in(0.1, self.view.logo.animate, self.view.logo)
        self.view.issues_table.add_row(issue)

    def invalid_issue(self, ident, issue):
        self.view.issues_table.invalidate_row(ident=issue['_id'])

    def update_issue(self, ident, issue):
        self.view.issues_table.update_row(ident=issue['_id'])

    def reduced_issue(self, ident, issue):
        self.view.issues_table.update_row(ident=issue['_id'])

    def warning(self, ident, msg):
        self.view._emit('warning', msg)

    @staticmethod
    def exit_handler(key):
        if key in ('q', 'Q', 'f10'):
            raise ExitMainLoop()
Пример #2
0
class Shipit():
    ISSUE_LIST = 0
    ISSUE_DETAIL = 1
    PR_DETAIL = 2
    PR_DIFF = 3

    def __init__(self, ui, repo, user):
        self.ui = ui
        self.repo = repo
        self.user = user

        self.issues_and_prs = IssuesAndPullRequests(self.repo)
        self.issues_and_prs.set_modified_callback(
            self.on_modify_issues_and_prs)
        self.issues_and_prs.show_open_issues()

        # Event handlers
        on("show_all", self.issues_and_prs.show_all)

        created_by_you = partial(self.issues_and_prs.show_created_by,
                                 self.user)
        on("show_created_by_you", created_by_you)

        assigned_to_you = partial(self.issues_and_prs.show_assigned_to,
                                  self.user)
        on("show_assigned_to_you", assigned_to_you)

        mentioning_you = partial(self.issues_and_prs.show_mentioning,
                                 self.user)
        on("show_mentioning_you", mentioning_you)

        on("show_open_issues", self.issues_and_prs.show_open_issues)
        on("show_closed_issues", self.issues_and_prs.show_closed_issues)
        on("show_pull_requests", self.issues_and_prs.show_pull_requests)

        on("filter_by_labels", self.issues_and_prs.filter_by_labels)
        on("clear_label_filters", self.issues_and_prs.clear_label_filters)

    def start(self):
        self.loop = MainLoop(self.ui,
                             PALETTE,
                             handle_mouse=True,
                             unhandled_input=self.handle_keypress)
        self.loop.set_alarm_at(0, discard_args(self.issue_list))
        self.loop.run()

    def on_modify_issues_and_prs(self):
        self.ui.issues_and_pulls(self.issues_and_prs)

    def issue_list(self):
        self.mode = self.ISSUE_LIST
        self.ui.issues_and_pulls(self.issues_and_prs)
        self.loop.draw_screen()

    def issue_detail(self, issue):
        self.mode = self.ISSUE_DETAIL
        self.ui.issue(issue)
        self.loop.draw_screen()

    def pull_request_detail(self, pr):
        self.mode = self.PR_DETAIL
        self.ui.pull_request(pr)
        self.loop.draw_screen()

    def diff(self, pr):
        self.mode = self.PR_DIFF
        self.ui.diff(pr)
        self.loop.draw_screen()

    def handle_keypress(self, key):
        if key == KEY_OPEN_ISSUE:
            if self.mode is self.ISSUE_LIST:
                issue_text = self.spawn_editor(NEW_ISSUE)

                if issue_text is None:
                    # TODO: cancelled by the user
                    return

                contents = unlines(issue_text)
                title, *body = contents

                if not title:
                    # TODO: incorrect input, at least a title is needed
                    return
                body = lines(body)

                issue = self.repo.create_issue(title=title, body=body)

                if issue:
                    self.issue_detail(issue)
                else:
                    self.issue_list()
        elif key == KEY_CLOSE_ISSUE:
            issue = self.ui.get_issue()

            if not issue:
                return

            self.issues_and_prs.close(issue)

            if self.mode is self.ISSUE_DETAIL:
                self.issue_detail(issue)
        elif key == KEY_REOPEN_ISSUE:
            issue = self.ui.get_issue()

            if issue and is_closed(issue):
                self.issues_and_prs.reopen(issue)

            if self.mode is self.ISSUE_DETAIL:
                self.issue_detail(issue)
        elif key == KEY_BACK:
            if self.mode is self.PR_DIFF:
                pr = self.ui.get_focused_item()
                self.pull_request_detail(pr)
            elif self.mode in [self.ISSUE_DETAIL, self.PR_DETAIL]:
                self.issue_list()
        elif key == KEY_DETAIL:
            if self.mode is self.ISSUE_LIST:
                issue_or_pr = self.ui.get_focused_item()

                if is_issue(issue_or_pr):
                    self.issue_detail(issue_or_pr)
                elif is_pull_request(issue_or_pr):
                    self.pull_request_detail(issue_or_pr)
        elif key == KEY_EDIT:
            item = self.ui.get_focused_item()

            if item is None:
                return

            if is_pull_request(item):
                item = item.issue

            # In case you aren't the owner of the repo, you are only allowed to
            # edit things that you created.
            if self.repo.owner != self.user and item.user != self.user:
                # TODO: beep
                return

            if is_issue(item):
                self.edit_issue(item)
            else:
                self.edit_body(item)
        elif key == KEY_COMMENT:
            item = self.ui.get_issue_or_pr()

            if item is None:
                return

            if is_pull_request(item):
                issue = item.issue
                self.comment_issue(item.issue, pull_request=item)
            else:
                self.comment_issue(item)
        elif key == KEY_DIFF:
            if self.mode is self.PR_DETAIL:
                pr = self.ui.get_focused_item()
                self.diff(pr)
        elif key == KEY_BROWSER:
            item = self.ui.get_focused_item()
            if hasattr(item, '_api'):
                webbrowser.open(item.html_url)
        elif key == KEY_QUIT:
            raise ExitMainLoop

    def edit_issue(self, issue):
        title_and_body = '\n'.join([issue.title, issue.body])
        issue_text = self.spawn_editor(title_and_body)

        if issue_text is None:
            # TODO: cancelled
            return

        contents = unlines(issue_text)
        title, *body = contents

        if not title:
            # TODO: incorrect input, at least a title is needed
            return
        body = lines(body)

        issue.edit(title=title, body=body)

        if self.mode is self.ISSUE_LIST:
            # TODO: focus
            self.issue_list()
        elif self.mode is self.ISSUE_DETAIL:
            self.issue_detail(issue)

    # TODO
    def edit_pull_request(self, pr):
        pass

    def edit_body(self, item):
        text = self.spawn_editor(item.body)

        if text is None:
            # TODO: cancelled
            return

        # TODO: ui must be updated!
        item.edit(text)

    def comment_issue(self, issue, *, pull_request=False):
        issue_thread = format_issue_thread(issue)

        comment_text = self.spawn_editor('\n'.join(issue_thread))

        if comment_text is None:
            # TODO: cancelled
            return

        if not comment_text:
            # TODO: A empty comment is invalid input
            return

        issue.create_comment(comment_text)

        if pull_request:
            self.pull_request_detail(pull_request)
        else:
            self.issue_detail(issue)

    def spawn_editor(self, help_text=None):
        """
        Open a editor with a temporary file containing ``help_text``.

        If the exit code is 0 the text from the file will be returned.

        Otherwise, ``None`` is returned.
        """
        text = '' if help_text is None else help_text

        tmp_file = tempfile.NamedTemporaryFile(mode='w+',
                                               suffix='.markdown',
                                               delete=False)
        tmp_file.write(text)
        tmp_file.close()

        fname = tmp_file.name

        self.loop.screen.stop()

        return_code = subprocess.call([os.getenv('EDITOR', 'vim'), fname])

        self.loop.screen.start()

        if return_code == 0:
            with open(fname, 'r') as f:
                contents = f.read()

        if return_code != 0:
            return None
        else:
            return strip_comments(contents)
Пример #3
0
class Shipit():
    ISSUE_LIST = 0
    ISSUE_DETAIL = 1
    PR_DETAIL = 2
    PR_DIFF = 3

    def __init__(self, ui, repo, user):
        self.ui = ui
        self.repo = repo
        self.user = user

        self.issues_and_prs = IssuesAndPullRequests(self.repo)
        self.issues_and_prs.set_modified_callback(self.on_modify_issues_and_prs)
        self.issues_and_prs.show_open_issues()

        # Event handlers
        on("show_all", self.issues_and_prs.show_all)

        created_by_you = partial(self.issues_and_prs.show_created_by, self.user)
        on("show_created_by_you", created_by_you)

        assigned_to_you = partial(self.issues_and_prs.show_assigned_to, self.user)
        on("show_assigned_to_you", assigned_to_you)

        mentioning_you = partial(self.issues_and_prs.show_mentioning, self.user)
        on("show_mentioning_you", mentioning_you)

        on("show_open_issues", self.issues_and_prs.show_open_issues)
        on("show_closed_issues", self.issues_and_prs.show_closed_issues)
        on("show_pull_requests", self.issues_and_prs.show_pull_requests)

        on("filter_by_labels", self.issues_and_prs.filter_by_labels)
        on("clear_label_filters", self.issues_and_prs.clear_label_filters)

    def start(self):
        self.loop = MainLoop(self.ui,
                             PALETTE,
                             handle_mouse=True,
                             unhandled_input=self.handle_keypress)
        self.loop.set_alarm_at(0, discard_args(self.issue_list))
        self.loop.run()

    def on_modify_issues_and_prs(self):
        self.ui.issues_and_pulls(self.issues_and_prs)

    def issue_list(self):
        self.mode = self.ISSUE_LIST
        self.ui.issues_and_pulls(self.issues_and_prs)
        self.loop.draw_screen()

    def issue_detail(self, issue):
        self.mode = self.ISSUE_DETAIL
        self.ui.issue(issue)
        self.loop.draw_screen()

    def pull_request_detail(self, pr):
        self.mode = self.PR_DETAIL
        self.ui.pull_request(pr)
        self.loop.draw_screen()

    def diff(self, pr):
        self.mode = self.PR_DIFF
        self.ui.diff(pr)
        self.loop.draw_screen()

    def handle_keypress(self, key):
        if key == KEY_OPEN_ISSUE:
            if self.mode is self.ISSUE_LIST:
                issue_text = self.spawn_editor(NEW_ISSUE)

                if issue_text is None:
                    # TODO: cancelled by the user
                    return

                contents = unlines(issue_text)
                title, *body = contents

                if not title:
                    # TODO: incorrect input, at least a title is needed
                    return
                body = lines(body)

                issue = self.repo.create_issue(title=title, body=body)

                if issue:
                    self.issue_detail(issue)
                else:
                    self.issue_list()
        elif key == KEY_CLOSE_ISSUE:
            issue = self.ui.get_issue()

            if not issue:
                return

            self.issues_and_prs.close(issue)

            if self.mode is self.ISSUE_DETAIL:
                self.issue_detail(issue)
        elif key == KEY_REOPEN_ISSUE:
            issue = self.ui.get_issue()

            if issue and is_closed(issue):
                self.issues_and_prs.reopen(issue)

            if self.mode is self.ISSUE_DETAIL:
                self.issue_detail(issue)
        elif key == KEY_BACK:
            if self.mode is self.PR_DIFF:
                pr = self.ui.get_focused_item()
                self.pull_request_detail(pr)
            elif self.mode in [self.ISSUE_DETAIL, self.PR_DETAIL]:
                self.issue_list()
        elif key == KEY_DETAIL:
            if self.mode is self.ISSUE_LIST:
                issue_or_pr = self.ui.get_focused_item()

                if is_issue(issue_or_pr):
                    self.issue_detail(issue_or_pr)
                elif is_pull_request(issue_or_pr):
                    self.pull_request_detail(issue_or_pr)
        elif key == KEY_EDIT:
            item = self.ui.get_focused_item()

            if item is None:
                return

            if is_pull_request(item):
                item = item.issue

            # In case you aren't the owner of the repo, you are only allowed to
            # edit things that you created.
            if self.repo.owner != self.user and item.user != self.user:
                # TODO: beep
                return

            if is_issue(item):
                self.edit_issue(item)
            else:
                self.edit_body(item)
        elif key == KEY_COMMENT:
            item = self.ui.get_issue_or_pr()

            if item is None:
                return

            if is_pull_request(item):
                issue = item.issue
                self.comment_issue(item.issue, pull_request=item)
            else:
                self.comment_issue(item)
        elif key == KEY_DIFF:
            if self.mode is self.PR_DETAIL:
                pr = self.ui.get_focused_item()
                self.diff(pr)
        elif key == KEY_BROWSER:
            item = self.ui.get_focused_item()
            if hasattr(item, '_api'):
                webbrowser.open(item.html_url)
        elif key == KEY_QUIT:
            raise ExitMainLoop

    def edit_issue(self, issue):
        title_and_body = '\n'.join([issue.title, issue.body])
        issue_text = self.spawn_editor(title_and_body)

        if issue_text is None:
            # TODO: cancelled
            return

        contents = unlines(issue_text)
        title, *body = contents

        if not title:
            # TODO: incorrect input, at least a title is needed
            return
        body = lines(body)

        issue.edit(title=title, body=body)

        if self.mode is self.ISSUE_LIST:
            # TODO: focus
            self.issue_list()
        elif self.mode is self.ISSUE_DETAIL:
            self.issue_detail(issue)

    # TODO
    def edit_pull_request(self, pr):
        pass

    def edit_body(self, item):
        text = self.spawn_editor(item.body)

        if text is None:
            # TODO: cancelled
            return

        # TODO: ui must be updated!
        item.edit(text)

    def comment_issue(self, issue, *, pull_request=False):
        issue_thread = format_issue_thread(issue)

        comment_text = self.spawn_editor('\n'.join(issue_thread))

        if comment_text is None:
            # TODO: cancelled
            return

        if not comment_text:
            # TODO: A empty comment is invalid input
            return

        issue.create_comment(comment_text)

        if pull_request:
            self.pull_request_detail(pull_request)
        else:
            self.issue_detail(issue)

    def spawn_editor(self, help_text=None):
        """
        Open a editor with a temporary file containing ``help_text``.

        If the exit code is 0 the text from the file will be returned.

        Otherwise, ``None`` is returned.
        """
        text = '' if help_text is None else help_text

        tmp_file = tempfile.NamedTemporaryFile(mode='w+',
                                            suffix='.markdown',
                                            delete=False)
        tmp_file.write(text)
        tmp_file.close()

        fname = tmp_file.name

        self.loop.screen.stop()

        return_code = subprocess.call([os.getenv('EDITOR', 'vim'), fname])

        self.loop.screen.start()

        if return_code == 0:
            with open(fname, 'r') as f:
                contents = f.read()

        if return_code != 0:
            return None
        else:
            return strip_comments(contents)