diff --git a/rtv/content_generators.py b/rtv/content_generators.py new file mode 100644 index 0000000..7fe2056 --- /dev/null +++ b/rtv/content_generators.py @@ -0,0 +1,135 @@ +from datetime import datetime, timedelta + + +def clean(unicode_string): + """ + Convert unicode string into ascii-safe characters. + """ + + return unicode_string.encode('ascii', 'replace').replace('\\', '') + + +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): + """ + Facilitates navigating through the comments in a PRAW submission. + """ + + def __init__(self, submission): + + self.submission = submission + + @staticmethod + def flatten_comments(submission): + """ + Flatten a PRAW comment tree while preserving the nested level of each + comment via the `nested_level` attribute. + """ + + stack = submission[:] + for item in stack: + item.nested_level = 0 + + 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 SubredditGenerator(object): + """ + Grabs a subreddit from PRAW lazily and store in an internal list for repeat + access. + + params: + session (praw.Reddit): Active reddit connection + subreddit (str): Subreddit to connect to, None defaults to front page. + """ + + def __init__(self, reddit_session, subreddit=None): + + self.r = reddit_session + self.r.config.decode_html_entities = True + + if subreddit is None: + self._submissions = self.r.get_front_page(limit=None) + else: + self._submissions = self.r.get_subreddit(subreddit, limit=None) + + self._submission_data = [] + + @staticmethod + def strip_praw_submission(sub): + """ + Parse through a submission and return a dict with data ready to be + displayed through the terminal. + """ + + is_selfpost = lambda s: s.startswith('http://www.reddit.com/r/') + + data = {} + data['title'] = clean(sub.title) + 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['subreddit'] = clean(sub.subreddit.url) + data['url'] = ('(selfpost)' if is_selfpost(sub.url) else clean(sub.url)) + + return data + + def get(self, index, n_cols): + """ + Grab the `i`th submission, with the title field formatted to fit inside + of a window of width `n` + """ + + assert(index >= 0) + + while index >= len(self._submission_data): + data = self._strip_praw_submission(self._submissions.next()) + self._submission_data.append(data) + + # Modifies the original original dict, faster than copying + data = self._submission_data[index] + data['split_title'] = textwrap.wrap(data['title'], width=n_cols) + data['n_rows'] = len(data['split_title']) + 3 + + return data + + def iterate(self, index, n_cols): + + while True: + yield self.get(index, n_cols) + index += 1 \ No newline at end of file diff --git a/rtv/subreddit.py b/rtv/subreddit.py index 226e138..2f8375e 100644 --- a/rtv/subreddit.py +++ b/rtv/subreddit.py @@ -2,22 +2,44 @@ import praw import textwrap import curses -from utils import humanize_timestamp, flatten_tree, clean +from content_generators import SubredditGenerator -def strip_submission(sub): - "Grab info from a PRAW submission and prep for display." +class SubredditViewer(object): - 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)) + def __init__(self, stdscr): + self.stdscr = stdscr - return out + def loop(self): + + while True: + cmd = self.stdscr.getch() + + # Move cursor up one submission + if cmd == curses.KEY_UP: + self.move_cursor(-1) + + # Move cursor down one submission + elif cmd == curses.KEY_DOWN: + self.move_cursor(1) + + # View submission + elif cmd in (curses.KEY_RIGHT, ord(' ')): + pass + + # Enter edit mode to change subreddit + elif cmd == ord('/'): + pass + + # Refresh page + elif cmd in (curses.KEY_F5, ord('r')): + pass + + # Quit + elif cmd == ord('q'): + pass + + else: + curses.beep() def draw_submission(win, data, top_down=True): "Draw a submission in the given window." @@ -49,46 +71,26 @@ def draw_submission(win, data, top_down=True): 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 focus_submission(win): + "Add a vertical column of reversed background on left side of the window" - def __init__(self): + n_rows, n_cols = win.getmaxyx() + for row in xrange(n_rows): + win.chgat(row, 0, 1, curses.A_REVERSE) - 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 unfocus_submission(win): + "Clear the vertical column" + n_rows, n_cols = win.getmaxyx() + for row in xrange(n_rows): + win.chgat(row, 0, 1, curses.A_NORMAL) def draw_subreddit(stdscr): - generator = SubmissionGenerator() + r = praw.Reddit(user_agent='reddit terminal viewer (rtv) v0.0') + generator = SubredditGenerator(r) main_window = stdscr.derwin(1, 0) main_window.erase() @@ -99,6 +101,7 @@ def draw_subreddit(stdscr): 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) + focus_submission(sub_window) sub_window.refresh() # Debugging current_row += n_rows + 1 if current_row >= max_rows: diff --git a/rtv/utils.py b/rtv/utils.py index fad4a79..de085ae 100644 --- a/rtv/utils.py +++ b/rtv/utils.py @@ -1,4 +1,3 @@ -from datetime import datetime, timedelta import curses from contextlib import contextmanager @@ -52,52 +51,3 @@ def curses_session(): curses.nocbreak() curses.endwin() -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' if long else '0min' - minutes = seconds / 60 - if minutes < 60: - return '%d' % minutes + (' minutes ago' if long else 'min') - hours = minutes / 60 - if hours < 24: - return '%d' % hours + (' hours ago' if long else 'hr') - days = hours / 24 - if days < 30: - return '%d' % days + (' days ago' if long else 'day') - months = days / 30.4 - if months < 12: - return '%d' % months + (' months ago' if long else 'month') - years = months / 12 - return '%d' % years + (' years ago' if long else 'yr') - - -def flatten_tree(tree): - """ - Flatten a PRAW comment tree while preserving the nested level of each - comment via the `nested_level` attribute. - """ - - stack = tree[:] - for item in stack: - item.nested_level = 0 - - 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 - - -def clean(unicode_string): - "Convert unicode string into ascii-safe characters." - return unicode_string.encode('ascii', 'replace').replace('\\', '') \ No newline at end of file