From 79a0e5b16ea2beda26353ed5addbd7b73ba5ef12 Mon Sep 17 00:00:00 2001 From: Michael Lazar Date: Sun, 25 Jan 2015 13:00:35 -0800 Subject: [PATCH] Moved utility functions, enabled window resizing. --- rtv/content_generators.py | 71 +++++++++++++-------------------------- rtv/subreddit.py | 23 +++++++------ rtv/utils.py | 49 +++++++++++++++++++++++++-- rtv/viewer.py | 5 +-- 4 files changed, 87 insertions(+), 61 deletions(-) diff --git a/rtv/content_generators.py b/rtv/content_generators.py index 1c4e3fc..316d860 100644 --- a/rtv/content_generators.py +++ b/rtv/content_generators.py @@ -1,51 +1,8 @@ -from datetime import datetime, timedelta import textwrap -def clean(unicode_string): - """ - Convert unicode string into ascii-safe characters. - """ +from utils import clean, strip_subreddit_url, humanize_timestamp - return unicode_string.encode('ascii', 'replace').replace('\\', '') - - -def strip_subreddit_url(permalink): - """ - Grab the subreddit from the permalink because submission.subreddit.url - makes a seperate call to the API. - """ - - subreddit = clean(permalink).split('/')[4] - return '/r/{}'.format(subreddit) - - -def humanize_timestamp(utc_timestamp, verbose=False): - """ - Convert a utc timestamp into a human readable relative-time. - """ - - timedelta = datetime.utcnow() - datetime.utcfromtimestamp(utc_timestamp) - - seconds = int(timedelta.total_seconds()) - if seconds < 60: - return 'moments ago' if verbose else '0min' - minutes = seconds / 60 - if minutes < 60: - return ('%d minutes ago' % minutes) if verbose else ('%dmin' % minutes) - hours = minutes / 60 - if hours < 24: - return ('%d hours ago' % hours) if verbose else ('%dhr' % hours) - days = hours / 24 - if days < 30: - return ('%d days ago' % days) if verbose else ('%dday' % days) - months = days / 30.4 - if months < 12: - return ('%d months ago' % months) if verbose else ('%dmonth' % months) - years = months / 12 - return ('%d years ago' % years) if verbose else ('%dyr' % years) - - -class SubmissionGenerator(object): +class SubmissionContent(object): """ Facilitates navigating through the comments in a PRAW submission. """ @@ -53,6 +10,8 @@ class SubmissionGenerator(object): def __init__(self, submission): self.submission = submission + self._comments = [] + @staticmethod def flatten_comments(submission): @@ -76,8 +35,24 @@ class SubmissionGenerator(object): retval.append(item) return retval + @staticmethod + def strip_praw_comment(comment): + """ + Parse through a submission comment and return a dict with data ready to + be displayed through the terminal. + """ -class SubredditGenerator(object): + data = {} + data['body'] = clean(comment.body) + data['created'] = humanize_timestamp(comment.created_utc) + data['score'] = '{} pts'.format(comment.score) + data['author'] = (clean(comment.author.name) if + getattr(comment, 'author') else '[deleted]') + + return data + + +class SubredditContent(object): """ Grabs a subreddit from PRAW and lazily stores submissions to an internal list for repeat access. @@ -110,10 +85,12 @@ class SubredditGenerator(object): data = {} data['title'] = clean(sub.title) + data['text'] = clean(sub.selftext) data['created'] = humanize_timestamp(sub.created_utc) data['comments'] = '{} comments'.format(sub.num_comments) data['score'] = '{} pts'.format(sub.score) - data['author'] = clean(sub.author.name) + data['author'] = (clean(sub.author.name) if getattr(sub, 'author') + else '[deleted]') data['subreddit'] = strip_subreddit_url(sub.permalink) data['url'] = ('(selfpost)' if is_selfpost(sub.url) else clean(sub.url)) diff --git a/rtv/subreddit.py b/rtv/subreddit.py index b4c4866..dd48c16 100644 --- a/rtv/subreddit.py +++ b/rtv/subreddit.py @@ -2,7 +2,7 @@ import praw import textwrap import curses -from content_generators import SubredditGenerator +from content_generators import SubredditContent from utils import curses_session from viewer import BaseViewer @@ -11,16 +11,14 @@ class SubredditViewer(BaseViewer): def __init__(self, stdscr, subreddit_content): self.stdscr = stdscr - - self._n_rows = None - self._n_cols = None self._title_window = None self._content_window = None super(SubredditViewer, self).__init__(subreddit_content) - self.draw() def loop(self): + + self.draw() while True: cmd = self.stdscr.getch() @@ -44,6 +42,9 @@ class SubredditViewer(BaseViewer): self.stdscr.clear() self.draw() + elif cmd == curses.KEY_RESIZE: + self.draw() + # Quit elif cmd == ord('q'): break @@ -54,8 +55,8 @@ class SubredditViewer(BaseViewer): def draw(self): # Refresh window bounds incase the screen has been resized - self._n_rows, self._n_cols = self.stdscr.getmaxyx() - self._title_window = self.stdscr.derwin(1, self._n_cols, 0, 0) + n_rows, n_cols = self.stdscr.getmaxyx() + self._title_window = self.stdscr.derwin(1, n_cols, 0, 0) self._content_window = self.stdscr.derwin(1, 0) self.draw_header() @@ -79,7 +80,7 @@ class SubredditViewer(BaseViewer): # and draw upwards. current_row = n_rows if inverted else 0 available_rows = n_rows - for data in self.content.iterate(page_index, step, n_cols-1): + for data in self.content.iterate(page_index, step, n_cols-2): window_rows = min(available_rows, data['n_rows']) start = current_row - window_rows if inverted else current_row window = self._content_window.derwin(window_rows, n_cols, start, 0) @@ -94,9 +95,11 @@ class SubredditViewer(BaseViewer): def draw_header(self): + n_rows, n_cols = self._title_window.getmaxyx() sub_name = self.content.display_name + self._title_window.erase() - self._title_window.addnstr(0, 0, sub_name, self._n_cols) + self._title_window.addnstr(0, 0, sub_name, n_cols) self._title_window.refresh() @staticmethod @@ -130,7 +133,7 @@ def main(): with curses_session() as stdscr: r = praw.Reddit(user_agent='reddit terminal viewer (rtv) v0.0') - generator = SubredditGenerator(r) + generator = SubredditContent(r) viewer = SubredditViewer(stdscr, generator) viewer.loop() diff --git a/rtv/utils.py b/rtv/utils.py index f90e264..852bab2 100644 --- a/rtv/utils.py +++ b/rtv/utils.py @@ -1,6 +1,51 @@ -import curses +from datetime import datetime, timedelta from contextlib import contextmanager +import curses + +def clean(unicode_string): + """ + Convert unicode string into ascii-safe characters. + """ + + return unicode_string.encode('ascii', 'replace').replace('\\', '') + + +def strip_subreddit_url(permalink): + """ + Grab the subreddit from the permalink because submission.subreddit.url + makes a seperate call to the API. + """ + + subreddit = clean(permalink).split('/')[4] + return '/r/{}'.format(subreddit) + + +def humanize_timestamp(utc_timestamp, verbose=False): + """ + Convert a utc timestamp into a human readable relative-time. + """ + + timedelta = datetime.utcnow() - datetime.utcfromtimestamp(utc_timestamp) + + seconds = int(timedelta.total_seconds()) + if seconds < 60: + return 'moments ago' if verbose else '0min' + minutes = seconds / 60 + if minutes < 60: + return ('%d minutes ago' % minutes) if verbose else ('%dmin' % minutes) + hours = minutes / 60 + if hours < 24: + return ('%d hours ago' % hours) if verbose else ('%dhr' % hours) + days = hours / 24 + if days < 30: + return ('%d days ago' % days) if verbose else ('%dday' % days) + months = days / 30.4 + if months < 12: + return ('%d months ago' % months) if verbose else ('%dmonth' % months) + years = months / 12 + return ('%d years ago' % years) if verbose else ('%dyr' % years) + @contextmanager def curses_session(): @@ -31,7 +76,7 @@ def curses_session(): pass # Hide blinking cursor - # curses.curs_set(0) + curses.curs_set(0) # Initialize color pairs - colored text on the default background curses.init_pair(1, curses.COLOR_RED, -1) diff --git a/rtv/viewer.py b/rtv/viewer.py index 1eed68e..6ce56a6 100644 --- a/rtv/viewer.py +++ b/rtv/viewer.py @@ -2,7 +2,7 @@ import curses class Navigator(object): """ - Handles cursor movement and screen paging. + Handles math behind cursor movement and screen paging. """ def __init__( @@ -67,13 +67,14 @@ class Navigator(object): class BaseViewer(object): """ - Base terminal viewer incorperating a cursor to navigate content + Base terminal viewer incorperates a cursor to navigate content """ def __init__(self, content): self.content = content self.nav = Navigator(self.content.get) + self._subwindows = None def draw_content(self):