From 7830ed2a48cac7d72383c63cca7fd372e27f355f Mon Sep 17 00:00:00 2001 From: user Date: Mon, 27 Jun 2016 13:05:07 -0500 Subject: [PATCH 01/19] Feature: view user pages and domain listings --- rtv/content.py | 97 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 32 deletions(-) diff --git a/rtv/content.py b/rtv/content.py index 8ca0324..1bb53b9 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -6,7 +6,7 @@ from datetime import datetime import six import praw -from praw.errors import InvalidSubreddit +from praw.errors import InvalidSubreddit, NotFound from kitchen.text.display import wrap from . import exceptions @@ -349,10 +349,13 @@ class SubredditContent(Content): list for repeat access. """ - def __init__(self, name, submissions, loader, order=None): + def __init__(self, name, submissions, loader, order=None, listing='r', + period=None): + self.listing = listing self.name = name self.order = order + self.period = period self._loader = loader self._submissions = submissions self._submission_data = [] @@ -367,52 +370,80 @@ class SubredditContent(Content): raise exceptions.SubredditError('No submissions') @classmethod - def from_name(cls, reddit, name, loader, order=None, query=None): + def from_name(cls, reddit, name, loader, order=None, query=None, + listing='r', period=None): # Strip leading and trailing backslashes - name = name.strip(' /') - if name.startswith('r/'): - name = name[2:] - - # If the order is not given explicitly, it will be searched for and - # stripped out of the subreddit name e.g. python/new. - if '/' in name: - name, name_order = name.split('/') + name = name.strip(' /').split('/') + if name[0] in ['r', 'u', 'user', 'domain']: + listing, *name = name + if len(name) > 1: + name, name_order = name order = order or name_order - display_name = '/r/{0}'.format(name) + else: + name = name[0] + listing = 'u' if name == 'me' else listing + display_name = '/{0}/{1}'.format(listing, name) - if order not in ['hot', 'top', 'rising', 'new', 'controversial', None]: + time = {t: '_from_' + t for t in ['all', 'day', 'hour', + 'month', 'week', 'year']} + time[None] = '' + + if period not in time.keys(): + raise exceptions.SubredditError('Unrecognized period "%s"' + % period) + + elif order not in ['hot', 'top', 'rising', 'new', + 'controversial', None]: raise exceptions.SubredditError('Unrecognized order "%s"' % order) - if name == 'me': - if not reddit.is_oauth_session(): - raise exceptions.AccountError('Not logged in') - elif order: - submissions = reddit.user.get_submitted(sort=order) - else: - submissions = reddit.user.get_submitted() + if query: + loc = None + if listing == 'r' and name != 'front': + loc = name - elif query: - if name == 'front': - submissions = reddit.search(query, subreddit=None, sort=order) - else: - submissions = reddit.search(query, subreddit=name, sort=order) + elif listing == 'domain': + query = 'site:{0} {1}'.format(name, query) - else: + elif listing in ['u', 'user']: + query = 'author:{0} {1}'.format(name, query) + + submissions = reddit.search(query, subreddit=loc, sort=order, + period=period) + + elif listing == 'domain': + submissions = reddit.get_domain_listing(name, + sort=(order or 'hot'), period=period) + + elif listing in ['u', 'user']: + if name == 'me': + if not reddit.is_oauth_session(): + raise exceptions.AccountError('Not logged in') + else: + submissions = reddit.user.get_submitted( \ + sort=(order or 'new')) + else: + redditor = reddit.get_redditor(name) + submissions = redditor.get_submitted(sort=(order or 'new'), + time=(period or 'all')) + + elif listing == 'r': if name == '': # Praw does not correctly handle empty strings # https://github.com/praw-dev/praw/issues/615 raise InvalidSubreddit() - if name == 'front': + elif name == 'front': dispatch = { None: reddit.get_front_page, 'hot': reddit.get_front_page, - 'top': reddit.get_top, + 'top': eval('reddit.get_top' + time[period]), 'rising': reddit.get_rising, 'new': reddit.get_new, - 'controversial': reddit.get_controversial, + 'controversial': eval('reddit.get_controversial' \ + + time[period]), } + else: subreddit = reddit.get_subreddit(name) # For special subreddits like /r/random we want to replace the @@ -421,14 +452,16 @@ class SubredditContent(Content): dispatch = { None: subreddit.get_hot, 'hot': subreddit.get_hot, - 'top': subreddit.get_top, + 'top': eval('subreddit.get_top' + time[period]), 'rising': subreddit.get_rising, 'new': subreddit.get_new, - 'controversial': subreddit.get_controversial, + 'controversial': eval('subreddit.get_controversial' \ + + time[period]), } submissions = dispatch[order](limit=None) - return cls(display_name, submissions, loader, order=order) + return cls(display_name, submissions, loader, order=order, + listing=listing, period=period) def get(self, index, n_cols=70): """ From a9c29bbd259990168cdc6c4163a5e18ab2df005c Mon Sep 17 00:00:00 2001 From: woorst Date: Mon, 27 Jun 2016 17:47:59 -0500 Subject: [PATCH 02/19] Remove use of python 3 specific tuple unpacking --- rtv/content.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtv/content.py b/rtv/content.py index 1bb53b9..ad6c9a8 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -6,7 +6,7 @@ from datetime import datetime import six import praw -from praw.errors import InvalidSubreddit, NotFound +from praw.errors import InvalidSubreddit from kitchen.text.display import wrap from . import exceptions @@ -376,7 +376,7 @@ class SubredditContent(Content): # Strip leading and trailing backslashes name = name.strip(' /').split('/') if name[0] in ['r', 'u', 'user', 'domain']: - listing, *name = name + listing, name = name[0], name[1:] if len(name) > 1: name, name_order = name order = order or name_order From 07bf94c65cf1d39ea892e136d8d771a83ed09a98 Mon Sep 17 00:00:00 2001 From: woorst Date: Tue, 28 Jun 2016 07:32:51 -0500 Subject: [PATCH 03/19] Add tests for tests_content_subreddit_from_name --- tests/test_content.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_content.py b/tests/test_content.py index 41a0e74..bc9ee2e 100644 --- a/tests/test_content.py +++ b/tests/test_content.py @@ -240,6 +240,16 @@ def test_content_subreddit_from_name(reddit, terminal): assert content.name == '/r/python' assert content.order is None + name = '/domain/python.org' + content = SubredditContent.from_name(reddit, name, terminal.loader) + assert content.name == '/domain/python.org' + assert content.order is None + + name = '/user/spez' + content = SubredditContent.from_name(reddit, name, terminal.loader) + assert content.name == '/user/spez' + assert content.order is None + # Can submit without the /r/ and with the order in the name name = 'python/top/' content = SubredditContent.from_name(reddit, name, terminal.loader) From 3b8b38efe319137e1c9f79ae2bd49cbecb6a2536 Mon Sep 17 00:00:00 2001 From: woorst Date: Tue, 28 Jun 2016 16:11:44 -0500 Subject: [PATCH 04/19] More robust checking for valid subreddit name; Can browse public multireddits now --- rtv/content.py | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/rtv/content.py b/rtv/content.py index ad6c9a8..e1a2d02 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import re from datetime import datetime +from itertools import groupby import six import praw @@ -373,15 +374,27 @@ class SubredditContent(Content): def from_name(cls, reddit, name, loader, order=None, query=None, listing='r', period=None): - # Strip leading and trailing backslashes - name = name.strip(' /').split('/') - if name[0] in ['r', 'u', 'user', 'domain']: - listing, name = name[0], name[1:] - if len(name) > 1: - name, name_order = name - order = order or name_order + # Strip leading, trailing and redundant backslashes + n = '' + n = ''.join([n + ''.join(list(g)) if k != '/' else '/' \ + for k, g in groupby(name)]).strip(' /') + name_list = n.split('/') + name_order = None + if name_list[0] in ['r', 'u', 'user', 'domain'] and len(name_list) > 1: + listing, name_list = name_list[0], name_list[1:] + if len(name_list) == 2: + name, name_order = name_list + elif len(name_list) in [3, 4] and name_list[1] == 'm': + name_order = name_list[3] if name_list[3:4] else name_order + name = '{0}/m/{2}'.format(*name_list) + elif len(name_list) == 1 and name_list[0] != '': + name = name_list[0] else: - name = name[0] + # Praw does not correctly handle empty strings + # https://github.com/praw-dev/praw/issues/615 + raise InvalidSubreddit() + + order = order or name_order listing = 'u' if name == 'me' else listing display_name = '/{0}/{1}'.format(listing, name) @@ -416,7 +429,12 @@ class SubredditContent(Content): sort=(order or 'hot'), period=period) elif listing in ['u', 'user']: - if name == 'me': + if '/m/' in name: + multireddit = reddit.get_multireddit(*name.split('/')[::2]) + submissions = eval('multireddit.get_{0}{1}(limit=None)' \ + .format((order or 'top'), time[period])) + + elif name == 'me': if not reddit.is_oauth_session(): raise exceptions.AccountError('Not logged in') else: @@ -428,12 +446,7 @@ class SubredditContent(Content): time=(period or 'all')) elif listing == 'r': - if name == '': - # Praw does not correctly handle empty strings - # https://github.com/praw-dev/praw/issues/615 - raise InvalidSubreddit() - - elif name == 'front': + if name == 'front': dispatch = { None: reddit.get_front_page, 'hot': reddit.get_front_page, From 377920820015e660fc02b72da2070d5138ff604d Mon Sep 17 00:00:00 2001 From: woorst Date: Tue, 28 Jun 2016 16:22:09 -0500 Subject: [PATCH 05/19] Change default multireddit sort to 'hot' --- rtv/content.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtv/content.py b/rtv/content.py index e1a2d02..a736481 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -432,7 +432,7 @@ class SubredditContent(Content): if '/m/' in name: multireddit = reddit.get_multireddit(*name.split('/')[::2]) submissions = eval('multireddit.get_{0}{1}(limit=None)' \ - .format((order or 'top'), time[period])) + .format((order or 'hot'), time[period])) elif name == 'me': if not reddit.is_oauth_session(): From 521ef9ec535591b6324a2301e425f3c31eefd846 Mon Sep 17 00:00:00 2001 From: woorst Date: Tue, 28 Jun 2016 16:35:31 -0500 Subject: [PATCH 06/19] Apply time period only to appropriatly sorted multireddits --- rtv/content.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/rtv/content.py b/rtv/content.py index a736481..5c84bc4 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -374,7 +374,7 @@ class SubredditContent(Content): def from_name(cls, reddit, name, loader, order=None, query=None, listing='r', period=None): - # Strip leading, trailing and redundant backslashes + # Strip leading, trailing, and redundant backslashes n = '' n = ''.join([n + ''.join(list(g)) if k != '/' else '/' \ for k, g in groupby(name)]).strip(' /') @@ -431,8 +431,12 @@ class SubredditContent(Content): elif listing in ['u', 'user']: if '/m/' in name: multireddit = reddit.get_multireddit(*name.split('/')[::2]) - submissions = eval('multireddit.get_{0}{1}(limit=None)' \ - .format((order or 'hot'), time[period])) + if order in ['top', 'controversial']: + submissions = eval('multireddit.get_{0}{1}(limit=None)' \ + .format((order), time[period])) + else: + submissions = eval('multireddit.get_{0}(limit=None)' \ + .format((order or 'hot'))) elif name == 'me': if not reddit.is_oauth_session(): From f9e9d8156332f174bc0c324f425bf55187fdbe69 Mon Sep 17 00:00:00 2001 From: woorst Date: Tue, 28 Jun 2016 16:46:02 -0500 Subject: [PATCH 07/19] Change prompt text --- rtv/subreddit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtv/subreddit.py b/rtv/subreddit.py index a03cb1a..85b8a3a 100644 --- a/rtv/subreddit.py +++ b/rtv/subreddit.py @@ -69,7 +69,7 @@ class SubredditPage(Page): def prompt_subreddit(self): "Open a prompt to navigate to a different subreddit" - name = self.term.prompt_input('Enter Subreddit: /r/') + name = self.term.prompt_input('Enter reddit page: ') if name is not None: self.refresh_content(order='ignore', name=name) @@ -218,4 +218,4 @@ class SubredditPage(Page): self.term.add_line(win, text, attr=Color.YELLOW) if data['flair']: text = ' {flair}'.format(**data) - self.term.add_line(win, text, attr=Color.RED) \ No newline at end of file + self.term.add_line(win, text, attr=Color.RED) From b54b4210dd55682babc502ae1e8911649a7e12ba Mon Sep 17 00:00:00 2001 From: woorst Date: Tue, 28 Jun 2016 16:52:59 -0500 Subject: [PATCH 08/19] Add test for from_name for multireddits --- tests/test_content.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_content.py b/tests/test_content.py index bc9ee2e..afbc256 100644 --- a/tests/test_content.py +++ b/tests/test_content.py @@ -250,6 +250,11 @@ def test_content_subreddit_from_name(reddit, terminal): assert content.name == '/user/spez' assert content.order is None + name = '/u/multi-mod/m/art' + content = SubredditContent.from_name(reddit, name, terminal.loader) + assert content.name == '/u/multi-mod/m/art' + assert content.order is None + # Can submit without the /r/ and with the order in the name name = 'python/top/' content = SubredditContent.from_name(reddit, name, terminal.loader) From 9c964b7069dd08e8aea012b1465130e9492f7fb1 Mon Sep 17 00:00:00 2001 From: woorst Date: Wed, 29 Jun 2016 00:15:03 -0500 Subject: [PATCH 09/19] Can browse through list of subscribed multireddits --- rtv/content.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ rtv/rtv.cfg | 1 + rtv/subreddit.py | 22 +++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/rtv/content.py b/rtv/content.py index 5c84bc4..978efd2 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -189,6 +189,20 @@ class Content(object): data['title'] = subscription.title return data + @staticmethod + def strip_praw_multireddit(multireddit): + """ + Parse through a multireddits and return a dict with data ready to be + displayed through the terminal. + """ + + data = {} + data['object'] = multireddit + data['type'] = 'Multireddit' + data['name'] = multireddit.path + data['title'] = multireddit.description_md + return data + @staticmethod def humanize_timestamp(utc_timestamp, verbose=False): """ @@ -560,3 +574,52 @@ class SubscriptionContent(Content): data['offset'] = 0 return data + + +class MultiredditContent(Content): + + def __init__(self, multireddits, loader): + + self.name = "Multireddits" + self.order = None + self._loader = loader + self._multireddits = multireddits + self._multireddit_data = [] + + try: + self.get(0) + except IndexError: + raise exceptions.SubscriptionError('No multireddits') + + @classmethod + def from_user(cls, reddit, multireddits, loader): + multireddits = (m for m in multireddits) + return cls(multireddits, loader) + + def get(self, index, n_cols=70): + """ + Grab the `i`th subscription, with the title field formatted to fit + inside of a window of width `n_cols` + """ + + if index < 0: + raise IndexError + + while index >= len(self._multireddit_data): + try: + with self._loader('Loading multireddits'): + multireddit = next(self._multireddits) + if self._loader.exception: + raise IndexError + except StopIteration: + raise IndexError + else: + data = self.strip_praw_multireddit(multireddit) + self._multireddit_data.append(data) + + data = self._multireddit_data[index] + data['split_title'] = self.wrap_text(data['title'], width=n_cols) + data['n_rows'] = len(data['split_title']) + 1 + data['offset'] = 0 + + return data diff --git a/rtv/rtv.cfg b/rtv/rtv.cfg index b7ad320..f5fcb0e 100644 --- a/rtv/rtv.cfg +++ b/rtv/rtv.cfg @@ -120,6 +120,7 @@ SUBREDDIT_POST = c SUBREDDIT_OPEN = l, SUBREDDIT_OPEN_IN_BROWSER = o, , , SUBREDDIT_OPEN_SUBSCRIPTIONS = s +MULTIREDDIT_OPEN_SUBSCRIPTIONS = S ; Subscription page SUBSCRIPTION_SELECT = l, , , diff --git a/rtv/subreddit.py b/rtv/subreddit.py index 85b8a3a..eea3cb0 100644 --- a/rtv/subreddit.py +++ b/rtv/subreddit.py @@ -9,6 +9,7 @@ from .content import SubredditContent from .page import Page, PageController, logged_in from .objects import Navigator, Color, Command from .submission import SubmissionPage +from .multireddits import MultiredditPage from .subscription import SubscriptionPage from .exceptions import TemporaryFileError @@ -168,6 +169,27 @@ class SubredditPage(Page): self.refresh_content(order='ignore', name=page.subreddit_data['name']) + @SubredditController.register(Command('MULTIREDDIT_OPEN_SUBSCRIPTIONS')) + @logged_in + def open_multireddit_subscriptions(self): + "Open user multireddit subscriptions page" + + with self.term.loader('Loading multireddits'): + page = MultiredditPage( + self.reddit, self.reddit.get_my_multireddits(), + self.term, self.config) + if self.term.loader.exception: + return + + page.loop() + + # When the user has chosen a subreddit in the subscriptions list, + # refresh content with the selected subreddit + if page.multireddit_data is not None: + self.refresh_content(order='ignore', + name=page.multireddit_data['name']) + + def _draw_item(self, win, data, inverted): n_rows, n_cols = win.getmaxyx() From 0d807fd6eb5c64da2c738d22ce57c1830a07e88b Mon Sep 17 00:00:00 2001 From: woorst Date: Wed, 29 Jun 2016 00:15:30 -0500 Subject: [PATCH 10/19] Can browse through list of subscribed multireddits --- rtv/multireddits.py | 74 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 rtv/multireddits.py diff --git a/rtv/multireddits.py b/rtv/multireddits.py new file mode 100644 index 0000000..d1f96af --- /dev/null +++ b/rtv/multireddits.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import curses + +from .page import Page, PageController +from .content import MultiredditContent +from .objects import Color, Navigator, Command + + +class SubscriptionController(PageController): + character_map = {} + + +class MultiredditPage(Page): + + def __init__(self, reddit, multireddits, term, config): + super(MultiredditPage, self).__init__(reddit, term, config, None) + + self.controller = SubscriptionController(self, keymap=config.keymap) + self.content = MultiredditContent.from_user(reddit, multireddits, + term.loader) + self.nav = Navigator(self.content.get) + self.multireddit_data = None + + @SubscriptionController.register(Command('REFRESH')) + def refresh_content(self, order=None, name=None): + "Re-download all subscriptions and reset the page index" + + # reddit.get_multireddits() does not support sorting by order + if order: + self.term.flash() + return + + with self.term.loader(): + self.content = MultiredditContent.from_user(self.reddit, + self.multireddits, self.term.loader) + if not self.term.loader.exception: + self.nav = Navigator(self.content.get) + + @SubscriptionController.register(Command('SUBSCRIPTION_SELECT')) + def select_multireddit(self): + "Store the selected multireddit and return to the subreddit page" + + self.multireddit_data = self.content.get(self.nav.absolute_index) + self.active = False + + @SubscriptionController.register(Command('SUBSCRIPTION_EXIT')) + def close_multireddit_subscriptions(self): + "Close multireddits and return to the subreddit page" + + self.active = False + + def _draw_banner(self): + # Subscriptions can't be sorted, so disable showing the order menu + pass + + def _draw_item(self, win, data, inverted): + n_rows, n_cols = win.getmaxyx() + n_cols -= 1 # Leave space for the cursor in the first column + + # Handle the case where the window is not large enough to fit the data. + valid_rows = range(0, n_rows) + offset = 0 if not inverted else -(data['n_rows'] - n_rows) + + row = offset + if row in valid_rows: + attr = curses.A_BOLD | Color.YELLOW + self.term.add_line(win, '{name}'.format(**data), row, 1, attr) + + row = offset + 1 + for row, text in enumerate(data['split_title'], start=row): + if row in valid_rows: + self.term.add_line(win, text, row, 1) From e2352c1db40dadd141c828f95e4b9f9b7bc6d299 Mon Sep 17 00:00:00 2001 From: woorst Date: Wed, 29 Jun 2016 12:37:26 -0500 Subject: [PATCH 11/19] Add documentation of new features --- CONTROLS.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CONTROLS.rst b/CONTROLS.rst index f5075f4..1c8ce72 100644 --- a/CONTROLS.rst +++ b/CONTROLS.rst @@ -30,6 +30,7 @@ Once you are logged in your username will appear in the top-right corner of the :``d``: Delete an existing post or comment :``i``: Display new messages prompt :``s``: View a list of subscribed subreddits +:``S``: View a list of subscribed multireddits -------------- Subreddit Mode @@ -44,11 +45,15 @@ In subreddit mode you can browse through the top submissions on either the front The ``/`` prompt accepts subreddits in the following formats +* ``python`` * ``/r/python`` * ``/r/python/new`` * ``/r/python+linux`` supports multireddits * ``/r/front`` will redirect to the front page -* ``/r/me`` will display your submissions +* ``/u/me`` will display your submissions +* ``/u/spez`` will display submissions from any other user +* ``/u/multi-mod/m/android`` display a multireddit curated by a user +* ``/domain/python.org`` dispaly submissions to stated domain --------------- Submission Mode From b5ff999b7c33e129ae5ba1487d0eb2394e3d088d Mon Sep 17 00:00:00 2001 From: woorst Date: Sat, 2 Jul 2016 22:28:48 -0500 Subject: [PATCH 12/19] Update controls in docs and other files --- CONTROLS.rst | 2 +- rtv/docs.py | 1 + rtv/rtv.cfg | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CONTROLS.rst b/CONTROLS.rst index 1c8ce72..37e9ed3 100644 --- a/CONTROLS.rst +++ b/CONTROLS.rst @@ -53,7 +53,7 @@ The ``/`` prompt accepts subreddits in the following formats * ``/u/me`` will display your submissions * ``/u/spez`` will display submissions from any other user * ``/u/multi-mod/m/android`` display a multireddit curated by a user -* ``/domain/python.org`` dispaly submissions to stated domain +* ``/domain/python.org`` display submissions to stated domain --------------- Submission Mode diff --git a/rtv/docs.py b/rtv/docs.py index bb9eab8..62c6e72 100644 --- a/rtv/docs.py +++ b/rtv/docs.py @@ -34,6 +34,7 @@ HELP = """ `d` : Delete an existing post or comment `i` : Display new messages prompt `s` : Open/close subscribed subreddits list + `S` : Open/close subscribed multireddits list [Subreddit Mode] `l` or `RIGHT` : Enter the selected submission diff --git a/rtv/rtv.cfg b/rtv/rtv.cfg index f5fcb0e..21665b5 100644 --- a/rtv/rtv.cfg +++ b/rtv/rtv.cfg @@ -124,4 +124,4 @@ MULTIREDDIT_OPEN_SUBSCRIPTIONS = S ; Subscription page SUBSCRIPTION_SELECT = l, , , -SUBSCRIPTION_EXIT = h, s, , +SUBSCRIPTION_EXIT = h, s, S, , From fc9198a84aac1eefbdd7e64a4f3e8a3c18793c00 Mon Sep 17 00:00:00 2001 From: woorst Date: Sat, 16 Jul 2016 19:07:36 -0500 Subject: [PATCH 13/19] Make search work for different types of reddit pages --- rtv/content.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/rtv/content.py b/rtv/content.py index 0d7b460..287681a 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -431,19 +431,19 @@ class SubredditContent(Content): raise exceptions.SubredditError('Unrecognized order "%s"' % order) if query: - loc = None - if listing == 'r' and name != 'front': - loc = name - - elif listing == 'domain': - query = 'site:{0} {1}'.format(name, query) - - elif listing in ['u', 'user']: + if listing in ['u', 'user'] and '/m/' not in name: + reddit.config.API_PATHS['search'] = 'r/{subreddit}/search' query = 'author:{0} {1}'.format(name, query) + location = None + else: + reddit.config.API_PATHS['search'] = \ + '{}/{{subreddit}}/search'.format(listing) + location = None if name == 'front' else name - submissions = reddit.search(query, subreddit=loc, sort=order, + submissions = reddit.search(query, subreddit=location, sort=order, period=period) + elif listing == 'domain': submissions = reddit.get_domain_listing(name, sort=(order or 'hot'), period=period) From 3018dec46ed33520ee15d9108167654454fa13ea Mon Sep 17 00:00:00 2001 From: woorst Date: Sat, 16 Jul 2016 19:18:33 -0500 Subject: [PATCH 14/19] subreddit name parsing code more readable --- rtv/content.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rtv/content.py b/rtv/content.py index 287681a..a4111be 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -395,10 +395,7 @@ class SubredditContent(Content): listing='r', period=None): # Strip leading, trailing, and redundant backslashes - n = '' - n = ''.join([n + ''.join(list(g)) if k != '/' else '/' \ - for k, g in groupby(name)]).strip(' /') - name_list = n.split('/') + name_list = [seg for seg in name.strip(' /').split('/') if seg] name_order = None if name_list[0] in ['r', 'u', 'user', 'domain'] and len(name_list) > 1: listing, name_list = name_list[0], name_list[1:] From 0149b00f427a66eda1472a3e3811389b755e7f04 Mon Sep 17 00:00:00 2001 From: woorst Date: Sat, 16 Jul 2016 19:31:44 -0500 Subject: [PATCH 15/19] Search works for logged in user's submissions --- rtv/content.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rtv/content.py b/rtv/content.py index a4111be..adddece 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -430,7 +430,10 @@ class SubredditContent(Content): if query: if listing in ['u', 'user'] and '/m/' not in name: reddit.config.API_PATHS['search'] = 'r/{subreddit}/search' - query = 'author:{0} {1}'.format(name, query) + if name == 'me' and reddit.is_oauth_session(): + query = 'author:{0} {1}'.format(reddit.get_me().name, query) + else: + query = 'author:{0} {1}'.format(name, query) location = None else: reddit.config.API_PATHS['search'] = \ From 04e2ccc43e3bb96ea396b5c43ed0e8f8ea8f13ab Mon Sep 17 00:00:00 2001 From: woorst Date: Sat, 16 Jul 2016 23:49:26 -0500 Subject: [PATCH 16/19] additional tests for from_name method --- tests/test_content.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_content.py b/tests/test_content.py index afbc256..7fc624c 100644 --- a/tests/test_content.py +++ b/tests/test_content.py @@ -291,6 +291,7 @@ def test_content_subreddit_from_name(reddit, terminal): # Queries SubredditContent.from_name(reddit, 'front', terminal.loader, query='pea') SubredditContent.from_name(reddit, 'python', terminal.loader, query='pea') + SubredditContent.from_name(reddit, 'me', terminal.loader, query='pea') def test_content_subreddit_multireddit(reddit, terminal): From e949474570485148d01988f7c308b82d8c4ab298 Mon Sep 17 00:00:00 2001 From: woorst Date: Sun, 17 Jul 2016 16:17:50 -0500 Subject: [PATCH 17/19] Generalize SubscriptionPage to handle lists of reddits --- rtv/content.py | 112 +++++++++-------------------------- rtv/exceptions.py | 6 +- rtv/reddits.py | 74 +++++++++++++++++++++++ rtv/subreddit.py | 23 ++++--- tests/conftest.py | 13 ++-- tests/test_reddits.py | 135 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 257 insertions(+), 106 deletions(-) create mode 100644 rtv/reddits.py create mode 100644 tests/test_reddits.py diff --git a/rtv/content.py b/rtv/content.py index adddece..1eee21e 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -182,31 +182,22 @@ class Content(object): return data @staticmethod - def strip_praw_subscription(subscription): + def strip_praw_reddit(reddit): """ - Parse through a subscription and return a dict with data ready to be + Parse through a reddit object and return a dict with data ready to be displayed through the terminal. """ data = {} - data['object'] = subscription - data['type'] = 'Subscription' - data['name'] = "/r/" + subscription.display_name - data['title'] = subscription.title - return data - - @staticmethod - def strip_praw_multireddit(multireddit): - """ - Parse through a multireddits and return a dict with data ready to be - displayed through the terminal. - """ - - data = {} - data['object'] = multireddit - data['type'] = 'Multireddit' - data['name'] = multireddit.path - data['title'] = multireddit.description_md + data['object'] = reddit + if isinstance(reddit, praw.objects.Subreddit): + data['type'] = 'Subreddit' + data['name'] = "/r/" + reddit.display_name + data['title'] = reddit.title + elif isinstance(reddit, praw.objects.Multireddit): + data['type'] = 'Multireddit' + data['name'] = reddit.path + data['title'] = reddit.description_md return data @staticmethod @@ -533,97 +524,48 @@ class SubredditContent(Content): return data -class SubscriptionContent(Content): +class ListRedditsContent(Content): - def __init__(self, subscriptions, loader): + def __init__(self, name, reddits, loader): - self.name = "Subscriptions" + self.name = name self.order = None self._loader = loader - self._subscriptions = subscriptions - self._subscription_data = [] + self._reddits = reddits + self._reddit_data = [] try: self.get(0) except IndexError: - raise exceptions.SubscriptionError('No subscriptions') + raise exceptions.ListRedditsError('No {}'.format(self.name)) @classmethod - def from_user(cls, reddit, loader): - subscriptions = reddit.get_my_subreddits(limit=None) - return cls(subscriptions, loader) + def from_user(cls, name, reddits, loader): + reddits = (r for r in reddits) + return cls(name, reddits, loader) def get(self, index, n_cols=70): """ - Grab the `i`th subscription, with the title field formatted to fit + Grab the `i`th reddit, with the title field formatted to fit inside of a window of width `n_cols` """ if index < 0: raise IndexError - while index >= len(self._subscription_data): + while index >= len(self._reddit_data): try: - with self._loader('Loading subscriptions'): - subscription = next(self._subscriptions) + with self._loader('Loading {}'.format(self.name)): + reddit = next(self._reddits) if self._loader.exception: raise IndexError except StopIteration: raise IndexError else: - data = self.strip_praw_subscription(subscription) - self._subscription_data.append(data) + data = self.strip_praw_reddit(reddit) + self._reddit_data.append(data) - data = self._subscription_data[index] - data['split_title'] = self.wrap_text(data['title'], width=n_cols) - data['n_rows'] = len(data['split_title']) + 1 - data['offset'] = 0 - - return data - - -class MultiredditContent(Content): - - def __init__(self, multireddits, loader): - - self.name = "Multireddits" - self.order = None - self._loader = loader - self._multireddits = multireddits - self._multireddit_data = [] - - try: - self.get(0) - except IndexError: - raise exceptions.SubscriptionError('No multireddits') - - @classmethod - def from_user(cls, reddit, multireddits, loader): - multireddits = (m for m in multireddits) - return cls(multireddits, loader) - - def get(self, index, n_cols=70): - """ - Grab the `i`th subscription, with the title field formatted to fit - inside of a window of width `n_cols` - """ - - if index < 0: - raise IndexError - - while index >= len(self._multireddit_data): - try: - with self._loader('Loading multireddits'): - multireddit = next(self._multireddits) - if self._loader.exception: - raise IndexError - except StopIteration: - raise IndexError - else: - data = self.strip_praw_multireddit(multireddit) - self._multireddit_data.append(data) - - data = self._multireddit_data[index] + data = self._reddit_data[index] data['split_title'] = self.wrap_text(data['title'], width=n_cols) data['n_rows'] = len(data['split_title']) + 1 data['offset'] = 0 diff --git a/rtv/exceptions.py b/rtv/exceptions.py index 1d9f976..31d9f5a 100644 --- a/rtv/exceptions.py +++ b/rtv/exceptions.py @@ -26,8 +26,8 @@ class SubredditError(RTVError): "Subreddit could not be reached" -class SubscriptionError(RTVError): - "Subscriptions could not be fetched" +class ListRedditsError(RTVError): + "List of reddits could not be fetched" class ProgramError(RTVError): @@ -39,4 +39,4 @@ class BrowserError(RTVError): class TemporaryFileError(RTVError): - "Indicates that an error has occurred and the file should not be deleted" \ No newline at end of file + "Indicates that an error has occurred and the file should not be deleted" diff --git a/rtv/reddits.py b/rtv/reddits.py new file mode 100644 index 0000000..a220833 --- /dev/null +++ b/rtv/reddits.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import curses + +from .page import Page, PageController +from .content import ListRedditsContent +from .objects import Color, Navigator, Command + + +class ListRedditsController(PageController): + character_map = {} + + +class ListRedditsPage(Page): + + def __init__(self, reddit, name, reddits, term, config, oauth): + super(ListRedditsPage, self).__init__(reddit, term, config, oauth) + + self.controller = ListRedditsController(self, keymap=config.keymap) + self.name = name + self.content = ListRedditsContent.from_user(name, reddits, term.loader) + self.nav = Navigator(self.content.get) + self.reddit_data = None + + @ListRedditsController.register(Command('REFRESH')) + def refresh_content(self, order=None, name=None): + "Re-download all reddits and reset the page index" + + # reddit listings does not support sorting by order + if order: + self.term.flash() + return + + with self.term.loader(): + self.content = ListRedditsContent.from_user(self.name, self.reddit, + self.term.loader) + if not self.term.loader.exception: + self.nav = Navigator(self.content.get) + + @ListRedditsController.register(Command('SUBSCRIPTION_SELECT')) + def select_reddit(self): + "Store the selected reddit and return to the subreddit page" + + self.reddit_data = self.content.get(self.nav.absolute_index) + self.active = False + + @ListRedditsController.register(Command('SUBSCRIPTION_EXIT')) + def close_subscriptions(self): + "Close list of reddits and return to the subreddit page" + + self.active = False + + def _draw_banner(self): + # Subscriptions can't be sorted, so disable showing the order menu + pass + + def _draw_item(self, win, data, inverted): + n_rows, n_cols = win.getmaxyx() + n_cols -= 1 # Leave space for the cursor in the first column + + # Handle the case where the window is not large enough to fit the data. + valid_rows = range(0, n_rows) + offset = 0 if not inverted else -(data['n_rows'] - n_rows) + + row = offset + if row in valid_rows: + attr = curses.A_BOLD | Color.YELLOW + self.term.add_line(win, '{name}'.format(**data), row, 1, attr) + + row = offset + 1 + for row, text in enumerate(data['split_title'], start=row): + if row in valid_rows: + self.term.add_line(win, text, row, 1) diff --git a/rtv/subreddit.py b/rtv/subreddit.py index 51a0e0b..2141d81 100644 --- a/rtv/subreddit.py +++ b/rtv/subreddit.py @@ -9,8 +9,7 @@ from .content import SubredditContent from .page import Page, PageController, logged_in from .objects import Navigator, Color, Command from .submission import SubmissionPage -from .multireddits import MultiredditPage -from .subscription import SubscriptionPage +from .reddits import ListRedditsPage from .exceptions import TemporaryFileError @@ -158,8 +157,9 @@ class SubredditPage(Page): "Open user subscriptions page" with self.term.loader('Loading subscriptions'): - page = SubscriptionPage( - self.reddit, self.term, self.config, self.oauth) + page = ListRedditsPage(self.reddit, 'My Subscriptions', + self.reddit.get_my_subreddits(limit=None), self.term, + self.config, self.oauth) if self.term.loader.exception: return @@ -167,9 +167,9 @@ class SubredditPage(Page): # When the user has chosen a subreddit in the subscriptions list, # refresh content with the selected subreddit - if page.subreddit_data is not None: + if page.reddit_data is not None: self.refresh_content(order='ignore', - name=page.subreddit_data['name']) + name=page.reddit_data['name']) @SubredditController.register(Command('MULTIREDDIT_OPEN_SUBSCRIPTIONS')) @logged_in @@ -177,9 +177,9 @@ class SubredditPage(Page): "Open user multireddit subscriptions page" with self.term.loader('Loading multireddits'): - page = MultiredditPage( - self.reddit, self.reddit.get_my_multireddits(), - self.term, self.config) + page = ListRedditsPage(self.reddit, + 'My Multireddits', self.reddit.get_my_multireddits(), + self.term, self.config, self.oauth) if self.term.loader.exception: return @@ -187,10 +187,9 @@ class SubredditPage(Page): # When the user has chosen a subreddit in the subscriptions list, # refresh content with the selected subreddit - if page.multireddit_data is not None: + if page.reddit_data is not None: self.refresh_content(order='ignore', - name=page.multireddit_data['name']) - + name=page.reddit_data['name']) def _draw_item(self, win, data, inverted): diff --git a/tests/conftest.py b/tests/conftest.py index 02ee9ee..056eb5b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,7 +16,7 @@ from rtv.config import Config from rtv.terminal import Terminal from rtv.subreddit import SubredditPage from rtv.submission import SubmissionPage -from rtv.subscription import SubscriptionPage +from rtv.reddits import ListRedditsPage try: from unittest import mock @@ -210,13 +210,14 @@ def subreddit_page(reddit, terminal, config, oauth): @pytest.fixture() -def subscription_page(reddit, terminal, config, oauth, refresh_token): - # Must be logged in to view your subscriptions - config.refresh_token = refresh_token - oauth.authorize() +def reddits(reddit, terminal, config, oauth): + return reddit.get_popular_subreddits() + +@pytest.fixture() +def list_reddits_page(reddit, name, reddits, terminal, config, oauth): with terminal.loader(): - page = SubscriptionPage(reddit, terminal, config, oauth) + page = ListRedditsPage(reddit, name, reddits, terminal, config, oauth) assert terminal.loader.exception is None page.draw() return page diff --git a/tests/test_reddits.py b/tests/test_reddits.py new file mode 100644 index 0000000..5b2945e --- /dev/null +++ b/tests/test_reddits.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import curses + +import praw +import pytest + +from rtv.reddits import ListRedditsPage + +try: + from unittest import mock +except ImportError: + import mock + + +def test_list_reddits_page_construct(reddit, reddits, terminal, config, + oauth, refresh_token): + window = terminal.stdscr.subwin + title = 'Subscriptions' + + with terminal.loader(): + page = ListRedditsPage(reddit, title, reddits, terminal, config, oauth) + assert terminal.loader.exception is None + + page.draw() + + # Header - Title + window.addstr.assert_any_call(0, 0, title.encode('utf-8')) + + # Header - Name + name = reddit.user.name.encode('utf-8') + window.addstr.assert_any_call(0, 59, name) + + # Banner shouldn't be drawn + menu = ('[1]hot ' + '[2]top ' + '[3]rising ' + '[4]new ' + '[5]controversial').encode('utf-8') + with pytest.raises(AssertionError): + window.addstr.assert_any_call(0, 0, menu) + + # Cursor - 2 lines + window.subwin.chgat.assert_any_call(0, 0, 1, 262144) + window.subwin.chgat.assert_any_call(1, 0, 1, 262144) + + # Reload with a smaller terminal window + terminal.stdscr.ncols = 20 + terminal.stdscr.nlines = 10 + with terminal.loader(): + page = ListRedditsPage(reddit, title, reddits, terminal, config, oauth) + assert terminal.loader.exception is None + + page.draw() + + +def test_list_reddits_refresh(list_reddits_page): + + # Refresh content - invalid order + list_reddits_page.controller.trigger('2') + assert curses.flash.called + curses.flash.reset_mock() + + # Refresh content + list_reddits_page.controller.trigger('r') + assert not curses.flash.called + + +def test_list_reddits_move(list_reddits_page): + + # Test movement + with mock.patch.object(list_reddits_page, 'clear_input_queue'): + + # Move cursor to the bottom of the page + while not curses.flash.called: + list_reddits_page.controller.trigger('j') + curses.flash.reset_mock() + assert list_reddits_page.nav.inverted + assert (list_reddits_page.nav.absolute_index == + len(list_reddits_page.content._reddit_data) - 1) + + # And back to the top + for i in range(list_reddits_page.nav.absolute_index): + list_reddits_page.controller.trigger('k') + assert not curses.flash.called + assert list_reddits_page.nav.absolute_index == 0 + assert not list_reddits_page.nav.inverted + + # Can't go up any further + list_reddits_page.controller.trigger('k') + assert curses.flash.called + assert list_reddits_page.nav.absolute_index == 0 + assert not list_reddits_page.nav.inverted + + # Page down should move the last item to the top + n = len(list_reddits_page._subwindows) + list_reddits_page.controller.trigger('n') + assert list_reddits_page.nav.absolute_index == n - 1 + + # And page up should move back up, but possibly not to the first item + list_reddits_page.controller.trigger('m') + + +def test_list_reddits_select(list_reddits_page): + + # Select a subreddit + list_reddits_page.controller.trigger(curses.KEY_ENTER) + assert list_reddits_page.reddit_data is not None + assert list_reddits_page.active is False + + +def test_list_reddits_close(list_reddits_page): + + # Close the list of reddits page + list_reddits_page.reddit_data = None + list_reddits_page.active = None + list_reddits_page.controller.trigger('h') + assert list_reddits_page.reddit_data is None + assert list_reddits_page.active is False + + +def test_list_reddits_page_invalid(list_reddits_page): + + # Test that other commands don't crash + methods = [ + 'a', # Upvote + 'z', # Downvote + 'd', # Delete + 'e', # Edit + ] + for ch in methods: + curses.flash.reset_mock() + list_reddits_page.controller.trigger(ch) + assert curses.flash.called From 99dbea0bd498b281d0393750f744d5b6d1500d50 Mon Sep 17 00:00:00 2001 From: woorst Date: Sun, 17 Jul 2016 16:20:27 -0500 Subject: [PATCH 18/19] Remove now redundant files --- rtv/multireddits.py | 74 ------------------- rtv/subscription.py | 73 ------------------- tests/test_subscription.py | 145 ------------------------------------- 3 files changed, 292 deletions(-) delete mode 100644 rtv/multireddits.py delete mode 100644 rtv/subscription.py delete mode 100644 tests/test_subscription.py diff --git a/rtv/multireddits.py b/rtv/multireddits.py deleted file mode 100644 index d1f96af..0000000 --- a/rtv/multireddits.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import curses - -from .page import Page, PageController -from .content import MultiredditContent -from .objects import Color, Navigator, Command - - -class SubscriptionController(PageController): - character_map = {} - - -class MultiredditPage(Page): - - def __init__(self, reddit, multireddits, term, config): - super(MultiredditPage, self).__init__(reddit, term, config, None) - - self.controller = SubscriptionController(self, keymap=config.keymap) - self.content = MultiredditContent.from_user(reddit, multireddits, - term.loader) - self.nav = Navigator(self.content.get) - self.multireddit_data = None - - @SubscriptionController.register(Command('REFRESH')) - def refresh_content(self, order=None, name=None): - "Re-download all subscriptions and reset the page index" - - # reddit.get_multireddits() does not support sorting by order - if order: - self.term.flash() - return - - with self.term.loader(): - self.content = MultiredditContent.from_user(self.reddit, - self.multireddits, self.term.loader) - if not self.term.loader.exception: - self.nav = Navigator(self.content.get) - - @SubscriptionController.register(Command('SUBSCRIPTION_SELECT')) - def select_multireddit(self): - "Store the selected multireddit and return to the subreddit page" - - self.multireddit_data = self.content.get(self.nav.absolute_index) - self.active = False - - @SubscriptionController.register(Command('SUBSCRIPTION_EXIT')) - def close_multireddit_subscriptions(self): - "Close multireddits and return to the subreddit page" - - self.active = False - - def _draw_banner(self): - # Subscriptions can't be sorted, so disable showing the order menu - pass - - def _draw_item(self, win, data, inverted): - n_rows, n_cols = win.getmaxyx() - n_cols -= 1 # Leave space for the cursor in the first column - - # Handle the case where the window is not large enough to fit the data. - valid_rows = range(0, n_rows) - offset = 0 if not inverted else -(data['n_rows'] - n_rows) - - row = offset - if row in valid_rows: - attr = curses.A_BOLD | Color.YELLOW - self.term.add_line(win, '{name}'.format(**data), row, 1, attr) - - row = offset + 1 - for row, text in enumerate(data['split_title'], start=row): - if row in valid_rows: - self.term.add_line(win, text, row, 1) diff --git a/rtv/subscription.py b/rtv/subscription.py deleted file mode 100644 index 44a443d..0000000 --- a/rtv/subscription.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import curses - -from .page import Page, PageController -from .content import SubscriptionContent -from .objects import Color, Navigator, Command - - -class SubscriptionController(PageController): - character_map = {} - - -class SubscriptionPage(Page): - - def __init__(self, reddit, term, config, oauth): - super(SubscriptionPage, self).__init__(reddit, term, config, oauth) - - self.controller = SubscriptionController(self, keymap=config.keymap) - self.content = SubscriptionContent.from_user(reddit, term.loader) - self.nav = Navigator(self.content.get) - self.subreddit_data = None - - @SubscriptionController.register(Command('REFRESH')) - def refresh_content(self, order=None, name=None): - "Re-download all subscriptions and reset the page index" - - # reddit.get_my_subreddits() does not support sorting by order - if order: - self.term.flash() - return - - with self.term.loader(): - self.content = SubscriptionContent.from_user(self.reddit, - self.term.loader) - if not self.term.loader.exception: - self.nav = Navigator(self.content.get) - - @SubscriptionController.register(Command('SUBSCRIPTION_SELECT')) - def select_subreddit(self): - "Store the selected subreddit and return to the subreddit page" - - self.subreddit_data = self.content.get(self.nav.absolute_index) - self.active = False - - @SubscriptionController.register(Command('SUBSCRIPTION_EXIT')) - def close_subscriptions(self): - "Close subscriptions and return to the subreddit page" - - self.active = False - - def _draw_banner(self): - # Subscriptions can't be sorted, so disable showing the order menu - pass - - def _draw_item(self, win, data, inverted): - n_rows, n_cols = win.getmaxyx() - n_cols -= 1 # Leave space for the cursor in the first column - - # Handle the case where the window is not large enough to fit the data. - valid_rows = range(0, n_rows) - offset = 0 if not inverted else -(data['n_rows'] - n_rows) - - row = offset - if row in valid_rows: - attr = curses.A_BOLD | Color.YELLOW - self.term.add_line(win, '{name}'.format(**data), row, 1, attr) - - row = offset + 1 - for row, text in enumerate(data['split_title'], start=row): - if row in valid_rows: - self.term.add_line(win, text, row, 1) \ No newline at end of file diff --git a/tests/test_subscription.py b/tests/test_subscription.py deleted file mode 100644 index a3ccb2b..0000000 --- a/tests/test_subscription.py +++ /dev/null @@ -1,145 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import curses - -import praw -import pytest - -from rtv.subscription import SubscriptionPage - -try: - from unittest import mock -except ImportError: - import mock - - -def test_subscription_page_construct(reddit, terminal, config, oauth, - refresh_token): - window = terminal.stdscr.subwin - - # Can't load the page if not logged in - with terminal.loader(): - SubscriptionPage(reddit, terminal, config, oauth) - assert isinstance( - terminal.loader.exception, praw.errors.LoginOrScopeRequired) - - # Log in - config.refresh_token = refresh_token - oauth.authorize() - - with terminal.loader(): - page = SubscriptionPage(reddit, terminal, config, oauth) - assert terminal.loader.exception is None - - page.draw() - - # Header - Title - title = 'Subscriptions'.encode('utf-8') - window.addstr.assert_any_call(0, 0, title) - - # Header - Name - name = reddit.user.name.encode('utf-8') - window.addstr.assert_any_call(0, 59, name) - - # Banner shouldn't be drawn - menu = ('[1]hot ' - '[2]top ' - '[3]rising ' - '[4]new ' - '[5]controversial').encode('utf-8') - with pytest.raises(AssertionError): - window.addstr.assert_any_call(0, 0, menu) - - # Cursor - 2 lines - window.subwin.chgat.assert_any_call(0, 0, 1, 262144) - window.subwin.chgat.assert_any_call(1, 0, 1, 262144) - - # Reload with a smaller terminal window - terminal.stdscr.ncols = 20 - terminal.stdscr.nlines = 10 - with terminal.loader(): - page = SubscriptionPage(reddit, terminal, config, oauth) - assert terminal.loader.exception is None - - page.draw() - - -def test_subscription_refresh(subscription_page): - - # Refresh content - invalid order - subscription_page.controller.trigger('2') - assert curses.flash.called - curses.flash.reset_mock() - - # Refresh content - subscription_page.controller.trigger('r') - assert not curses.flash.called - - -def test_subscription_move(subscription_page): - - # Test movement - with mock.patch.object(subscription_page, 'clear_input_queue'): - - # Move cursor to the bottom of the page - while not curses.flash.called: - subscription_page.controller.trigger('j') - curses.flash.reset_mock() - assert subscription_page.nav.inverted - assert (subscription_page.nav.absolute_index == - len(subscription_page.content._subscription_data) - 1) - - # And back to the top - for i in range(subscription_page.nav.absolute_index): - subscription_page.controller.trigger('k') - assert not curses.flash.called - assert subscription_page.nav.absolute_index == 0 - assert not subscription_page.nav.inverted - - # Can't go up any further - subscription_page.controller.trigger('k') - assert curses.flash.called - assert subscription_page.nav.absolute_index == 0 - assert not subscription_page.nav.inverted - - # Page down should move the last item to the top - n = len(subscription_page._subwindows) - subscription_page.controller.trigger('n') - assert subscription_page.nav.absolute_index == n - 1 - - # And page up should move back up, but possibly not to the first item - subscription_page.controller.trigger('m') - - -def test_subscription_select(subscription_page): - - # Select a subreddit - subscription_page.controller.trigger(curses.KEY_ENTER) - assert subscription_page.subreddit_data is not None - assert subscription_page.active is False - - -def test_subscription_close(subscription_page): - - # Close the subscriptions page - subscription_page.subreddit_data = None - subscription_page.active = None - subscription_page.controller.trigger('h') - assert subscription_page.subreddit_data is None - assert subscription_page.active is False - - -def test_subscription_page_invalid(subscription_page): - - # Test that other commands don't crash - methods = [ - 'a', # Upvote - 'z', # Downvote - 'd', # Delete - 'e', # Edit - ] - for ch in methods: - curses.flash.reset_mock() - subscription_page.controller.trigger(ch) - assert curses.flash.called \ No newline at end of file From bf59214de08c07ac14c41538a6e04cdcd4bc9f1d Mon Sep 17 00:00:00 2001 From: woorst Date: Sun, 17 Jul 2016 19:52:35 -0500 Subject: [PATCH 19/19] ListRedditsContent.from_user method replaced with from_func; updated tests accordingly --- rtv/content.py | 6 +++--- rtv/reddits.py | 9 +++++---- rtv/subreddit.py | 10 +++++----- tests/conftest.py | 11 ++++------- tests/test_content.py | 30 +++++++++++------------------- tests/test_reddits.py | 9 +++++---- 6 files changed, 33 insertions(+), 42 deletions(-) diff --git a/rtv/content.py b/rtv/content.py index 1eee21e..ad6bf92 100644 --- a/rtv/content.py +++ b/rtv/content.py @@ -388,7 +388,7 @@ class SubredditContent(Content): # Strip leading, trailing, and redundant backslashes name_list = [seg for seg in name.strip(' /').split('/') if seg] name_order = None - if name_list[0] in ['r', 'u', 'user', 'domain'] and len(name_list) > 1: + if len(name_list) > 1 and name_list[0] in ['r', 'u', 'user', 'domain']: listing, name_list = name_list[0], name_list[1:] if len(name_list) == 2: name, name_order = name_list @@ -540,8 +540,8 @@ class ListRedditsContent(Content): raise exceptions.ListRedditsError('No {}'.format(self.name)) @classmethod - def from_user(cls, name, reddits, loader): - reddits = (r for r in reddits) + def from_func(cls, name, func, loader): + reddits = (r for r in func()) return cls(name, reddits, loader) def get(self, index, n_cols=70): diff --git a/rtv/reddits.py b/rtv/reddits.py index a220833..4786250 100644 --- a/rtv/reddits.py +++ b/rtv/reddits.py @@ -14,12 +14,13 @@ class ListRedditsController(PageController): class ListRedditsPage(Page): - def __init__(self, reddit, name, reddits, term, config, oauth): + def __init__(self, reddit, name, func, term, config, oauth): super(ListRedditsPage, self).__init__(reddit, term, config, oauth) self.controller = ListRedditsController(self, keymap=config.keymap) self.name = name - self.content = ListRedditsContent.from_user(name, reddits, term.loader) + self.func = func + self.content = ListRedditsContent.from_func(name, func, term.loader) self.nav = Navigator(self.content.get) self.reddit_data = None @@ -33,8 +34,8 @@ class ListRedditsPage(Page): return with self.term.loader(): - self.content = ListRedditsContent.from_user(self.name, self.reddit, - self.term.loader) + self.content = ListRedditsContent.from_func(self.name, + self.func, self.term.loader) if not self.term.loader.exception: self.nav = Navigator(self.content.get) diff --git a/rtv/subreddit.py b/rtv/subreddit.py index 2141d81..e9d1aa0 100644 --- a/rtv/subreddit.py +++ b/rtv/subreddit.py @@ -156,10 +156,10 @@ class SubredditPage(Page): def open_subscriptions(self): "Open user subscriptions page" + func = lambda : self.reddit.get_my_subreddits(limit=None) with self.term.loader('Loading subscriptions'): - page = ListRedditsPage(self.reddit, 'My Subscriptions', - self.reddit.get_my_subreddits(limit=None), self.term, - self.config, self.oauth) + page = ListRedditsPage(self.reddit, 'My Subscriptions', func, + self.term, self.config, self.oauth) if self.term.loader.exception: return @@ -176,10 +176,10 @@ class SubredditPage(Page): def open_multireddit_subscriptions(self): "Open user multireddit subscriptions page" + func = lambda : self.reddit.get_my_multireddits() with self.term.loader('Loading multireddits'): page = ListRedditsPage(self.reddit, - 'My Multireddits', self.reddit.get_my_multireddits(), - self.term, self.config, self.oauth) + 'My Multireddits', func, self.term, self.config, self.oauth) if self.term.loader.exception: return diff --git a/tests/conftest.py b/tests/conftest.py index 056eb5b..df470aa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -210,14 +210,11 @@ def subreddit_page(reddit, terminal, config, oauth): @pytest.fixture() -def reddits(reddit, terminal, config, oauth): - return reddit.get_popular_subreddits() - - -@pytest.fixture() -def list_reddits_page(reddit, name, reddits, terminal, config, oauth): +def list_reddits_page(reddit, terminal, config, oauth): + title = 'Popular Subreddits' + func = reddit.get_popular_subreddits with terminal.loader(): - page = ListRedditsPage(reddit, name, reddits, terminal, config, oauth) + page = ListRedditsPage(reddit, title, func, terminal, config, oauth) assert terminal.loader.exception is None page.draw() return page diff --git a/tests/test_content.py b/tests/test_content.py index 7fc624c..c328511 100644 --- a/tests/test_content.py +++ b/tests/test_content.py @@ -9,7 +9,7 @@ import praw import pytest from rtv.content import ( - Content, SubmissionContent, SubredditContent, SubscriptionContent) + Content, SubmissionContent, SubredditContent, ListRedditsContent) from rtv import exceptions @@ -291,7 +291,6 @@ def test_content_subreddit_from_name(reddit, terminal): # Queries SubredditContent.from_name(reddit, 'front', terminal.loader, query='pea') SubredditContent.from_name(reddit, 'python', terminal.loader, query='pea') - SubredditContent.from_name(reddit, 'me', terminal.loader, query='pea') def test_content_subreddit_multireddit(reddit, terminal): @@ -333,23 +332,16 @@ def test_content_subreddit_me(reddit, oauth, refresh_token, terminal): assert isinstance(terminal.loader.exception, exceptions.SubredditError) -def test_content_subscription(reddit, oauth, refresh_token, terminal): +def test_content_list_reddits(reddit, oauth, refresh_token, terminal): - # Not logged in + title = 'Popular Subreddits' + func = reddit.get_popular_subreddits with terminal.loader(): - SubscriptionContent.from_user(reddit, terminal.loader) - assert isinstance( - terminal.loader.exception, praw.errors.LoginOrScopeRequired) - - # Logged in - oauth.config.refresh_token = refresh_token - oauth.authorize() - with terminal.loader(): - content = SubscriptionContent.from_user(reddit, terminal.loader) + content = ListRedditsContent.from_func(title, func, terminal.loader) assert terminal.loader.exception is None # These are static - assert content.name == 'Subscriptions' + assert content.name == title assert content.order is None # Validate content @@ -361,11 +353,11 @@ def test_content_subscription(reddit, oauth, refresh_token, terminal): assert not isinstance(val, six.binary_type) -def test_content_subscription_empty(terminal): +def test_content_list_reddits_empty(terminal): - # Simulate an empty subscription generator - subscriptions = iter([]) + # Simulate an empty list of reddits + func = lambda : iter([]) with terminal.loader(): - SubscriptionContent(subscriptions, terminal.loader) - assert isinstance(terminal.loader.exception, exceptions.SubscriptionError) + ListRedditsContent('test', func(), terminal.loader()) + assert isinstance(terminal.loader.exception, exceptions.ListRedditsError) diff --git a/tests/test_reddits.py b/tests/test_reddits.py index 5b2945e..f128c47 100644 --- a/tests/test_reddits.py +++ b/tests/test_reddits.py @@ -14,13 +14,14 @@ except ImportError: import mock -def test_list_reddits_page_construct(reddit, reddits, terminal, config, +def test_list_reddits_page_construct(reddit, terminal, config, oauth, refresh_token): window = terminal.stdscr.subwin - title = 'Subscriptions' + title = 'Popular Subreddits' + func = reddit.get_popular_subreddits with terminal.loader(): - page = ListRedditsPage(reddit, title, reddits, terminal, config, oauth) + page = ListRedditsPage(reddit, title, func, terminal, config, oauth) assert terminal.loader.exception is None page.draw() @@ -49,7 +50,7 @@ def test_list_reddits_page_construct(reddit, reddits, terminal, config, terminal.stdscr.ncols = 20 terminal.stdscr.nlines = 10 with terminal.loader(): - page = ListRedditsPage(reddit, title, reddits, terminal, config, oauth) + page = ListRedditsPage(reddit, title, func, terminal, config, oauth) assert terminal.loader.exception is None page.draw()