diff --git a/rtv/content_generators.py b/rtv/content.py similarity index 78% rename from rtv/content_generators.py rename to rtv/content.py index 57608ca..819fade 100644 --- a/rtv/content_generators.py +++ b/rtv/content.py @@ -3,8 +3,7 @@ import praw from utils import clean, strip_subreddit_url, humanize_timestamp -# TODO: rename, ... container? -class BaseContent(object): +class ContainerBase(object): @staticmethod def strip_praw_comment(comment): @@ -19,6 +18,7 @@ class BaseContent(object): if isinstance(comment, praw.objects.MoreComments): data['type'] = 'MoreComments' + data['count'] = comment.count data['body'] = 'More comments [{}]'.format(comment.count) else: data['type'] = 'Comment' @@ -55,30 +55,7 @@ class BaseContent(object): return data - @staticmethod - 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): +class SubredditContainer(ContainerBase): """ Grabs a subreddit from PRAW and lazily stores submissions to an internal list for repeat access. @@ -152,7 +129,7 @@ class SubredditContent(BaseContent): self.display_name = '/r/' + self.subreddit -class SubmissionContent(BaseContent): +class SubmissionContainer(ContainerBase): """ Grabs a submission from PRAW and lazily store comments to an internal list for repeat access and to allow expanding and hiding comments. @@ -166,7 +143,6 @@ class SubmissionContent(BaseContent): self.display_name = None self._submission_data = None - self._comments = None self._comment_data = None self.reset() @@ -201,10 +177,52 @@ class SubmissionContent(BaseContent): 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: - yield self.get(index, n_cols) + yield self.get(index, n_cols=n_cols) index += step def reset(self): @@ -216,5 +234,27 @@ class SubmissionContent(BaseContent): self._submission_data = self.strip_praw_submission(self.submission) self.display_name = self._submission_data['permalink'] - self._comments = self.flatten_comments(self.submission.comments) - self._comment_data = [self.strip_praw_comment(c) for c in self._comments] \ No newline at end of file + comments = self.flatten_comments(self.submission.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 \ No newline at end of file diff --git a/rtv/main.py b/rtv/main.py index 4116a5a..6c296d1 100644 --- a/rtv/main.py +++ b/rtv/main.py @@ -1,24 +1,25 @@ import argparse import praw from utils import curses_session -from content_generators import SubredditContent +from content import SubredditContainer from subreddit_viewer import SubredditViewer -parser = argparse.ArgumentParser(description='Reddit Terminal Viewer (RTV)') -parser.add_argument('-u', '--username', help='reddit username') -parser.add_argument('-p', '--password', help='reddit password') -parser.add_argument('-s', '--subreddit', default='front', help='subreddit name') -parser.add_argument('-l', '--link', help='full link to a specific submission') +parser = argparse.ArgumentParser(description='Reddit Terminal Viewer') +parser.add_argument('-s', dest='subreddit', default='front', help='subreddit name') +parser.add_argument('-l', dest='link', help='full link to a specific submission') +group = parser.add_argument_group('authentication (optional)') +group.add_argument('-u', dest='username', help='reddit username') +group.add_argument('-p', dest='password', help='reddit password') 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: r.login(args.username, args.password) with curses_session() as stdscr: - content = SubredditContent(r, subreddit=args.subreddit) + content = SubredditContainer(r, subreddit=args.subreddit) viewer = SubredditViewer(stdscr, content) viewer.loop() diff --git a/rtv/submission_viewer.py b/rtv/submission_viewer.py index 37152f7..a6e8c15 100644 --- a/rtv/submission_viewer.py +++ b/rtv/submission_viewer.py @@ -2,7 +2,7 @@ import praw import curses import sys -from content_generators import SubmissionContent, SubredditContent +from content import SubmissionContainer, SubredditContainer from utils import curses_session, LoadScreen from viewer import BaseViewer @@ -33,6 +33,10 @@ class SubmissionViewer(BaseViewer): elif cmd in (curses.KEY_F5, ord('r')): self.refresh_content() + # Show / hide a comment tree + elif cmd == ord(' '): + self.toggle_comment() + elif cmd == curses.KEY_RESIZE: self.draw() @@ -47,6 +51,11 @@ class SubmissionViewer(BaseViewer): else: curses.beep() + def toggle_comment(self): + + self.content.toggle(self.nav.absolute_index) + self.draw() + def refresh_content(self): self.add_loading() @@ -70,6 +79,9 @@ class SubmissionViewer(BaseViewer): if data['type'] == 'MoreComments': self.draw_more_comments(win, data) + elif data['type'] == 'HiddenComment': + self.draw_more_comments(win, data) + elif data['type'] == 'Comment': self.draw_comment(win, data, inverted=inverted) diff --git a/rtv/subreddit_viewer.py b/rtv/subreddit_viewer.py index 9571bcf..70c7e18 100644 --- a/rtv/subreddit_viewer.py +++ b/rtv/subreddit_viewer.py @@ -3,7 +3,7 @@ import textwrap import curses import sys -from content_generators import SubredditContent, SubmissionContent +from content import SubredditContainer, SubmissionContainer from submission_viewer import SubmissionViewer from viewer import BaseViewer from utils import curses_session, text_input, LoadScreen @@ -75,7 +75,7 @@ class SubredditViewer(BaseViewer): with LoadScreen(self.stdscr): submission = self.content.get(self.nav.absolute_index)['object'] - content = SubmissionContent(submission) + content = SubmissionContainer(submission) viewer = SubmissionViewer(self.stdscr, content) viewer.loop()