Squashed commit of the following:

Updated the supported python versions list.
    Fixed regression in displaying xposts. #173.
    Fixing a few style things.
    Added a more robust test for the tornado handler.
    Trying without pytest-cov
    Updated travis for coverage.
    Remove python 3.2 support because no unicode literals, following what praw supports.
    "Side effect is not iterable."
    Added requirements for travis.
    Renamed travis file correctly.
    Adding test configurations, got tox working.
    Adding vcr cassettes to the repo.
    Renamed requirements files.
    Split up tests and cleaned up test names.
    Tests done, still one failure.
    Treat cassettes as binary to prevent bad merging.
    Fixed a few broken tests.
    Added a timeout to notifications.
    Prepping subreddit page.
    Finished submission page tests.
    Working on submission tests.
    Fixed vcr matching on urls with params, started submission tests.
    Log cleanup.
    Still trying to fix a broken test.
    -Fixed a few pytest bugs and tweaked logging.
    Still working on subscription tests.
    Finished page tests, on to subscription page.
    Finished content tests and starting page tests.
    Added the test refresh-token file to gitignore.
    Moved functional test file out of the repository.
    Continuing work on subreddit content tests.
    Tests now match module names, cassettes are split into individual tests for faster loading.
    Linter fixes.
    Cleanup.
    Added support for nested loaders.
    Added pytest options, starting subreddit content tests.
    Back on track with loader, continuing content tests.
    Finishing submission content tests and discovered snag with loader exception handling.
    VCR up and running, continuing to implement content tests.
    Playing around with vcr.py
    Moved helper functions into terminal and new objects.py
    Fixed a few broken tests.
    Working on navigator tests.
    Reorganizing some things.
    Mocked webbrowser._tryorder for terminal test.
    Completed oauth tests.
    Progress on the oauth tests.
    Working on adding fake tornado request.
    Starting on OAuth tool tests.
    Finished curses helpers tests.
    Still working on curses helpers tests.
    Almost finished with tests on curses helpers.
    Adding tests and working on mocking stdscr.
    Starting to add tests for curses functions.
    Merge branch 'future_work' of https://github.com/michael-lazar/rtv into future_work
    Refactoring controller, still in progress.
    Renamed auth handler.
    Rename CursesHelper to CursesBase.
    Added temporary file with a possible template for func testing.
    Mixup between basename and dirname.
    Merge branch 'future_work' of https://github.com/michael-lazar/rtv into future_work
    py3 compatability for mock.
    Beginning to refactor the curses session.
    Started adding tests, improved unicode handling in the config.
    Cleanup, fixed a few typos.
    Major refactor, almost done!.
    Started a config class.
    Merge branch 'master' into future_work
    The editor now handles unicode characters in all situations.
    Fixed a few typos from previous commits.
    __main__.py formatting.
    Cleaned up history logic and moved to the config file.
This commit is contained in:
Michael Lazar
2015-12-02 22:37:50 -08:00
parent b91bb86e36
commit a7b789bfd9
70 changed files with 42141 additions and 1560 deletions

View File

@@ -1,47 +1,33 @@
import curses
import sys
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import time
import logging
import curses
from . import docs
from .content import SubmissionContent
from .page import BasePage, Navigator, BaseController
from .helpers import open_browser, open_editor
from .curses_helpers import (Color, LoadScreen, get_arrow, get_gold, add_line,
show_notification)
from .docs import COMMENT_FILE
__all__ = ['SubmissionController', 'SubmissionPage']
_logger = logging.getLogger(__name__)
from .page import Page, PageController, logged_in
from .objects import Navigator, Color
from .terminal import Terminal
class SubmissionController(BaseController):
class SubmissionController(PageController):
character_map = {}
class SubmissionPage(BasePage):
class SubmissionPage(Page):
def __init__(self, stdscr, reddit, oauth, url=None, submission=None):
def __init__(self, reddit, term, config, oauth, url=None, submission=None):
super(SubmissionPage, self).__init__(reddit, term, config, oauth)
if url:
self.content = SubmissionContent.from_url(reddit, url, term.loader)
else:
self.content = SubmissionContent(submission, term.loader)
self.controller = SubmissionController(self)
self.loader = LoadScreen(stdscr)
if url:
content = SubmissionContent.from_url(reddit, url, self.loader)
elif submission:
content = SubmissionContent(submission, self.loader)
else:
raise ValueError('Must specify url or submission')
super(SubmissionPage, self).__init__(stdscr, reddit, content, oauth,
page_index=-1)
def loop(self):
"Main control loop"
self.active = True
while self.active:
self.draw()
cmd = self.stdscr.getch()
self.controller.trigger(cmd)
# Start at the submission post, which is indexed as -1
self.nav = Navigator(self.content.get, page_index=-1)
@SubmissionController.register(curses.KEY_RIGHT, 'l', ' ')
def toggle_comment(self):
@@ -50,9 +36,9 @@ class SubmissionPage(BasePage):
current_index = self.nav.absolute_index
self.content.toggle(current_index)
if self.nav.inverted:
# Reset the page so that the bottom is at the cursor position.
# This is a workaround to handle if folding the causes the
# cursor index to go out of bounds.
# Reset the navigator so that the cursor is at the bottom of the
# page. This is a workaround to handle if folding the comment
# causes the cursor index to go out of bounds.
self.nav.page_index, self.nav.cursor_index = current_index, 0
@SubmissionController.register(curses.KEY_LEFT, 'h')
@@ -63,88 +49,93 @@ class SubmissionPage(BasePage):
@SubmissionController.register(curses.KEY_F5, 'r')
def refresh_content(self, order=None):
"Re-download comments reset the page index"
"Re-download comments and reset the page index"
order = order or self.content.order
self.content = SubmissionContent.from_url(
self.reddit, self.content.name, self.loader, order=order)
self.nav = Navigator(self.content.get, page_index=-1)
url = self.content.name
@SubmissionController.register(curses.KEY_ENTER, 10, 'o')
with self.term.loader():
self.content = SubmissionContent.from_url(
self.reddit, url, self.term.loader, order=order)
if not self.term.loader.exception:
self.nav = Navigator(self.content.get, page_index=-1)
@SubmissionController.register(curses.KEY_ENTER, Terminal.RETURN, 'o')
def open_link(self):
"Open the current submission page with the webbrowser"
"Open the selected item with the webbrowser"
data = self.content.get(self.nav.absolute_index)
url = data.get('permalink')
if url:
open_browser(url)
self.term.open_browser(url)
else:
curses.flash()
self.term.flash()
@SubmissionController.register('c')
@logged_in
def add_comment(self):
"""
Add a top-level comment if the submission is selected, or reply to the
selected comment.
"""
Submit a reply to the selected item.
if not self.reddit.is_oauth_session():
show_notification(self.stdscr, ['Not logged in'])
return
Selected item:
Submission - add a top level comment
Comment - add a comment reply
"""
data = self.content.get(self.nav.absolute_index)
if data['type'] == 'Submission':
content = data['text']
body = data['text']
reply = data['object'].add_comment
elif data['type'] == 'Comment':
content = data['body']
body = data['body']
reply = data['object'].reply
else:
curses.flash()
self.term.flash()
return
# Comment out every line of the content
content = '\n'.join(['# |' + line for line in content.split('\n')])
comment_info = COMMENT_FILE.format(
# Construct the text that will be displayed in the editor file.
# The post body will be commented out and added for reference
lines = ['# |' + line for line in body.split('\n')]
content = '\n'.join(lines)
comment_info = docs.COMMENT_FILE.format(
author=data['author'],
type=data['type'].lower(),
content=content)
comment_text = open_editor(comment_info)
if not comment_text:
show_notification(self.stdscr, ['Aborted'])
comment = self.term.open_editor(comment_info)
if not comment:
self.term.show_notification('Aborted')
return
with self.safe_call as s:
with self.loader(message='Posting', delay=0):
if data['type'] == 'Submission':
data['object'].add_comment(comment_text)
else:
data['object'].reply(comment_text)
time.sleep(2.0)
s.catch = False
with self.term.loader(message='Posting', delay=0):
reply(comment)
# Give reddit time to process the submission
time.sleep(2.0)
if not self.term.loader.exception:
self.refresh_content()
@SubmissionController.register('d')
@logged_in
def delete_comment(self):
"Delete a comment as long as it is not the current submission"
if self.nav.absolute_index != -1:
self.delete()
self.delete_item()
else:
curses.flash()
self.term.flash()
def draw_item(self, win, data, inverted=False):
def _draw_item(self, win, data, inverted=False):
if data['type'] == 'MoreComments':
return self.draw_more_comments(win, data)
return self._draw_more_comments(win, data)
elif data['type'] == 'HiddenComment':
return self.draw_more_comments(win, data)
return self._draw_more_comments(win, data)
elif data['type'] == 'Comment':
return self.draw_comment(win, data, inverted=inverted)
return self._draw_comment(win, data, inverted=inverted)
else:
return self.draw_submission(win, data)
return self._draw_submission(win, data)
@staticmethod
def draw_comment(win, data, inverted=False):
def _draw_comment(self, win, data, inverted=False):
n_rows, n_cols = win.getmaxyx()
n_cols -= 1
@@ -158,73 +149,65 @@ class SubmissionPage(BasePage):
attr = curses.A_BOLD
attr |= (Color.BLUE if not data['is_author'] else Color.GREEN)
add_line(win, u'{author} '.format(**data), row, 1, attr)
self.term.add_line(win, '{author} '.format(**data), row, 1, attr)
if data['flair']:
attr = curses.A_BOLD | Color.YELLOW
add_line(win, u'{flair} '.format(**data), attr=attr)
self.term.add_line(win, '{flair} '.format(**data), attr=attr)
text, attr = get_arrow(data['likes'])
add_line(win, text, attr=attr)
add_line(win, u' {score} {created} '.format(**data))
text, attr = self.term.get_arrow(data['likes'])
self.term.add_line(win, text, attr=attr)
self.term.add_line(win, ' {score} {created} '.format(**data))
if data['gold']:
text, attr = get_gold()
add_line(win, text, attr=attr)
text, attr = self.term.guilded
self.term.add_line(win, text, attr=attr)
for row, text in enumerate(data['split_body'], start=offset + 1):
for row, text in enumerate(data['split_body'], start=offset+1):
if row in valid_rows:
add_line(win, text, row, 1)
self.term.add_line(win, text, row, 1)
# Unfortunately vline() doesn't support custom color so we have to
# build it one segment at a time.
attr = Color.get_level(data['level'])
x = 0
for y in range(n_rows):
x = 0
# http://bugs.python.org/issue21088
if (sys.version_info.major,
sys.version_info.minor,
sys.version_info.micro) == (3, 4, 0):
x, y = y, x
self.term.addch(win, y, x, curses.ACS_VLINE, attr)
win.addch(y, x, curses.ACS_VLINE, attr)
return attr | curses.ACS_VLINE
return (attr | curses.ACS_VLINE)
@staticmethod
def draw_more_comments(win, data):
def _draw_more_comments(self, win, data):
n_rows, n_cols = win.getmaxyx()
n_cols -= 1
add_line(win, u'{body}'.format(**data), 0, 1)
add_line(win, u' [{count}]'.format(**data), attr=curses.A_BOLD)
self.term.add_line(win, '{body}'.format(**data), 0, 1)
self.term.add_line(win, ' [{count}]'.format(**data), attr=curses.A_BOLD)
attr = Color.get_level(data['level'])
win.addch(0, 0, curses.ACS_VLINE, attr)
self.term.addch(win, 0, 0, curses.ACS_VLINE, attr)
return (attr | curses.ACS_VLINE)
return attr | curses.ACS_VLINE
@staticmethod
def draw_submission(win, data):
def _draw_submission(self, win, data):
n_rows, n_cols = win.getmaxyx()
n_cols -= 3 # one for each side of the border + one for offset
for row, text in enumerate(data['split_title'], start=1):
add_line(win, text, row, 1, curses.A_BOLD)
self.term.add_line(win, text, row, 1, curses.A_BOLD)
row = len(data['split_title']) + 1
attr = curses.A_BOLD | Color.GREEN
add_line(win, u'{author}'.format(**data), row, 1, attr)
self.term.add_line(win, '{author}'.format(**data), row, 1, attr)
attr = curses.A_BOLD | Color.YELLOW
if data['flair']:
add_line(win, u' {flair}'.format(**data), attr=attr)
add_line(win, u' {created} {subreddit}'.format(**data))
self.term.add_line(win, ' {flair}'.format(**data), attr=attr)
self.term.add_line(win, ' {created} {subreddit}'.format(**data))
row = len(data['split_title']) + 2
attr = curses.A_UNDERLINE | Color.BLUE
add_line(win, u'{url}'.format(**data), row, 1, attr)
self.term.add_line(win, '{url}'.format(**data), row, 1, attr)
offset = len(data['split_title']) + 3
# Cut off text if there is not enough room to display the whole post
@@ -235,20 +218,20 @@ class SubmissionPage(BasePage):
split_text.append('(Not enough space to display)')
for row, text in enumerate(split_text, start=offset):
add_line(win, text, row, 1)
self.term.add_line(win, text, row, 1)
row = len(data['split_title']) + len(split_text) + 3
add_line(win, u'{score} '.format(**data), row, 1)
text, attr = get_arrow(data['likes'])
add_line(win, text, attr=attr)
add_line(win, u' {comments} '.format(**data))
self.term.add_line(win, '{score} '.format(**data), row, 1)
text, attr = self.term.get_arrow(data['likes'])
self.term.add_line(win, text, attr=attr)
self.term.add_line(win, ' {comments} '.format(**data))
if data['gold']:
text, attr = get_gold()
add_line(win, text, attr=attr)
text, attr = self.term.gold
self.term.add_line(win, text, attr=attr)
if data['nsfw']:
text, attr = 'NSFW', (curses.A_BOLD | Color.RED)
add_line(win, text, attr=attr)
self.term.add_line(win, text, attr=attr)
win.border()