Merge pull request #158 from zambonin/master
New screenshot and code width
This commit is contained in:
@@ -4,7 +4,7 @@ RTV: Reddit Terminal Viewer
|
||||
|
||||
RTV is an application that allows you to view and interact with reddit from your terminal. It is compatible with *most* terminal emulators on Linux and OSX.
|
||||
|
||||
.. image:: http://i.imgur.com/W1hxqCt.png
|
||||
.. image:: http://i.imgur.com/xpOEi1E.png
|
||||
|
||||
RTV is built in **python** using the **curses** library.
|
||||
|
||||
@@ -201,7 +201,7 @@ How do I run the code directly using python?
|
||||
This project is structured to be run as a python *module*. This means that in order to resolve imports you need to launch using python's ``-m`` flag. This method works for all versions of python. Follow the example below, which assumes that you have cloned the repository into the directory **~/rtv_project**.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
||||
$ cd ~/rtv_project
|
||||
$ python2 -m rtv
|
||||
$ python3 -m rtv
|
||||
@@ -225,7 +225,7 @@ License
|
||||
Please see `LICENSE <https://github.com/michael-lazar/rtv/blob/master/LICENSE>`_.
|
||||
|
||||
|
||||
.. |python| image:: https://img.shields.io/badge/python-2.7%2C%203.4-blue.svg?style=flat-square
|
||||
.. |python| image:: https://img.shields.io/badge/python-2.7%2C%203.5-blue.svg?style=flat-square
|
||||
:target: https://pypi.python.org/pypi/rtv/
|
||||
:alt: Supported Python versions
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import os
|
||||
import sys
|
||||
import locale
|
||||
import logging
|
||||
@@ -6,7 +5,6 @@ import logging
|
||||
import praw
|
||||
import praw.errors
|
||||
import tornado
|
||||
import requests
|
||||
from requests import exceptions
|
||||
|
||||
from . import config
|
||||
@@ -28,6 +26,7 @@ _logger = logging.getLogger(__name__)
|
||||
# ptrace_scope to 0 in /etc/sysctl.d/10-ptrace.conf.
|
||||
# http://blog.mellenthin.de/archives/2010/10/18/gdb-attach-fails
|
||||
|
||||
|
||||
def main():
|
||||
"Main entry point"
|
||||
|
||||
@@ -77,7 +76,8 @@ def main():
|
||||
subreddit = args.subreddit or 'front'
|
||||
page = SubredditPage(stdscr, reddit, oauth, subreddit)
|
||||
page.loop()
|
||||
except (exceptions.RequestException, praw.errors.PRAWException, RTVError) as e:
|
||||
except (exceptions.RequestException, praw.errors.PRAWException,
|
||||
RTVError) as e:
|
||||
_logger.exception(e)
|
||||
print('{}: {}'.format(type(e).__name__, e))
|
||||
except KeyboardInterrupt:
|
||||
|
||||
@@ -21,8 +21,10 @@ oauth_client_id = 'E2oEtRQfdfAfNQ'
|
||||
oauth_client_secret = 'praw_gapfill'
|
||||
oauth_redirect_uri = 'http://127.0.0.1:65000/'
|
||||
oauth_redirect_port = 65000
|
||||
oauth_scope = ['edit', 'history', 'identity', 'mysubreddits', 'privatemessages',
|
||||
'read', 'report', 'save', 'submit', 'subscribe', 'vote']
|
||||
oauth_scope = ['edit', 'history', 'identity', 'mysubreddits',
|
||||
'privatemessages', 'read', 'report', 'save', 'submit',
|
||||
'subscribe', 'vote']
|
||||
|
||||
|
||||
def build_parser():
|
||||
parser = argparse.ArgumentParser(
|
||||
@@ -51,6 +53,7 @@ def build_parser():
|
||||
help='Remove any saved OAuth tokens before starting')
|
||||
return parser
|
||||
|
||||
|
||||
def load_config():
|
||||
"""
|
||||
Attempt to load settings from the local config file.
|
||||
@@ -74,6 +77,7 @@ def load_config():
|
||||
|
||||
return config_dict
|
||||
|
||||
|
||||
def load_refresh_token(filename=TOKEN):
|
||||
if os.path.exists(filename):
|
||||
with open(filename) as fp:
|
||||
@@ -81,10 +85,12 @@ def load_refresh_token(filename=TOKEN):
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def save_refresh_token(token, filename=TOKEN):
|
||||
with open(filename, 'w+') as fp:
|
||||
fp.write(token)
|
||||
|
||||
|
||||
def clear_refresh_token(filename=TOKEN):
|
||||
if os.path.exists(filename):
|
||||
os.remove(filename)
|
||||
|
||||
@@ -4,7 +4,8 @@ import praw
|
||||
import requests
|
||||
import re
|
||||
|
||||
from .exceptions import SubmissionError, SubredditError, SubscriptionError, AccountError
|
||||
from .exceptions import (SubmissionError, SubredditError, SubscriptionError,
|
||||
AccountError)
|
||||
from .helpers import humanize_timestamp, wrap_text, strip_subreddit_url
|
||||
|
||||
__all__ = ['SubredditContent', 'SubmissionContent', 'SubscriptionContent']
|
||||
@@ -112,7 +113,8 @@ class BaseContent(object):
|
||||
"selfpost" or "x-post" or a link.
|
||||
"""
|
||||
|
||||
reddit_link = re.compile("https?://(www\.)?(np\.)?redd(it\.com|\.it)/r/.*")
|
||||
reddit_link = re.compile(
|
||||
"https?://(www\.)?(np\.)?redd(it\.com|\.it)/r/.*")
|
||||
author = getattr(sub, 'author', '[deleted]')
|
||||
name = getattr(author, 'name', '[deleted]')
|
||||
flair = getattr(sub, 'link_flair_text', '')
|
||||
|
||||
@@ -24,17 +24,18 @@ def clean(string, n_cols=None):
|
||||
http://nedbatchelder.com/text/unipain.html
|
||||
|
||||
Python 2 input string will be a unicode type (unicode code points). Curses
|
||||
will accept unicode 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
|
||||
will accept unicode 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). If we encode the unicode to a utf-8 byte string and pass that to
|
||||
curses, it will render correctly.
|
||||
range(128). If we encode the unicode to a utf-8 byte string and pass that
|
||||
to curses, it 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 not be correct. If code points are passed to addnstr, curses will treat
|
||||
each code point as one character and will not account for wide characters.
|
||||
If utf-8 is passed in, addnstr will treat each 'byte' as a single character.
|
||||
will not be correct. If code points are passed to addnstr, curses will
|
||||
treat each code point as one character and will not account for wide
|
||||
characters. If utf-8 is passed in, addnstr will treat each 'byte' as a
|
||||
single character.
|
||||
"""
|
||||
|
||||
if n_cols is not None and n_cols <= 0:
|
||||
@@ -124,7 +125,8 @@ def check_browser_display():
|
||||
|
||||
# Use the convention defined here to parse $BROWSER
|
||||
# https://docs.python.org/2/library/webbrowser.html
|
||||
console_browsers = ['www-browser', 'links', 'links2', 'elinks', 'lynx', 'w3m']
|
||||
console_browsers = ['www-browser', 'links', 'links2', 'elinks', 'lynx',
|
||||
'w3m']
|
||||
if "BROWSER" in os.environ:
|
||||
user_browser = os.environ["BROWSER"].split(os.pathsep)[0]
|
||||
if user_browser in console_browsers:
|
||||
@@ -138,7 +140,8 @@ def check_browser_display():
|
||||
|
||||
def wrap_text(text, width):
|
||||
"""
|
||||
Wrap text paragraphs to the given character width while preserving newlines.
|
||||
Wrap text paragraphs to the given character width while preserving
|
||||
newlines.
|
||||
"""
|
||||
out = []
|
||||
for paragraph in text.splitlines():
|
||||
|
||||
@@ -9,7 +9,8 @@ def history_path():
|
||||
Create the path to the history log
|
||||
"""
|
||||
HOME = os.path.expanduser('~')
|
||||
XDG_CONFIG_HOME = os.getenv('XDG_CACHE_HOME', os.path.join(HOME, '.config'))
|
||||
XDG_CONFIG_HOME = os.getenv('XDG_CACHE_HOME',
|
||||
os.path.join(HOME, '.config'))
|
||||
path = os.path.join(XDG_CONFIG_HOME, 'rtv')
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
10
rtv/oauth.py
10
rtv/oauth.py
@@ -7,7 +7,7 @@ from tornado import gen, ioloop, web, httpserver
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from . import config
|
||||
from .curses_helpers import show_notification, prompt_input
|
||||
from .curses_helpers import show_notification
|
||||
from .helpers import check_browser_display, open_browser
|
||||
|
||||
__all__ = ['OAuthTool']
|
||||
@@ -18,6 +18,7 @@ oauth_error = None
|
||||
|
||||
template_path = os.path.join(os.path.dirname(__file__), 'templates')
|
||||
|
||||
|
||||
class AuthHandler(web.RequestHandler):
|
||||
|
||||
def get(self):
|
||||
@@ -27,12 +28,14 @@ class AuthHandler(web.RequestHandler):
|
||||
oauth_code = self.get_argument('code', default='placeholder')
|
||||
oauth_error = self.get_argument('error', default='placeholder')
|
||||
|
||||
self.render('index.html', state=oauth_state, code=oauth_code, error=oauth_error)
|
||||
self.render('index.html', state=oauth_state, code=oauth_code,
|
||||
error=oauth_error)
|
||||
|
||||
# Stop IOLoop if using a background browser such as firefox
|
||||
if check_browser_display():
|
||||
ioloop.IOLoop.current().stop()
|
||||
|
||||
|
||||
class OAuthTool(object):
|
||||
|
||||
def __init__(self, reddit, stdscr=None, loader=None):
|
||||
@@ -46,7 +49,8 @@ class OAuthTool(object):
|
||||
|
||||
# Initialize Tornado webapp
|
||||
routes = [('/', AuthHandler)]
|
||||
self.callback_app = web.Application(routes, template_path=template_path)
|
||||
self.callback_app = web.Application(routes,
|
||||
template_path=template_path)
|
||||
|
||||
self.reddit.set_oauth_app_info(config.oauth_client_id,
|
||||
config.oauth_client_secret,
|
||||
|
||||
@@ -117,7 +117,7 @@ class Navigator(object):
|
||||
self.page_index += (self.step * (n_windows-1))
|
||||
self.inverted = not self.inverted
|
||||
self.cursor_index \
|
||||
= (n_windows-(direction<0)) - self.cursor_index
|
||||
= (n_windows-(direction < 0)) - self.cursor_index
|
||||
|
||||
valid = False
|
||||
adj = 0
|
||||
@@ -565,7 +565,8 @@ class BasePage(object):
|
||||
if not valid:
|
||||
curses.flash()
|
||||
|
||||
# Note: ACS_VLINE doesn't like changing the attribute, so always redraw.
|
||||
# Note: ACS_VLINE doesn't like changing the attribute,
|
||||
# so always redraw.
|
||||
self._draw_content()
|
||||
self._add_cursor()
|
||||
|
||||
@@ -575,7 +576,8 @@ class BasePage(object):
|
||||
if not valid:
|
||||
curses.flash()
|
||||
|
||||
# Note: ACS_VLINE doesn't like changing the attribute, so always redraw.
|
||||
# Note: ACS_VLINE doesn't like changing the attribute,
|
||||
# so always redraw.
|
||||
self._draw_content()
|
||||
self._add_cursor()
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from .page import BasePage, Navigator, BaseController
|
||||
from .submission import SubmissionPage
|
||||
from .subscription import SubscriptionPage
|
||||
from .content import SubredditContent
|
||||
from .helpers import open_browser, open_editor, strip_subreddit_url
|
||||
from .helpers import open_browser, open_editor
|
||||
from .docs import SUBMISSION_FILE
|
||||
from .history import load_history, save_history
|
||||
from .curses_helpers import (Color, LoadScreen, add_line, get_arrow, get_gold,
|
||||
@@ -105,7 +105,8 @@ class SubredditPage(BasePage):
|
||||
"Select the current submission to view posts"
|
||||
|
||||
data = self.content.get(self.nav.absolute_index)
|
||||
page = SubmissionPage(self.stdscr, self.reddit, self.oauth, url=data['permalink'])
|
||||
page = SubmissionPage(self.stdscr, self.reddit, self.oauth,
|
||||
url=data['permalink'])
|
||||
page.loop()
|
||||
if data['url_type'] == 'selfpost':
|
||||
global history
|
||||
@@ -120,7 +121,8 @@ class SubredditPage(BasePage):
|
||||
global history
|
||||
history.add(url)
|
||||
if data['url_type'] in ['x-post', 'selfpost']:
|
||||
page = SubmissionPage(self.stdscr, self.reddit, self.oauth, url=url)
|
||||
page = SubmissionPage(self.stdscr, self.reddit, self.oauth,
|
||||
url=url)
|
||||
page.loop()
|
||||
else:
|
||||
open_browser(url)
|
||||
@@ -162,7 +164,8 @@ class SubredditPage(BasePage):
|
||||
time.sleep(2.0)
|
||||
# Open the newly created post
|
||||
s.catch = False
|
||||
page = SubmissionPage(self.stdscr, self.reddit, self.oauth, submission=post)
|
||||
page = SubmissionPage(self.stdscr, self.reddit, self.oauth,
|
||||
submission=post)
|
||||
page.loop()
|
||||
self.refresh_content()
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import curses
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
|
||||
from .content import SubscriptionContent
|
||||
@@ -10,9 +8,11 @@ from .curses_helpers import (Color, LoadScreen, add_line)
|
||||
__all__ = ['SubscriptionController', 'SubscriptionPage']
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SubscriptionController(BaseController):
|
||||
character_map = {}
|
||||
|
||||
|
||||
class SubscriptionPage(BasePage):
|
||||
|
||||
def __init__(self, stdscr, reddit, oauth):
|
||||
@@ -44,7 +44,8 @@ class SubscriptionPage(BasePage):
|
||||
def store_selected_subreddit(self):
|
||||
"Store the selected subreddit and return to the subreddit page"
|
||||
|
||||
self.selected_subreddit_data = self.content.get(self.nav.absolute_index)
|
||||
self.selected_subreddit_data = self.content.get(
|
||||
self.nav.absolute_index)
|
||||
self.active = False
|
||||
|
||||
@SubscriptionController.register(curses.KEY_LEFT, 'h', 's')
|
||||
|
||||
Reference in New Issue
Block a user