Added folding comment tree and loading more comments.

This commit is contained in:
Michael Lazar
2015-01-29 23:51:46 -08:00
parent 606f41fd67
commit 6ef1abadc3
4 changed files with 96 additions and 43 deletions

View File

@@ -3,8 +3,7 @@ import praw
from utils import clean, strip_subreddit_url, humanize_timestamp from utils import clean, strip_subreddit_url, humanize_timestamp
# TODO: rename, ... container? class ContainerBase(object):
class BaseContent(object):
@staticmethod @staticmethod
def strip_praw_comment(comment): def strip_praw_comment(comment):
@@ -19,6 +18,7 @@ class BaseContent(object):
if isinstance(comment, praw.objects.MoreComments): if isinstance(comment, praw.objects.MoreComments):
data['type'] = 'MoreComments' data['type'] = 'MoreComments'
data['count'] = comment.count
data['body'] = 'More comments [{}]'.format(comment.count) data['body'] = 'More comments [{}]'.format(comment.count)
else: else:
data['type'] = 'Comment' data['type'] = 'Comment'
@@ -55,30 +55,7 @@ class BaseContent(object):
return data return data
@staticmethod class SubredditContainer(ContainerBase):
def flatten_comments(comments, initial_level=0):
"""
Flatten a PRAW comment tree while preserving the nested level of each
comment via the `nested_level` attribute.
"""
stack = comments[:]
for item in stack:
item.nested_level = initial_level
retval = []
while stack:
item = stack.pop(0)
nested = getattr(item, 'replies', None)
if nested:
for n in nested:
n.nested_level = item.nested_level + 1
stack[0:0] = nested
retval.append(item)
return retval
class SubredditContent(BaseContent):
""" """
Grabs a subreddit from PRAW and lazily stores submissions to an internal Grabs a subreddit from PRAW and lazily stores submissions to an internal
list for repeat access. list for repeat access.
@@ -152,7 +129,7 @@ class SubredditContent(BaseContent):
self.display_name = '/r/' + self.subreddit self.display_name = '/r/' + self.subreddit
class SubmissionContent(BaseContent): class SubmissionContainer(ContainerBase):
""" """
Grabs a submission from PRAW and lazily store comments to an internal Grabs a submission from PRAW and lazily store comments to an internal
list for repeat access and to allow expanding and hiding comments. list for repeat access and to allow expanding and hiding comments.
@@ -166,7 +143,6 @@ class SubmissionContent(BaseContent):
self.display_name = None self.display_name = None
self._submission_data = None self._submission_data = None
self._comments = None
self._comment_data = None self._comment_data = None
self.reset() self.reset()
@@ -201,10 +177,52 @@ class SubmissionContent(BaseContent):
return data return data
def iterate(self, index, step, n_cols): def toggle(self, index):
"""
Toggle the state of the object at the given index.
If it is a comment, pack it into a hidden comment.
If it is a hidden comment, unpack it.
If it is more comments, load the comments.
"""
data = self.get(index)
if data['type'] == 'Comment':
cache = [data]
count = 1
for d in self.iterate(index+1, 1):
if d['level'] <= data['level']:
break
count += d.get('count', 1)
cache.append(d)
comment = {}
comment['type'] = 'HiddenComment'
comment['cache'] = cache
comment['count'] = count
comment['level'] = data['level']
comment['body'] = 'Hidden [{}]'.format(count)
self._comment_data[index:index+len(cache)] = [comment]
elif data['type'] == 'HiddenComment':
self._comment_data[index:index+1] = data['cache']
elif data['type'] == 'MoreComments':
comments = data['object'].comments()
comments = self.flatten_comments(comments, root_level=data['level'])
comment_data = [self.strip_praw_comment(c) for c in comments]
self._comment_data[index:index+1] = comment_data
else:
raise ValueError('% type not recognized' % data['type'])
def iterate(self, index, step, n_cols=70):
while True: while True:
yield self.get(index, n_cols) yield self.get(index, n_cols=n_cols)
index += step index += step
def reset(self): def reset(self):
@@ -216,5 +234,27 @@ class SubmissionContent(BaseContent):
self._submission_data = self.strip_praw_submission(self.submission) self._submission_data = self.strip_praw_submission(self.submission)
self.display_name = self._submission_data['permalink'] self.display_name = self._submission_data['permalink']
self._comments = self.flatten_comments(self.submission.comments) comments = self.flatten_comments(self.submission.comments)
self._comment_data = [self.strip_praw_comment(c) for c in self._comments] self._comment_data = [self.strip_praw_comment(c) for c in comments]
@staticmethod
def flatten_comments(comments, root_level=0):
"""
Flatten a PRAW comment tree while preserving the nested level of each
comment via the `nested_level` attribute.
"""
stack = comments[:]
for item in stack:
item.nested_level = root_level
retval = []
while stack:
item = stack.pop(0)
nested = getattr(item, 'replies', None)
if nested:
for n in nested:
n.nested_level = item.nested_level + 1
stack[0:0] = nested
retval.append(item)
return retval

View File

@@ -1,24 +1,25 @@
import argparse import argparse
import praw import praw
from utils import curses_session from utils import curses_session
from content_generators import SubredditContent from content import SubredditContainer
from subreddit_viewer import SubredditViewer from subreddit_viewer import SubredditViewer
parser = argparse.ArgumentParser(description='Reddit Terminal Viewer (RTV)') parser = argparse.ArgumentParser(description='Reddit Terminal Viewer')
parser.add_argument('-u', '--username', help='reddit username') parser.add_argument('-s', dest='subreddit', default='front', help='subreddit name')
parser.add_argument('-p', '--password', help='reddit password') parser.add_argument('-l', dest='link', help='full link to a specific submission')
parser.add_argument('-s', '--subreddit', default='front', help='subreddit name') group = parser.add_argument_group('authentication (optional)')
parser.add_argument('-l', '--link', help='full link to a specific submission') group.add_argument('-u', dest='username', help='reddit username')
group.add_argument('-p', dest='password', help='reddit password')
def main(args): def main(args):
r = praw.Reddit(user_agent='reddit terminal viewer (rtv) v0.0') r = praw.Reddit(user_agent='reddit terminal viewer v0.0')
if args.username and args.password: if args.username and args.password:
r.login(args.username, args.password) r.login(args.username, args.password)
with curses_session() as stdscr: with curses_session() as stdscr:
content = SubredditContent(r, subreddit=args.subreddit) content = SubredditContainer(r, subreddit=args.subreddit)
viewer = SubredditViewer(stdscr, content) viewer = SubredditViewer(stdscr, content)
viewer.loop() viewer.loop()

View File

@@ -2,7 +2,7 @@ import praw
import curses import curses
import sys import sys
from content_generators import SubmissionContent, SubredditContent from content import SubmissionContainer, SubredditContainer
from utils import curses_session, LoadScreen from utils import curses_session, LoadScreen
from viewer import BaseViewer from viewer import BaseViewer
@@ -33,6 +33,10 @@ class SubmissionViewer(BaseViewer):
elif cmd in (curses.KEY_F5, ord('r')): elif cmd in (curses.KEY_F5, ord('r')):
self.refresh_content() self.refresh_content()
# Show / hide a comment tree
elif cmd == ord(' '):
self.toggle_comment()
elif cmd == curses.KEY_RESIZE: elif cmd == curses.KEY_RESIZE:
self.draw() self.draw()
@@ -47,6 +51,11 @@ class SubmissionViewer(BaseViewer):
else: else:
curses.beep() curses.beep()
def toggle_comment(self):
self.content.toggle(self.nav.absolute_index)
self.draw()
def refresh_content(self): def refresh_content(self):
self.add_loading() self.add_loading()
@@ -70,6 +79,9 @@ class SubmissionViewer(BaseViewer):
if data['type'] == 'MoreComments': if data['type'] == 'MoreComments':
self.draw_more_comments(win, data) self.draw_more_comments(win, data)
elif data['type'] == 'HiddenComment':
self.draw_more_comments(win, data)
elif data['type'] == 'Comment': elif data['type'] == 'Comment':
self.draw_comment(win, data, inverted=inverted) self.draw_comment(win, data, inverted=inverted)

View File

@@ -3,7 +3,7 @@ import textwrap
import curses import curses
import sys import sys
from content_generators import SubredditContent, SubmissionContent from content import SubredditContainer, SubmissionContainer
from submission_viewer import SubmissionViewer from submission_viewer import SubmissionViewer
from viewer import BaseViewer from viewer import BaseViewer
from utils import curses_session, text_input, LoadScreen from utils import curses_session, text_input, LoadScreen
@@ -75,7 +75,7 @@ class SubredditViewer(BaseViewer):
with LoadScreen(self.stdscr): with LoadScreen(self.stdscr):
submission = self.content.get(self.nav.absolute_index)['object'] submission = self.content.get(self.nav.absolute_index)['object']
content = SubmissionContent(submission) content = SubmissionContainer(submission)
viewer = SubmissionViewer(self.stdscr, content) viewer = SubmissionViewer(self.stdscr, content)
viewer.loop() viewer.loop()