diff --git a/rtv/submission.py b/rtv/submission.py index eb1ec3f..2ff5097 100644 --- a/rtv/submission.py +++ b/rtv/submission.py @@ -147,7 +147,8 @@ class SubmissionDisplay(object): getattr(comment, 'author') else '[deleted]') date = utils.humanize_timestamp(comment.created_utc) score = submission.score - color_attr = curses.color_pair(curses.COLOR_BLUE) + color_attr = (curses.color_pair(curses.COLOR_GREEN) if comment.is_author + else curses.color_pair(curses.COLOR_BLUE)) win.addstr(0, 1, author, curses.A_UNDERLINE|color_attr) win.addstr(' {} points {}'.format(score, date), curses.A_BOLD) @@ -174,7 +175,7 @@ class SubmissionDisplay(object): return True - def draw_page(self, submission, index=-1): + def draw_page(self, submission, comments, index=-1): """ Draw the comments page starting at the given index. """ @@ -190,13 +191,14 @@ class SubmissionDisplay(object): self._draw_post(submission) index += 1 - comments = utils.flatten_tree(submission.comments) for comment in comments[index:]: try: if isinstance(comment, praw.objects.MoreComments): self._draw_more_comments(comment) else: + comment.is_author = (comment.author == submission.author) self._draw_comment(comment) + except OOBError: break @@ -211,9 +213,9 @@ class SubmissionController(object): self._index = -1 self._cursor = 0 - def loop(self, submission): + def loop(self, submission, comments): - self.display.draw_page(submission, self._index) + self.display.draw_page(submission, comments, self._index) while True: @@ -227,17 +229,20 @@ class SubmissionController(object): else: continue - self.display.draw_page(submission, self._index) + self.display.draw_page(submission, comments, self._index) if __name__ == '__main__': r = praw.Reddit(user_agent='reddit terminal viewer (rtv) v0.0') + r.config.decode_html_entities = True submissions = r.get_subreddit('all').get_hot(limit=5) submission = submissions.next() + comments = utils.flatten_tree(submission.comments) + with utils.curses_session() as stdscr: display = SubmissionDisplay(stdscr) controller = SubmissionController(display) - controller.loop(submission) \ No newline at end of file + controller.loop(submission, comments) \ No newline at end of file diff --git a/rtv/subreddit.py b/rtv/subreddit.py new file mode 100644 index 0000000..226e138 --- /dev/null +++ b/rtv/subreddit.py @@ -0,0 +1,113 @@ +import praw +import textwrap +import curses + +from utils import humanize_timestamp, flatten_tree, clean + +def strip_submission(sub): + "Grab info from a PRAW submission and prep for display." + + out = {} + out['title'] = clean(sub.title) + out['created'] = humanize_timestamp(sub.created_utc, long=False) + out['comments'] = '{} comments'.format(sub.num_comments) + out['score'] = '{} pts'.format(sub.score) + out['author'] = clean(sub.author.name) + out['subreddit'] = clean(sub.subreddit.url) + out['url'] = ('(selfpost)' if sub.url.startswith('http://www.reddit.com/r/') + else clean(sub.url)) + + return out + +def draw_submission(win, data, top_down=True): + "Draw a submission in the given window." + + win.erase() + n_rows, n_cols = win.getmaxyx() + n_cols -= 1 # Leave space for the cursor on the first line + + # Handle the case where the window is not large enough to fit the data. + # Print as many rows as possible, either from the top down of the bottom up. + valid_rows = range(0, n_rows) + offset = 0 if top_down else -(data['n_rows'] - n_rows) + + n_title = len(data['split_title']) + for row, text in enumerate(data['split_title'], start=offset): + if row in valid_rows: + win.addstr(row, 1, text) + + row = n_title + if row in valid_rows: + win.addnstr(row, 1, '{url}'.format(**data), n_cols) + + row = n_title + 1 + if row in valid_rows: + win.addnstr(row, 1, '{created} {comments} {score}'.format(**data), n_cols) + + row = n_title + 2 + if row in valid_rows: + win.addnstr(row, 1, '{author} {subreddit}'.format(**data), n_cols) + + +class SubmissionGenerator(object): + """ + Grab submissions from PRAW lazily and store in an internal list for repeat + access. + """ + + def __init__(self): + + self.r = praw.Reddit(user_agent='reddit terminal viewer (rtv) v0.0') + self.r.config.decode_html_entities = True + + self._submissions = self.r.get_front_page(limit=None) + self._submission_data = [] + + def get(self, index, n_cols): + + assert(index >= 0) + + while index >= len(self._submission_data): + data = strip_submission(self._submissions.next()) + self._submission_data.append(data) + + # Modifies the original original dict, faster than copying + out = self._submission_data[index] + out['split_title'] = textwrap.wrap(out['title'], width=n_cols) + out['n_rows'] = len(out['split_title']) + 3 + + return out + + def iterate(self, index, n_cols): + + while True: + yield self.get(index, n_cols) + index += 1 + + + +def draw_subreddit(stdscr): + + generator = SubmissionGenerator() + + main_window = stdscr.derwin(1, 0) + main_window.erase() + max_rows, max_cols = main_window.getmaxyx() + + submission_i, current_row = 0, 0 + for data in generator.iterate(submission_i, max_cols-1): + n_rows = min(max_rows-current_row, data['n_rows']) + sub_window = main_window.derwin(n_rows, max_cols, current_row, 0) + draw_submission(sub_window, data) + sub_window.refresh() # Debugging + current_row += n_rows + 1 + if current_row >= max_rows: + break + + main_window.refresh() + main_window.getch() + +if __name__ == '__main__': + + #draw_submissions(None) + curses.wrapper(draw_subreddit) \ No newline at end of file diff --git a/rtv/utils.py b/rtv/utils.py index 9f009e1..fad4a79 100644 --- a/rtv/utils.py +++ b/rtv/utils.py @@ -52,28 +52,28 @@ def curses_session(): curses.nocbreak() curses.endwin() -def humanize_timestamp(utc_timestamp): +def humanize_timestamp(utc_timestamp, long=True): """ Convert a utc timestamp into a human readable time relative to now. """ timedelta = datetime.utcnow() - datetime.utcfromtimestamp(utc_timestamp) seconds = int(timedelta.total_seconds()) if seconds < 60: - return 'moments ago' + return 'moments ago' if long else '0min' minutes = seconds / 60 if minutes < 60: - return '{} minutes ago'.format(minutes) + return '%d' % minutes + (' minutes ago' if long else 'min') hours = minutes / 60 if hours < 24: - return '{} hours ago'.format(hours) + return '%d' % hours + (' hours ago' if long else 'hr') days = hours / 24 if days < 30: - return '{} days ago'.format(days) + return '%d' % days + (' days ago' if long else 'day') months = days / 30.4 if months < 12: - return '{} months ago'.format(months) + return '%d' % months + (' months ago' if long else 'month') years = months / 12 - return '{} years ago'.format(years) + return '%d' % years + (' years ago' if long else 'yr') def flatten_tree(tree): @@ -95,4 +95,9 @@ def flatten_tree(tree): n.nested_level = item.nested_level + 1 stack[0:0] = nested retval.append(item) - return retval \ No newline at end of file + return retval + + +def clean(unicode_string): + "Convert unicode string into ascii-safe characters." + return unicode_string.encode('ascii', 'replace').replace('\\', '') \ No newline at end of file