Merge remote-tracking branch 'upstream/master'

Refactoring
This commit is contained in:
David Foucher
2016-07-24 01:27:11 +02:00
76 changed files with 62359 additions and 13645 deletions

View File

@@ -213,9 +213,15 @@ class Content(object):
data = {}
data['object'] = subscription
data['type'] = 'Subscription'
data['name'] = "/r/" + subscription.display_name
data['title'] = subscription.title
if isinstance(subscription, praw.objects.Multireddit):
data['type'] = 'Multireddit'
data['name'] = subscription.path
data['title'] = subscription.description_md
else:
data['type'] = 'Subscription'
data['name'] = "/r/" + subscription.display_name
data['title'] = subscription.title
return data
@staticmethod
@@ -399,75 +405,149 @@ class SubredditContent(Content):
@classmethod
def from_name(cls, reddit, name, loader, order=None, query=None):
"""
Params:
reddit (praw.Reddit): Instance of the reddit api.
name (text): The name of the desired subreddit, user, multireddit,
etc. In most cases this translates directly from the URL that
reddit itself uses. This is what users will type in the command
prompt when they navigate to a new location.
loader (terminal.loader): Handler for the load screen that will be
displayed when making http requests.
order (text): If specified, the order that posts will be sorted in.
For `top` and `controversial`, you can specify the time frame
by including a dash, e.g. "top-year". If an order is not
specified, it will be extracted from the name.
query (text): Content to search for on the given subreddit or
user's page.
"""
# TODO: refactor this into smaller methods
# Strip leading and trailing backslashes
name = name.strip(' /')
if name.startswith('r/'):
name = name[2:]
# Strip leading, trailing, and redundant backslashes
parts = [seg for seg in name.strip(' /').split('/') if seg]
# 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('/')
order = order or name_order
display_name = '/r/{0}'.format(name)
# Check for the resource type, assume /r/ as the default
if len(parts) >= 3 and parts[2] == 'm':
# E.g. /u/multi-mod/m/android
resource_root, parts = '/'.join(parts[:3]), parts[3:]
elif len(parts) > 1 and parts[0] in ['r', 'u', 'user', 'domain']:
resource_root = parts.pop(0)
else:
resource_root = 'r'
# There should at most two parts left, the resource and the order
if len(parts) == 1:
resource, resource_order = parts[0], None
elif len(parts) == 2:
resource, resource_order = parts
else:
raise InvalidSubreddit()
if not resource:
# Praw does not correctly handle empty strings
# https://github.com/praw-dev/praw/issues/615
raise InvalidSubreddit()
# If the order was explicitly passed in, it will take priority over
# the order that was extracted from the name
order = order or resource_order
display_order = order
display_name = '/'.join(['', resource_root, resource])
# Split the order from the period E.g. controversial-all, top-hour
if order and '-' in order:
order, period = order.split('-', 1)
else:
period = None
if order not in ['hot', 'top', 'rising', 'new', 'controversial', None]:
raise exceptions.SubredditError('Unrecognized order "%s"' % order)
raise InvalidSubreddit('Invalid order "%s"' % order)
if period not in ['all', 'day', 'hour', 'month', 'week', 'year', None]:
raise InvalidSubreddit('Invalid period "%s"' % period)
if period and order not in ['top', 'controversial']:
raise InvalidSubreddit('"%s" order does not allow sorting by'
'period' % order)
if name == 'me':
# On some objects, praw doesn't allow you to pass arguments for the
# order and period. Instead you need to call special helper functions
# such as Multireddit.get_controversial_from_year(). Build the method
# name here for convenience.
if period:
method_alias = 'get_{0}_from_{1}'.format(order, period)
elif order:
method_alias = 'get_{0}'.format(order)
else:
method_alias = 'get_hot'
# Here's where we start to build the submission generators
if query:
if resource_root in ('u', 'user'):
search = '/r/{subreddit}/search'
author = reddit.user.name if resource == 'me' else resource
query = 'author:{0} {1}'.format(author, query)
subreddit = None
else:
search = resource_root + '/{subreddit}/search'
subreddit = None if resource == 'front' else resource
reddit.config.API_PATHS['search'] = search
submissions = reddit.search(query, subreddit=subreddit,
sort=order, period=period)
elif resource_root == 'domain':
order = order or 'hot'
submissions = reddit.get_domain_listing(
resource, sort=order, period=period, limit=None)
elif resource_root.endswith('/m'):
redditor = resource_root.split('/')[1]
multireddit = reddit.get_multireddit(redditor, resource)
submissions = getattr(multireddit, method_alias)(limit=None)
elif resource_root in ('u', 'user') and resource == '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()
order = order or 'new'
submissions = reddit.user.get_submitted(sort=order, limit=None)
elif name == 'saved':
elif resource_root in ('u', 'user') and resource == 'saved':
if not reddit.is_oauth_session():
raise exceptions.AccountError('Not logged in')
elif order:
submissions = reddit.user.get_saved(sort=order)
else:
submissions = reddit.user.get_saved()
order = order or 'new'
submissions = reddit.user.get_saved(sort=order, limit=None)
elif query:
if name == 'front':
submissions = reddit.search(query, subreddit=None, sort=order)
elif resource_root in ('u', 'user'):
order = order or 'new'
period = period or 'all'
redditor = reddit.get_redditor(resource)
submissions = redditor.get_submitted(
sort=order, time=period, limit=None)
elif resource == 'front':
if order in (None, 'hot'):
submissions = reddit.get_front_page(limit=None)
elif period:
# For the front page, praw makes you send the period as `t`
# instead of calling reddit.get_hot_from_week()
method_alias = 'get_{0}'.format(order)
method = getattr(reddit, method_alias)
submissions = method(limit=None, t=period)
else:
submissions = reddit.search(query, subreddit=name, sort=order)
submissions = getattr(reddit, method_alias)(limit=None)
else:
if name == '':
# Praw does not correctly handle empty strings
# https://github.com/praw-dev/praw/issues/615
raise InvalidSubreddit()
subreddit = reddit.get_subreddit(resource)
submissions = getattr(subreddit, method_alias)(limit=None)
if name == 'front':
dispatch = {
None: reddit.get_front_page,
'hot': reddit.get_front_page,
'top': reddit.get_top,
'rising': reddit.get_rising,
'new': reddit.get_new,
'controversial': reddit.get_controversial,
}
else:
subreddit = reddit.get_subreddit(name)
# For special subreddits like /r/random we want to replace the
# display name with the one returned by the request.
display_name = '/r/{0}'.format(subreddit.display_name)
dispatch = {
None: subreddit.get_hot,
'hot': subreddit.get_hot,
'top': subreddit.get_top,
'rising': subreddit.get_rising,
'new': subreddit.get_new,
'controversial': subreddit.get_controversial,
}
submissions = dispatch[order](limit=None)
# For special subreddits like /r/random we want to replace the
# display name with the one returned by the request.
display_name = '/r/{0}'.format(subreddit.display_name)
return cls(display_name, submissions, loader, order=order)
# We made it!
return cls(display_name, submissions, loader, order=display_order)
def get(self, index, n_cols=70):
"""
@@ -510,9 +590,9 @@ class SubredditContent(Content):
class SubscriptionContent(Content):
def __init__(self, subscriptions, loader):
def __init__(self, name, subscriptions, loader):
self.name = "Subscriptions"
self.name = name
self.order = None
self._loader = loader
self._subscriptions = subscriptions
@@ -521,16 +601,28 @@ class SubscriptionContent(Content):
try:
self.get(0)
except IndexError:
raise exceptions.SubscriptionError('No subscriptions')
raise exceptions.SubscriptionError('No content')
@classmethod
def from_user(cls, reddit, loader):
subscriptions = reddit.get_my_subreddits(limit=None)
return cls(subscriptions, loader)
def from_user(cls, reddit, loader, content_type='subreddit'):
if content_type == 'subreddit':
name = 'My Subreddits'
items = reddit.get_my_subreddits(limit=None)
elif content_type == 'multireddit':
name = 'My Multireddits'
# Multireddits are returned as a list
items = iter(reddit.get_my_multireddits())
elif content_type == 'popular':
name = 'Popular Subreddits'
items = reddit.get_popular_subreddits(limit=None)
else:
raise exceptions.SubscriptionError('Invalid type %s', content_type)
return cls(name, items, loader)
def get(self, index, n_cols=70):
"""
Grab the `i`th subscription, with the title field formatted to fit
Grab the `i`th object, with the title field formatted to fit
inside of a window of width `n_cols`
"""
@@ -539,7 +631,7 @@ class SubscriptionContent(Content):
while index >= len(self._subscription_data):
try:
with self._loader('Loading subscriptions'):
with self._loader('Loading content'):
subscription = next(self._subscriptions)
if self._loader.exception:
raise IndexError

View File

@@ -34,12 +34,14 @@ HELP = """
`e` : Edit an existing post or comment
`d` : Delete an existing post or comment
`i` : Display new messages prompt
`s` : Open/close subscribed subreddits list
`s` : Open subscribed subreddits
`S` : Open subscribed multireddits
[Subreddit Mode]
`l` or `RIGHT` : Enter the selected submission
`/` : Open a prompt to switch subreddits
`f` : Open a prompt to search the current subreddit
'p' : Toggle between the front page and last visited subreddit
[Submission Mode]
`h` or `LEFT` : Return to subreddit mode

View File

@@ -27,7 +27,7 @@ class SubredditError(RTVError):
class SubscriptionError(RTVError):
"Subscriptions could not be fetched"
"Content 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"
"Indicates that an error has occurred and the file should not be deleted"

View File

@@ -299,7 +299,8 @@ class Page(object):
sub_name = self.content.name
sub_name = sub_name.replace('/r/front', 'Front Page')
sub_name = sub_name.replace('/r/me', 'My Submissions')
sub_name = sub_name.replace('/u/me', 'My Submissions')
sub_name = sub_name.replace('/u/saved', 'My Saved Submissions')
self.term.add_line(window, sub_name, 0, 0)
# Set the terminal title
@@ -340,7 +341,8 @@ class Page(object):
text = spacing.join(items)
self.term.add_line(window, text, 0, 0)
if self.content.order is not None:
col = text.find(self.content.order) - 3
order = self.content.order.split('-')[0]
col = text.find(order) - 3
window.chgat(0, col, 3, attr | curses.A_REVERSE)
self._row += 1

View File

@@ -122,7 +122,9 @@ SUBREDDIT_POST = c
SUBREDDIT_OPEN = l, <KEY_RIGHT>
SUBREDDIT_OPEN_IN_BROWSER = o, <LF>, <KEY_ENTER>, <KEY_ENTER>
SUBREDDIT_OPEN_SUBSCRIPTIONS = s
SUBREDDIT_OPEN_MULTIREDDITS = S
SUBREDDIT_FRONTPAGE = p
; Subscription page
SUBSCRIPTION_SELECT = l, <LF>, <KEY_ENTER>, <KEY_RIGHT>
SUBSCRIPTION_EXIT = h, s, <ESC>, <KEY_LEFT>
SUBSCRIPTION_EXIT = h, s, S, <ESC>, <KEY_LEFT>

View File

@@ -150,7 +150,7 @@ class SubmissionPage(Page):
@SubmissionController.register(Command('SUBMISSION_OPEN_IN_URLVIEWER'))
def comment_urlview(self):
data = self.content.get(self.nav.absolute_index)
comment = data.get('body', '')
comment = data.get('body') or data.get('text') or data.get('url_full')
if comment:
self.term.open_urlview(comment)
else:

View File

@@ -30,6 +30,7 @@ class SubredditPage(Page):
self.controller = SubredditController(self, keymap=config.keymap)
self.content = SubredditContent.from_name(reddit, name, term.loader)
self.nav = Navigator(self.content.get)
self._toggled_subreddit = None
@SubredditController.register(Command('REFRESH'))
def refresh_content(self, order=None, name=None):
@@ -55,7 +56,7 @@ class SubredditPage(Page):
name = name or self.content.name
query = self.term.prompt_input('Search {0}:'.format(name))
query = self.term.prompt_input('Search {0}: '.format(name))
if not query:
return
@@ -69,10 +70,27 @@ 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 page: /')
if name is not None:
self.refresh_content(order='ignore', name=name)
@SubredditController.register(Command('SUBREDDIT_FRONTPAGE'))
def show_frontpage(self):
"""
If on a subreddit, remember it and head back to the front page.
If this was pressed on the front page, go back to the last subreddit.
"""
if not self.content.name == '/r/front':
target = '/r/front'
self._toggled_subreddit = self.content.name
else:
target = self._toggled_subreddit
# target still may be empty string if this command hasn't yet been used
if target is not None:
self.refresh_content(order='ignore', name=target)
@SubredditController.register(Command('SUBREDDIT_OPEN'))
def open_submission(self, url=None):
"Select the current submission to view posts"
@@ -157,8 +175,27 @@ class SubredditPage(Page):
"Open user subscriptions page"
with self.term.loader('Loading subscriptions'):
page = SubscriptionPage(
self.reddit, self.term, self.config, self.oauth)
page = SubscriptionPage(self.reddit, self.term, self.config,
self.oauth, content_type='subreddit')
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.subreddit_data is not None:
self.refresh_content(order='ignore',
name=page.subreddit_data['name'])
@SubredditController.register(Command('SUBREDDIT_OPEN_MULTIREDDITS'))
@logged_in
def open_multireddit_subscriptions(self):
"Open user multireddit subscriptions page"
with self.term.loader('Loading multireddits'):
page = SubscriptionPage(self.reddit, self.term, self.config,
self.oauth, content_type='multireddit')
if self.term.loader.exception:
return

View File

@@ -14,12 +14,14 @@ class SubscriptionController(PageController):
class SubscriptionPage(Page):
def __init__(self, reddit, term, config, oauth):
def __init__(self, reddit, term, config, oauth, content_type='subreddit'):
super(SubscriptionPage, self).__init__(reddit, term, config, oauth)
self.controller = SubscriptionController(self, keymap=config.keymap)
self.content = SubscriptionContent.from_user(reddit, term.loader)
self.content = SubscriptionContent.from_user(
reddit, term.loader, content_type)
self.nav = Navigator(self.content.get)
self.content_type = content_type
self.subreddit_data = None
@SubscriptionController.register(Command('REFRESH'))
@@ -32,8 +34,8 @@ class SubscriptionPage(Page):
return
with self.term.loader():
self.content = SubscriptionContent.from_user(self.reddit,
self.term.loader)
self.content = SubscriptionContent.from_user(
self.reddit, self.term.loader, self.content_type)
if not self.term.loader.exception:
self.nav = Navigator(self.content.get)
@@ -70,4 +72,4 @@ class SubscriptionPage(Page):
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)
self.term.add_line(win, text, row, 1)

View File

@@ -387,7 +387,7 @@ class Terminal(object):
try:
with self.suspend():
p = subprocess.Popen([pager], stdin=subprocess.PIPE)
p.stdin.write(self.clean(data))
p.stdin.write(data.encode('utf-8'))
p.stdin.close()
try:
p.wait()
@@ -456,15 +456,14 @@ class Terminal(object):
urlview = os.getenv('RTV_URLVIEWER') or 'urlview'
try:
with self.suspend():
p = subprocess.Popen([urlview],
stdin=subprocess.PIPE)
p = subprocess.Popen([urlview], stdin=subprocess.PIPE)
try:
p.communicate(input=six.b(data))
p.communicate(input=data.encode('utf-8'))
except KeyboardInterrupt:
p.terminate()
except OSError:
self.show_notification(
'Could not open urls with {}'.format(urlview))
'Failed to open {0}'.format(urlview))
def text_input(self, window, allow_resize=False):
"""