Added folding comment tree and loading more comments.
This commit is contained in:
@@ -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
|
||||||
17
rtv/main.py
17
rtv/main.py
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user