Implemented unicode sandwich.

This commit is contained in:
Michael Lazar
2015-03-12 19:02:22 -07:00
parent 47ad49a189
commit 17a1c27a2c
6 changed files with 70 additions and 58 deletions

View File

@@ -3,25 +3,10 @@ from datetime import datetime
from contextlib import contextmanager from contextlib import contextmanager
import praw import praw
import six
import requests import requests
from .errors import SubmissionURLError, SubredditNameError from .errors import SubmissionURLError, SubredditNameError
FORCE_ASCII = True
def clean(string):
encoding = 'ascii' if FORCE_ASCII else 'utf-8'
if six.PY2:
out = string.encode(encoding, 'replace')
else:
out = string.encode().decode(encoding, 'replace')
out = out.replace('\\', '')
return out
def split_text(big_text, width): def split_text(big_text, width):
return [ return [
text text
@@ -32,14 +17,13 @@ def split_text(big_text, width):
for text in (textwrap.wrap(line, width=width) or ['']) for text in (textwrap.wrap(line, width=width) or [''])
] ]
def strip_subreddit_url(permalink): def strip_subreddit_url(permalink):
""" """
Grab the subreddit from the permalink because submission.subreddit.url Grab the subreddit from the permalink because submission.subreddit.url
makes a seperate call to the API. makes a seperate call to the API.
""" """
subreddit = clean(permalink).split('/')[4] subreddit = permalink.split('/')[4]
return '/r/{}'.format(subreddit) return '/r/{}'.format(subreddit)
@@ -131,13 +115,12 @@ class BaseContent(object):
data['body'] = 'More comments'.format(comment.count) data['body'] = 'More comments'.format(comment.count)
else: else:
data['type'] = 'Comment' data['type'] = 'Comment'
data['body'] = clean(comment.body) data['body'] = comment.body
data['created'] = humanize_timestamp(comment.created_utc) data['created'] = humanize_timestamp(comment.created_utc)
data['score'] = '{} pts'.format(comment.score) data['score'] = '{} pts'.format(comment.score)
data['author'] = (clean(comment.author.name) if data['author'] = (comment.author.name if
getattr(comment, 'author') else '[deleted]') getattr(comment, 'author') else '[deleted]')
sub_author = (comment.submission.author.name if
sub_author = (clean(comment.submission.author.name) if
getattr(comment.submission, 'author') else '[deleted]') getattr(comment.submission, 'author') else '[deleted]')
data['is_author'] = (data['author'] == sub_author) data['is_author'] = (data['author'] == sub_author)
@@ -155,17 +138,17 @@ class BaseContent(object):
data = {} data = {}
data['object'] = sub data['object'] = sub
data['type'] = 'Submission' data['type'] = 'Submission'
data['title'] = clean(sub.title) data['title'] = sub.title
data['text'] = clean(sub.selftext) data['text'] = 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) if getattr(sub, 'author') data['author'] = (sub.author.name if getattr(sub, 'author')
else '[deleted]') else '[deleted]')
data['permalink'] = clean(sub.permalink) data['permalink'] = sub.permalink
data['subreddit'] = strip_subreddit_url(sub.permalink) data['subreddit'] = strip_subreddit_url(sub.permalink)
data['url_full'] = clean(sub.url) data['url_full'] = sub.url
data['url'] = ('selfpost' if is_selfpost(sub.url) else clean(sub.url)) data['url'] = ('selfpost' if is_selfpost(sub.url) else sub.url)
return data return data
@@ -359,11 +342,11 @@ class SubredditContent(BaseContent):
# there is is no other way to check things like multireddits that # there is is no other way to check things like multireddits that
# don't have a real corresponding subreddit object. # don't have a real corresponding subreddit object.
content = cls(display_name, submissions, loader) content = cls(display_name, submissions, loader)
try: #try:
content.get(0) content.get(0)
except: #except:
# TODO: Trap specific errors # # TODO: Trap specific errors
raise SubredditNameError(display_name) # raise SubredditNameError(display_name)
return content return content

View File

@@ -5,7 +5,7 @@ import praw
from requests.exceptions import ConnectionError, HTTPError from requests.exceptions import ConnectionError, HTTPError
from praw.errors import InvalidUserPass from praw.errors import InvalidUserPass
from . import content from . import utils
from .errors import SubmissionURLError, SubredditNameError from .errors import SubmissionURLError, SubredditNameError
from .utils import curses_session, load_config, HELP from .utils import curses_session, load_config, HELP
from .subreddit import SubredditPage from .subreddit import SubredditPage
@@ -64,7 +64,7 @@ def main():
if getattr(args, key) is None: if getattr(args, key) is None:
setattr(args, key, val) setattr(args, key, val)
content.FORCE_ASCII = args.force_ascii utils.FORCE_ASCII = args.force_ascii
if args.subreddit is None: if args.subreddit is None:
args.subreddit = 'front' args.subreddit = 'front'

View File

@@ -1,6 +1,6 @@
import curses import curses
from .utils import Color from .utils import Color, clean
class Navigator(object): class Navigator(object):
""" """
@@ -157,14 +157,15 @@ class BasePage(object):
self._header_window.bkgd(' ', attr) self._header_window.bkgd(' ', attr)
sub_name = self.content.name.replace('/r/front', 'Front Page ') sub_name = self.content.name.replace('/r/front', 'Front Page ')
self._header_window.addnstr(0, 0, sub_name, n_cols-1) self._header_window.addnstr(0, 0, clean(sub_name), n_cols-1)
if self.reddit.user is not None: if self.reddit.user is not None:
username = self.reddit.user.name username = self.reddit.user.name
s_col = (n_cols - len(username) - 1) s_col = (n_cols - len(username) - 1)
# Only print the username if it fits in the empty space on the right # Only print the username if it fits in the empty space on the right
if (s_col - 1) >= len(sub_name): if (s_col - 1) >= len(sub_name):
self._header_window.addnstr(0, s_col, username, (n_cols-s_col-1)) n = (n_cols - s_col - 1)
self._header_window.addnstr(0, s_col, clean(username), n)
self._header_window.refresh() self._header_window.refresh()

View File

@@ -6,7 +6,7 @@ import six
from .content import SubmissionContent from .content import SubmissionContent
from .page import BasePage from .page import BasePage
from .utils import LoadScreen, Color, ESCAPE, display_help, open_new_tab from .utils import LoadScreen, Color, ESCAPE, display_help, open_new_tab, clean
class SubmissionPage(BasePage): class SubmissionPage(BasePage):
@@ -99,7 +99,8 @@ class SubmissionPage(BasePage):
@staticmethod @staticmethod
def draw_comment(win, data, inverted=False): def draw_comment(win, data, inverted=False):
import logging
_logger = logging.getLogger(__name__)
n_rows, n_cols = win.getmaxyx() n_rows, n_cols = win.getmaxyx()
n_cols -= 1 n_cols -= 1
@@ -112,14 +113,14 @@ class SubmissionPage(BasePage):
text = '{author}'.format(**data) text = '{author}'.format(**data)
attr = curses.A_BOLD attr = curses.A_BOLD
attr |= (Color.BLUE if not data['is_author'] else Color.GREEN) attr |= (Color.BLUE if not data['is_author'] else Color.GREEN)
win.addnstr(row, 1, text, n_cols-1, attr) win.addnstr(row, 1, clean(text), n_cols-1, attr)
text = ' {score} {created}'.format(**data) text = ' {score} {created}'.format(**data)
win.addnstr(text, n_cols - win.getyx()[1]) win.addnstr(clean(text), n_cols - win.getyx()[1])
n_body = len(data['split_body']) n_body = len(data['split_body'])
for row, text in enumerate(data['split_body'], start=offset+1): for row, text in enumerate(data['split_body'], start=offset+1):
if row in valid_rows: if row in valid_rows:
win.addnstr(row, 1, text, n_cols-1) win.addnstr(row, 1, clean(text), n_cols-50)
# Vertical line, unfortunately vline() doesn't support custom color so # Vertical line, unfortunately vline() doesn't support custom color so
# we have to build it one chr at a time. # we have to build it one chr at a time.
@@ -145,9 +146,9 @@ class SubmissionPage(BasePage):
n_rows, n_cols = win.getmaxyx() n_rows, n_cols = win.getmaxyx()
n_cols -= 1 n_cols -= 1
text = '{body}'.format(**data) text = '{body}'.format(**data)
win.addnstr(0, 1, text, n_cols-1) win.addnstr(0, 1, clean(text), n_cols-1)
text = ' [{count}]'.format(**data) text = ' [{count}]'.format(**data)
win.addnstr(text, n_cols - win.getyx()[1], curses.A_BOLD) win.addnstr(clean(text), n_cols - win.getyx()[1], curses.A_BOLD)
attr = Color.get_level(data['level']) attr = Color.get_level(data['level'])
for y in range(n_rows): for y in range(n_rows):
@@ -167,26 +168,26 @@ class SubmissionPage(BasePage):
return return
for row, text in enumerate(data['split_title'], start=1): for row, text in enumerate(data['split_title'], start=1):
win.addnstr(row, 1, text, n_cols, curses.A_BOLD) win.addnstr(row, 1, clean(text), n_cols, curses.A_BOLD)
row = len(data['split_title']) + 1 row = len(data['split_title']) + 1
attr = curses.A_BOLD | Color.GREEN attr = curses.A_BOLD | Color.GREEN
text = '{author}'.format(**data) text = '{author}'.format(**data)
win.addnstr(row, 1, text, n_cols, attr) win.addnstr(row, 1, clean(text), n_cols, attr)
text = ' {created} {subreddit}'.format(**data) text = ' {created} {subreddit}'.format(**data)
win.addnstr(text, n_cols - win.getyx()[1]) win.addnstr(clean(text), n_cols - win.getyx()[1])
row = len(data['split_title']) + 2 row = len(data['split_title']) + 2
attr = curses.A_UNDERLINE | Color.BLUE attr = curses.A_UNDERLINE | Color.BLUE
text = '{url}'.format(**data) text = '{url}'.format(**data)
win.addnstr(row, 1, text, n_cols, attr) win.addnstr(row, 1, clean(text), n_cols, attr)
offset = len(data['split_title']) + 3 offset = len(data['split_title']) + 3
for row, text in enumerate(data['split_text'], start=offset): for row, text in enumerate(data['split_text'], start=offset):
win.addnstr(row, 1, text, n_cols) win.addnstr(row, 1, clean(text), n_cols)
row = len(data['split_title']) + len(data['split_text']) + 3 row = len(data['split_title']) + len(data['split_text']) + 3
text = '{score} {comments}'.format(**data) text = '{score} {comments}'.format(**data)
win.addnstr(row, 1, text, n_cols, curses.A_BOLD) win.addnstr(row, 1, clean(text), n_cols, curses.A_BOLD)
win.border() win.border()

View File

@@ -8,7 +8,7 @@ from .page import BasePage
from .submission import SubmissionPage from .submission import SubmissionPage
from .content import SubredditContent from .content import SubredditContent
from .utils import (LoadScreen, Color, text_input, display_message, from .utils import (LoadScreen, Color, text_input, display_message,
display_help, open_new_tab) display_help, open_new_tab, clean)
# Used to keep track of browsing history across the current session # Used to keep track of browsing history across the current session
@@ -87,7 +87,7 @@ class SubredditPage(BasePage):
attr = curses.A_BOLD | Color.CYAN attr = curses.A_BOLD | Color.CYAN
prompt = 'Enter Subreddit: /r/' prompt = 'Enter Subreddit: /r/'
n_rows, n_cols = self.stdscr.getmaxyx() n_rows, n_cols = self.stdscr.getmaxyx()
self.stdscr.addstr(n_rows-1, 0, prompt, attr) self.stdscr.addstr(n_rows-1, 0, clean(prompt), attr)
self.stdscr.refresh() self.stdscr.refresh()
window = self.stdscr.derwin(1, n_cols-len(prompt),n_rows-1, len(prompt)) window = self.stdscr.derwin(1, n_cols-len(prompt),n_rows-1, len(prompt))
window.attrset(attr) window.attrset(attr)
@@ -129,7 +129,7 @@ class SubredditPage(BasePage):
for row, text in enumerate(data['split_title'], start=offset): for row, text in enumerate(data['split_title'], start=offset):
if row in valid_rows: if row in valid_rows:
attr = curses.A_BOLD attr = curses.A_BOLD
win.addstr(row, 1, text, attr) win.addstr(row, 1, clean(text), attr)
row = n_title + offset row = n_title + offset
if row in valid_rows: if row in valid_rows:
@@ -137,16 +137,16 @@ class SubredditPage(BasePage):
link_color = Color.MAGENTA if seen else Color.BLUE link_color = Color.MAGENTA if seen else Color.BLUE
attr = curses.A_UNDERLINE | link_color attr = curses.A_UNDERLINE | link_color
text = '{url}'.format(**data) text = '{url}'.format(**data)
win.addnstr(row, 1, text, n_cols-1, attr) win.addnstr(row, 1, clean(text), n_cols-1, attr)
row = n_title + offset + 1 row = n_title + offset + 1
if row in valid_rows: if row in valid_rows:
text = '{created} {comments} {score}'.format(**data) text = '{created} {comments} {score}'.format(**data)
win.addnstr(row, 1, text, n_cols-1) win.addnstr(row, 1, clean(text), n_cols-1)
row = n_title + offset + 2 row = n_title + offset + 2
if row in valid_rows: if row in valid_rows:
text = '{author}'.format(**data) text = '{author}'.format(**data)
win.addnstr(row, 1, text, n_cols-1, curses.A_BOLD) win.addnstr(row, 1, clean(text), n_cols-1, curses.A_BOLD)
text = ' {subreddit}'.format(**data) text = ' {subreddit}'.format(**data)
win.addnstr(text, n_cols - win.getyx()[1], Color.YELLOW) win.addnstr(clean(text), n_cols - win.getyx()[1], Color.YELLOW)

View File

@@ -7,10 +7,12 @@ import threading
from curses import textpad, ascii from curses import textpad, ascii
from contextlib import contextmanager from contextlib import contextmanager
import six
from six.moves import configparser from six.moves import configparser
from .errors import EscapePressed from .errors import EscapePressed
FORCE_ASCII = True
ESCAPE = 27 ESCAPE = 27
HELP = """ HELP = """
@@ -30,6 +32,31 @@ Submission Mode
`RIGHT` or `l` : Fold the selected comment, or load additional comments `RIGHT` or `l` : Fold the selected comment, or load additional comments
""" """
def clean(string):
"""
Required reading!
http://nedbatchelder.com/text/unipain.html
Python 2 input string will be a unicode type (unicode code points). Curses
will accept that if all of the points are in the ascii range. However, if
any of the code points are not valid ascii curses will throw a
UnicodeEncodeError: 'ascii' codec can't encode character, ordinal not in
range(128). However, if we encode the unicode to a utf-8 byte string and
pass that to curses, curses will render correctly.
Python 3 input string will be a string type (unicode code points). Curses
will accept that in all cases. However, the n character count in addnstr
will get screwed up.
"""
if six.PY2:
string = string.encode('utf-8', 'replace')
else:
string = string.encode('utf-8', 'replace')
pass
return string
class Color(object): class Color(object):
COLORS = { COLORS = {