Moved utility functions, enabled window resizing.
This commit is contained in:
@@ -1,51 +1,8 @@
|
|||||||
from datetime import datetime, timedelta
|
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
def clean(unicode_string):
|
from utils import clean, strip_subreddit_url, humanize_timestamp
|
||||||
"""
|
|
||||||
Convert unicode string into ascii-safe characters.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return unicode_string.encode('ascii', 'replace').replace('\\', '')
|
class SubmissionContent(object):
|
||||||
|
|
||||||
|
|
||||||
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):
|
|
||||||
"""
|
"""
|
||||||
Facilitates navigating through the comments in a PRAW submission.
|
Facilitates navigating through the comments in a PRAW submission.
|
||||||
"""
|
"""
|
||||||
@@ -53,6 +10,8 @@ class SubmissionGenerator(object):
|
|||||||
def __init__(self, submission):
|
def __init__(self, submission):
|
||||||
|
|
||||||
self.submission = submission
|
self.submission = submission
|
||||||
|
self._comments = []
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def flatten_comments(submission):
|
def flatten_comments(submission):
|
||||||
@@ -76,8 +35,24 @@ class SubmissionGenerator(object):
|
|||||||
retval.append(item)
|
retval.append(item)
|
||||||
return retval
|
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
|
Grabs a subreddit from PRAW and lazily stores submissions to an internal
|
||||||
list for repeat access.
|
list for repeat access.
|
||||||
@@ -110,10 +85,12 @@ class SubredditGenerator(object):
|
|||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
data['title'] = clean(sub.title)
|
data['title'] = clean(sub.title)
|
||||||
|
data['text'] = clean(sub.selftext)
|
||||||
data['created'] = humanize_timestamp(sub.created_utc)
|
data['created'] = humanize_timestamp(sub.created_utc)
|
||||||
data['comments'] = '{} comments'.format(sub.num_comments)
|
data['comments'] = '{} comments'.format(sub.num_comments)
|
||||||
data['score'] = '{} pts'.format(sub.score)
|
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['subreddit'] = strip_subreddit_url(sub.permalink)
|
||||||
data['url'] = ('(selfpost)' if is_selfpost(sub.url) else clean(sub.url))
|
data['url'] = ('(selfpost)' if is_selfpost(sub.url) else clean(sub.url))
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import praw
|
|||||||
import textwrap
|
import textwrap
|
||||||
import curses
|
import curses
|
||||||
|
|
||||||
from content_generators import SubredditGenerator
|
from content_generators import SubredditContent
|
||||||
from utils import curses_session
|
from utils import curses_session
|
||||||
from viewer import BaseViewer
|
from viewer import BaseViewer
|
||||||
|
|
||||||
@@ -11,16 +11,14 @@ class SubredditViewer(BaseViewer):
|
|||||||
def __init__(self, stdscr, subreddit_content):
|
def __init__(self, stdscr, subreddit_content):
|
||||||
|
|
||||||
self.stdscr = stdscr
|
self.stdscr = stdscr
|
||||||
|
|
||||||
self._n_rows = None
|
|
||||||
self._n_cols = None
|
|
||||||
self._title_window = None
|
self._title_window = None
|
||||||
self._content_window = None
|
self._content_window = None
|
||||||
|
|
||||||
super(SubredditViewer, self).__init__(subreddit_content)
|
super(SubredditViewer, self).__init__(subreddit_content)
|
||||||
self.draw()
|
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
|
|
||||||
|
self.draw()
|
||||||
while True:
|
while True:
|
||||||
cmd = self.stdscr.getch()
|
cmd = self.stdscr.getch()
|
||||||
|
|
||||||
@@ -44,6 +42,9 @@ class SubredditViewer(BaseViewer):
|
|||||||
self.stdscr.clear()
|
self.stdscr.clear()
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
|
elif cmd == curses.KEY_RESIZE:
|
||||||
|
self.draw()
|
||||||
|
|
||||||
# Quit
|
# Quit
|
||||||
elif cmd == ord('q'):
|
elif cmd == ord('q'):
|
||||||
break
|
break
|
||||||
@@ -54,8 +55,8 @@ class SubredditViewer(BaseViewer):
|
|||||||
def draw(self):
|
def draw(self):
|
||||||
|
|
||||||
# Refresh window bounds incase the screen has been resized
|
# Refresh window bounds incase the screen has been resized
|
||||||
self._n_rows, self._n_cols = self.stdscr.getmaxyx()
|
n_rows, n_cols = self.stdscr.getmaxyx()
|
||||||
self._title_window = self.stdscr.derwin(1, self._n_cols, 0, 0)
|
self._title_window = self.stdscr.derwin(1, n_cols, 0, 0)
|
||||||
self._content_window = self.stdscr.derwin(1, 0)
|
self._content_window = self.stdscr.derwin(1, 0)
|
||||||
|
|
||||||
self.draw_header()
|
self.draw_header()
|
||||||
@@ -79,7 +80,7 @@ class SubredditViewer(BaseViewer):
|
|||||||
# and draw upwards.
|
# and draw upwards.
|
||||||
current_row = n_rows if inverted else 0
|
current_row = n_rows if inverted else 0
|
||||||
available_rows = n_rows
|
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'])
|
window_rows = min(available_rows, data['n_rows'])
|
||||||
start = current_row - window_rows if inverted else current_row
|
start = current_row - window_rows if inverted else current_row
|
||||||
window = self._content_window.derwin(window_rows, n_cols, start, 0)
|
window = self._content_window.derwin(window_rows, n_cols, start, 0)
|
||||||
@@ -94,9 +95,11 @@ class SubredditViewer(BaseViewer):
|
|||||||
|
|
||||||
def draw_header(self):
|
def draw_header(self):
|
||||||
|
|
||||||
|
n_rows, n_cols = self._title_window.getmaxyx()
|
||||||
sub_name = self.content.display_name
|
sub_name = self.content.display_name
|
||||||
|
|
||||||
self._title_window.erase()
|
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()
|
self._title_window.refresh()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -130,7 +133,7 @@ def main():
|
|||||||
|
|
||||||
with curses_session() as stdscr:
|
with curses_session() as stdscr:
|
||||||
r = praw.Reddit(user_agent='reddit terminal viewer (rtv) v0.0')
|
r = praw.Reddit(user_agent='reddit terminal viewer (rtv) v0.0')
|
||||||
generator = SubredditGenerator(r)
|
generator = SubredditContent(r)
|
||||||
viewer = SubredditViewer(stdscr, generator)
|
viewer = SubredditViewer(stdscr, generator)
|
||||||
viewer.loop()
|
viewer.loop()
|
||||||
|
|
||||||
|
|||||||
49
rtv/utils.py
49
rtv/utils.py
@@ -1,6 +1,51 @@
|
|||||||
import curses
|
from datetime import datetime, timedelta
|
||||||
from contextlib import contextmanager
|
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
|
@contextmanager
|
||||||
def curses_session():
|
def curses_session():
|
||||||
|
|
||||||
@@ -31,7 +76,7 @@ def curses_session():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Hide blinking cursor
|
# Hide blinking cursor
|
||||||
# curses.curs_set(0)
|
curses.curs_set(0)
|
||||||
|
|
||||||
# Initialize color pairs - colored text on the default background
|
# Initialize color pairs - colored text on the default background
|
||||||
curses.init_pair(1, curses.COLOR_RED, -1)
|
curses.init_pair(1, curses.COLOR_RED, -1)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import curses
|
|||||||
|
|
||||||
class Navigator(object):
|
class Navigator(object):
|
||||||
"""
|
"""
|
||||||
Handles cursor movement and screen paging.
|
Handles math behind cursor movement and screen paging.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -67,13 +67,14 @@ class Navigator(object):
|
|||||||
|
|
||||||
class BaseViewer(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):
|
def __init__(self, content):
|
||||||
|
|
||||||
self.content = content
|
self.content = content
|
||||||
self.nav = Navigator(self.content.get)
|
self.nav = Navigator(self.content.get)
|
||||||
|
|
||||||
self._subwindows = None
|
self._subwindows = None
|
||||||
|
|
||||||
def draw_content(self):
|
def draw_content(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user