Merge remote-tracking branch 'upstream/master'

This commit is contained in:
David Foucher
2016-07-30 12:52:37 +02:00
28 changed files with 1791 additions and 154 deletions

View File

@@ -1,4 +1,3 @@
================
RTV Contributors
================
@@ -7,22 +6,26 @@ Thanks to the following people for their contributions to this project.
* `Michael Lazar <https://github.com/michael-lazar>`_
* `Tobin Brown <https://github.com/Brobin>`_
* `woorst <https://github.com/woorst>`_
* `Théo Piboubès <https://github.com/TheoPib>`_
* `Yusuke Sakamoto <https://github.com/yskmt>`_
* `Johnathan Jenkins <https://github.com/shaggytwodope>`_
* `obosob <https://github.com/obosob>`_
* `Gustavo Zambonin <https://github.com/zambonin>`_
* `mekhami <https://github.com/mekhami>`_
* `Noah Morrison <https://github.com/noahmorrison>`_
* `obosob <https://github.com/obosob>`_
* `Toby Hughes <https://github.com/tobywhughes>`_
* `Shawn Hind <https://github.com/shanhind>`_
* `Noah Morrison <https://github.com/noahmorrison>`_
* `mardiqwop <https://github.com/mardiqwop>`_
* `Shawn Hind <https://github.com/shawnhind>`_
* `5225225 <https://github.com/5225225>`_
* `JuanPablo <https://github.com/juanpabloaj>`_
* `Robert Greener <https://github.com/ragreener1>`_
* `peterpans01 <https://github.com/peterpans01>`_
* `Adam Talsma <https://github.com/a-tal>`_
* `afloofloo <https://github.com/afloofloo>`_
* `Matthew Smith <https://github.com/msmith491>`_
* `Marc Abramowitz <https://github.com/msabramo>`_
* `Hans Roman <https://github.com/snahor>`_
* `Ram-Z <https://github.com/Ram-Z>`_
* `mralext20 <https://github.com/mralext20>`_
* `Wieland Hoffmann <http://github.com/mineo>`_
* `Marc Abramowitz <http://github.com/msabramo>`_
* `Hans Roman <http://github.com/snahor>`_
* `Gustavo Zambonin <https://github.com/zambonin>`_
* `Wieland Hoffmann <https://github.com/mineo>`_
* `Adam Talsma <https://github.com/a-tal>`_
* `Alexander Terry <https://github.com/mralext20>`_
* `peterpans01 <https://github.com/peterpans01>`_

View File

@@ -1,8 +1,7 @@
include version.py
include CHANGELOG.rst
include CONTRIBUTORS.rst
include AUTHORS.rst
include README.rst
include LICENSE
include rtv.1
include rtv/rtv.cfg
include rtv/templates/*

View File

@@ -76,14 +76,14 @@ Move the cursor using either the arrow keys or *Vim* style movement
Press ``/`` to open the navigation prompt, where you can type things like
- /front
- /r/commandprompt+linuxmasterrace
- /r/programming/controversial-week
- /u/me
- /u/multi-mod/m/art
- /domain/github.com
- ``/front``
- ``/r/commandprompt+linuxmasterrace``
- ``/r/programming/controversial-week``
- ``/u/me``
- ``/u/multi-mod/m/art``
- ``/domain/github.com``
See `CONTROLS <https://github.com/michael-lazar/rtv/blob/master/CONTROLS.rst>`_ for the full list of commands.
See `CONTROLS <https://github.com/michael-lazar/rtv/blob/master/CONTROLS.rst>`_ for the full list of commands
========
Settings
@@ -93,55 +93,34 @@ Settings
Configuration
-------------
Configuration settings are stored in ``{HOME}/.config/rtv/rtv.cfg``.
Auto-generate the config file by running
Configuration files are stored in the ``{HOME}/.config/rtv/`` directory
See `rtv.cfg <https://github.com/michael-lazar/rtv/blob/master/rtv/templates/rtv.cfg>`_ for the full list of configurable options. You can clone this file onto your system by running
.. code-block:: bash
$ rtv --copy-config
See the `default config <https://github.com/michael-lazar/rtv/blob/master/rtv/rtv.cfg>`_ for the full list of settings.
------
Editor
------
You can compose posts and reply to comments using your preferred text editor.
Set the editor by changing ``$RTV_EDITOR`` in your environment.
.. code-block:: bash
$ export RTV_EDITOR=gedit
If not specified, the default system ``$EDITOR`` (or *nano*) will be used.
-----------
Web Browser
Environment
-----------
You can open submission links using your web browser.
On most systems the default web browser will open in a new window.
If you prefer the complete terminal experience, set ``$BROWSER`` to a console-based web browser.
RTV will respect the following environment variables when accessing external programs
.. code-block:: bash
$ export BROWSER=w3m
`w3m <http://w3m.sourceforge.net/>`_, `lynx <http://lynx.isc.org/>`_, and `elinks <http://elinks.or.cz/>`_ are all good choices.
----------
Url Viewer
----------
You can extract links from inside of comments using urlview.
Use ``$RTV_URLVIEWER`` to specify a custom url viewer.
.. code-block:: bash
$ export RTV_URLVIEWER=urlview
`urlview <https://github.com/sigpipe/urlview>`_ and `urlscan <https://github.com/firecat53/urlscan>`_ are known to be compatible, but any program that accepts text via a stdin pipe will do.
These applications don't come pre-installed, but are available through most systems' package managers.
``$BROWSER``
Submission links can be opened in your web browser.
On most systems the default web browser will open in a new window.
If you prefer the complete terminal experience, try using a console-based web browser
(`w3m <http://w3m.sourceforge.net/>`_, `lynx <http://lynx.isc.org/>`_, and `elinks <http://elinks.or.cz/>`_ are all good choices).
``$PAGER``
Extra long comments and submissions can be viewed through the system pager.
``$RTV_EDITOR``
Compose posts and replying to comments is done using your preferred text editor.
If not specified, the default system ``$EDITOR`` (or `nano <https://www.nano-editor.org/>`_) will be used.
``$RTV_URLVIEWER``
A url viewer can be used to extract links from inside of comments.
`urlview <https://github.com/sigpipe/urlview>`_ and `urlscan <https://github.com/firecat53/urlscan>`_ are known to be compatible.
These applications don't come pre-installed, but are available through most systems' package managers.
===
FAQ

View File

@@ -5,12 +5,14 @@ import os
import sys
import locale
import logging
import warnings
import six
import praw
import tornado
from . import docs
from .config import Config, copy_default_config
from .config import Config, copy_default_config, copy_default_mailcap
from .oauth import OAuthHelper
from .terminal import Terminal
from .objects import curses_session, Color
@@ -34,6 +36,10 @@ def main():
# Squelch SSL warnings
logging.captureWarnings(True)
if six.PY3:
# These ones get triggered even when capturing warnings is turned on
warnings.simplefilter('ignore', ResourceWarning) #pylint:disable=E0602
locale.setlocale(locale.LC_ALL, '')
# Set the terminal title
@@ -59,6 +65,10 @@ def main():
copy_default_config()
return
if config['copy_mailcap']:
copy_default_mailcap()
return
# Load the browsing history from previous sessions
config.load_history()
@@ -103,7 +113,7 @@ def main():
if not config['monochrome']:
Color.init()
term = Terminal(stdscr, config['ascii'])
term = Terminal(stdscr, config)
with term.loader('Initializing', catch_exception=False):
reddit = praw.Reddit(user_agent=user_agent,
decode_html_entities=False,

View File

@@ -16,9 +16,11 @@ from .objects import KeyMap
PACKAGE = os.path.dirname(__file__)
HOME = os.path.expanduser('~')
TEMPLATE = os.path.join(PACKAGE, 'templates')
DEFAULT_CONFIG = os.path.join(PACKAGE, 'rtv.cfg')
DEFAULT_CONFIG = os.path.join(TEMPLATE, 'rtv.cfg')
DEFAULT_MAILCAP = os.path.join(TEMPLATE, 'mailcap')
XDG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.join(HOME, '.config'))
CONFIG = os.path.join(XDG_HOME, 'rtv', 'rtv.cfg')
MAILCAP = os.path.join(HOME, '.mailcap')
TOKEN = os.path.join(XDG_HOME, 'rtv', 'refresh-token')
HISTORY = os.path.join(XDG_HOME, 'rtv', 'history.log')
@@ -59,30 +61,50 @@ def build_parser():
parser.add_argument(
'--copy-config', dest='copy_config', action='store_const', const=True,
help='Copy the default configuration to {HOME}/.config/rtv/rtv.cfg')
parser.add_argument(
'--copy-mailcap', dest='copy_mailcap', action='store_const', const=True,
help='Copy an example mailcap configuration to {HOME}/.mailcap')
parser.add_argument(
'--enable-media', dest='enable_media', action='store_const', const=True,
help='Open external links using programs defined in the mailcap config')
return parser
def copy_default_mailcap(filename=MAILCAP):
"""
Copy the example mailcap configuration to the specified file.
"""
return _copy_settings_file(DEFAULT_MAILCAP, filename, 'mailcap')
def copy_default_config(filename=CONFIG):
"""
Copy the default configuration file to the user's {HOME}/.config/rtv
Copy the default rtv user configuration to the specified file.
"""
return _copy_settings_file(DEFAULT_CONFIG, filename, 'config')
def _copy_settings_file(source, destination, name):
"""
Copy a file from the repo to the user's home directory.
"""
if os.path.exists(filename):
if os.path.exists(destination):
try:
ch = six.moves.input(
'File %s already exists, overwrite? y/[n]):' % filename)
'File %s already exists, overwrite? y/[n]):' % destination)
if ch not in ('Y', 'y'):
return
except KeyboardInterrupt:
return
filepath = os.path.dirname(filename)
filepath = os.path.dirname(destination)
if not os.path.exists(filepath):
os.makedirs(filepath)
print('Copying default settings to %s' % filename)
shutil.copy(DEFAULT_CONFIG, filename)
os.chmod(filename, 0o664)
print('Copying default %s to %s' % (name, destination))
shutil.copy(source, destination)
os.chmod(destination, 0o664)
class OrderedSet(object):
@@ -215,6 +237,7 @@ class Config(object):
'monochrome': partial(config.getboolean, 'rtv'),
'clear_auth': partial(config.getboolean, 'rtv'),
'persistent': partial(config.getboolean, 'rtv'),
'enable_media': partial(config.getboolean, 'rtv'),
'history_size': partial(config.getint, 'rtv'),
'oauth_redirect_port': partial(config.getint, 'rtv'),
'oauth_scope': lambda x: rtv[x].split(',')

View File

@@ -40,3 +40,7 @@ class BrowserError(RTVError):
class TemporaryFileError(RTVError):
"Indicates that an error has occurred and the file should not be deleted"
class MailcapEntryNotFound(RTVError):
"A valid mailcap entry could not be coerced from the given url"

175
rtv/mime_parsers.py Normal file
View File

@@ -0,0 +1,175 @@
import re
import logging
import mimetypes
import requests
from bs4 import BeautifulSoup
_logger = logging.getLogger(__name__)
class BaseMIMEParser(object):
"""
BaseMIMEParser can be sub-classed to define custom handlers for determining
the MIME type of external urls.
"""
pattern = re.compile(r'.*$')
@staticmethod
def get_mimetype(url):
"""
Guess based on the file extension.
Args:
url (text): Web url that was linked to by a reddit submission.
Returns:
modified_url (text): The url (or filename) that will be used when
constructing the command to run.
content_type (text): The mime-type that will be used when
constructing the command to run. If the mime-type is unknown,
return None and the program will fallback to using the web
browser.
"""
filename = url.split('?')[0]
content_type, _ = mimetypes.guess_type(filename)
return url, content_type
class GfycatMIMEParser(BaseMIMEParser):
"""
Gfycat provides a primitive json api to generate image links. URLs can be
downloaded as either gif, webm, or mjpg. Webm was selected because it's
fast and works with VLC.
https://gfycat.com/api
https://gfycat.com/UntidyAcidicIberianemeraldlizard -->
https://giant.gfycat.com/UntidyAcidicIberianemeraldlizard.webm
"""
pattern = re.compile(r'https?://(www\.)?gfycat\.com/[^.]+$')
@staticmethod
def get_mimetype(url):
parts = url.split('/')
api_url = '/'.join(parts[:-1] + ['cajax', 'get'] + parts[-1:])
resp = requests.get(api_url)
image_url = resp.json()['gfyItem']['webmUrl']
return image_url, 'video/webm'
class YoutubeMIMEParser(BaseMIMEParser):
"""
Youtube videos can be streamed with vlc or downloaded with youtube-dl.
Assign a custom mime-type so they can be referenced in mailcap.
"""
pattern = re.compile(
r'(?:https?://)?(m\.)?(?:youtu\.be/|(?:www\.)?youtube\.com/watch'
r'(?:\.php)?\'?.*v=)([a-zA-Z0-9\-_]+)')
@staticmethod
def get_mimetype(url):
return url, 'video/x-youtube'
class GifvMIMEParser(BaseMIMEParser):
"""
Special case for .gifv, which is a custom video format for imgur serves
as html with a special <video> frame. Note that attempting for download as
.webm also returns this html page. However, .mp4 appears to return the raw
video file.
"""
pattern = re.compile(r'.*[.]gifv$')
@staticmethod
def get_mimetype(url):
modified_url = url[:-4] + 'mp4'
return modified_url, 'video/mp4'
class RedditUploadsMIMEParser(BaseMIMEParser):
"""
Reddit uploads do not have a file extension, but we can grab the mime-type
from the page header.
"""
pattern = re.compile(r'https://i\.reddituploads\.com/.+$')
@staticmethod
def get_mimetype(url):
page = requests.head(url)
content_type = page.headers.get('Content-Type', '')
content_type = content_type.split(';')[0] # Strip out the encoding
return url, content_type
class ImgurMIMEParser(BaseMIMEParser):
"""
The majority of imgur links don't point directly to the image, so we need
to open the provided url and scrape the page for the link.
Scrape the actual image url from an imgur landing page. Imgur intentionally
obscures this on most reddit links in order to draw more traffic for their
advertisements.
There are a couple of <meta> tags that supply the relevant info:
<meta name="twitter:image" content="https://i.imgur.com/xrqQ4LEh.jpg">
<meta property="og:image" content="http://i.imgur.com/xrqQ4LE.jpg?fb">
<link rel="image_src" href="http://i.imgur.com/xrqQ4LE.jpg">
"""
pattern = re.compile(r'https?://(w+\.)?(m\.)?imgur\.com/[^.]+$')
@staticmethod
def get_mimetype(url):
page = requests.get(url)
soup = BeautifulSoup(page.content, 'html.parser')
tag = soup.find('meta', attrs={'name': 'twitter:image'})
if tag:
url = tag.get('content')
if GifvMIMEParser.pattern.match(url):
return GifvMIMEParser.get_mimetype(url)
return BaseMIMEParser.get_mimetype(url)
class ImgurAlbumMIMEParser(BaseMIMEParser):
"""
Imgur albums can contain several images, which need to be scraped from the
landing page. Assumes the following html structure:
<div class="post-image">
<a href="//i.imgur.com/L3Lfp1O.jpg" class="zoom">
<img class="post-image-placeholder"
src="//i.imgur.com/L3Lfp1Og.jpg" alt="Close up">
<img class="js-post-image-thumb"
src="//i.imgur.com/L3Lfp1Og.jpg" alt="Close up">
</a>
</div>
"""
pattern = re.compile(r'https?://(w+\.)?(m\.)?imgur\.com/a(lbum)?/[^.]+$')
@staticmethod
def get_mimetype(url):
page = requests.get(url)
soup = BeautifulSoup(page.content, 'html.parser')
urls = []
for div in soup.find_all('div', class_='post-image'):
img = div.find('img')
src = img.get('src') if img else None
if src:
urls.append('http:{0}'.format(src))
if urls:
return " ".join(urls), 'image/x-imgur-album'
else:
return url, None
# Parsers should be listed in the order they will be checked
parsers = [
GfycatMIMEParser,
ImgurAlbumMIMEParser,
ImgurMIMEParser,
RedditUploadsMIMEParser,
YoutubeMIMEParser,
GifvMIMEParser,
BaseMIMEParser]

View File

@@ -190,7 +190,7 @@ class LoadScreen(object):
self.exception = e
exc_name = type(e).__name__
_logger.info('Loader caught: {0} - {1}'.format(exc_name, e))
_logger.info('Loader caught: %s - %s', exc_name, e)
if isinstance(e, KeyboardInterrupt):
# Don't need to print anything for this one, just swallow it

View File

@@ -81,7 +81,7 @@ class SubredditPage(Page):
If this was pressed on the front page, go back to the last subreddit.
"""
if not self.content.name == '/r/front':
if self.content.name != '/r/front':
target = '/r/front'
self._toggled_subreddit = self.content.name
else:
@@ -124,7 +124,7 @@ class SubredditPage(Page):
self.open_submission(url=data['url_full'])
self.config.history.add(data['url_full'])
else:
self.term.open_browser(data['url_full'])
self.term.open_link(data['url_full'])
self.config.history.add(data['url_full'])
@SubredditController.register(Command('SUBREDDIT_POST'))

62
rtv/templates/mailcap Normal file
View File

@@ -0,0 +1,62 @@
# Example mailcap file for Reddit Terminal Viewer
# https://github.com/michael-lazar/rtv/
#
# Copy the contents of this file to {HOME}/.mailcap, or point to using $MAILCAPS
# Then launch RTV using the --enable-media flag. All shell commands defined in
# this file depend on external programs that must be installed on your system.
#
# HELP REQUESTED! If you come up with your own commands (especially for OS X)
# and would like to share, please post an issue on the GitHub tracker and we
# can get them added to this file as references.
#
#
# Mailcap 101
# - The first entry with a matching MIME type will be executed, * is a wildcard
# - %s will be replaced with the image or video url
# - Add ``test=test -n "$DISPLAY"`` if your command opens a new window
# - Add ``needstermial`` for commands that use the terminal
# - Add ``copiousoutput`` for commands that dump text to stdout
###############################################################################
# Commands below this point will open media in a separate window without
# pausing execution of RTV.
###############################################################################
# Feh is a simple and effective image viewer
# Note that rtv returns a list of urls for imgur albums, so we don't put quotes
# around the `%s`
image/x-imgur-album; feh -g 640x480 %s; test=test -n "$DISPLAY"
image/*; feh -g 640x480 '%s'; test=test -n "$DISPLAY"
# Youtube videos are assigned a custom mime-type, which can be streamed with
# vlc or youtube-dl.
video/x-youtube; vlc '%s' --width 640 --height 480; test=test -n "$DISPLAY"
video/x-youtube; youtube-dl -q -o - '%s' | mpv - --autofit 640x480; test=test -n "$DISPLAY"
# Mpv is a simple and effective video streamer
video/webm; mpv '%s' --autofit 640x480; test=test -n "$DISPLAY"
video/*; mpv '%s' --autofit 640x480; test=test -n "$DISPLAY"
###############################################################################
# Commands below this point will attempt to display media directly in the
# terminal when X is not available.
###############################################################################
# View true images in the terminal, supported by rxvt-unicode, xterm and st
# Requires the w3m-img package
# image/*; w3m -o 'ext_image_viewer=off' '%s'; needsterminal
# Don't have a solution for albums yet
image/x-imgur-album; echo
# 256 color images using half-width unicode characters
# Much higher quality that img2txt, but must be built from source
# https://github.com/rossy/img2xterm
image/*; curl -s '%s' | convert -resize 80x80 - jpg:/tmp/rtv.jpg && img2xterm /tmp/rtv.jpg; needsterminal; copiousoutput
# Display images in classic ascii using img2txt and lib-caca
image/*; curl -s '%s' | convert - jpg:/tmp/rtv.jpg && img2txt -f utf8 /tmp/rtv.jpg; needsterminal; copiousoutput
# Ascii videos
video/x-youtube; youtube-dl -q -o - '%s' | mplayer -cache 8192 -vo caca -quiet -; needsterminal
video/*; wget '%s' -O - | mplayer -cache 8192 -vo caca -quiet -; needsterminal

View File

@@ -34,6 +34,9 @@ clear_auth = False
; Maximum number of opened links that will be saved in the history file.
history_size = 200
; Open external links using programs defined in the mailcap config.
enable_media = True
################
# OAuth Settings
################

View File

@@ -17,10 +17,13 @@ from contextlib import contextmanager
import six
from kitchen.text.display import textual_width_chop
from mailcap_fix import mailcap
from . import exceptions
from . import mime_parsers
from .objects import LoadScreen, Color
try:
# Added in python 3.4+
from html import unescape
@@ -42,28 +45,29 @@ class Terminal(object):
RETURN = 10
SPACE = 32
def __init__(self, stdscr, ascii=False):
def __init__(self, stdscr, config):
self.stdscr = stdscr
self.ascii = ascii
self.config = config
self.loader = LoadScreen(self)
self._display = None
self._mailcap_dict = mailcap.getcaps()
@property
def up_arrow(self):
symbol = '^' if self.ascii else ''
symbol = '^' if self.config['ascii'] else ''
attr = curses.A_BOLD | Color.GREEN
return symbol, attr
@property
def down_arrow(self):
symbol = 'v' if self.ascii else ''
symbol = 'v' if self.config['ascii'] else ''
attr = curses.A_BOLD | Color.RED
return symbol, attr
@property
def neutral_arrow(self):
symbol = 'o' if self.ascii else ''
symbol = 'o' if self.config['ascii'] else ''
attr = curses.A_BOLD
return symbol, attr
@@ -75,7 +79,7 @@ class Terminal(object):
@property
def guilded(self):
symbol = '*' if self.ascii else ''
symbol = '*' if self.config['ascii'] else ''
attr = curses.A_BOLD | Color.YELLOW
return symbol, attr
@@ -228,7 +232,7 @@ class Terminal(object):
if isinstance(string, six.text_type):
string = unescape(string)
if self.ascii:
if self.config['ascii']:
if isinstance(string, six.binary_type):
string = string.decode('utf-8')
string = string.encode('ascii', 'replace')
@@ -279,7 +283,7 @@ class Terminal(object):
"""
if isinstance(message, six.string_types):
message = [message]
message = message.splitlines()
n_rows, n_cols = self.stdscr.getmaxyx()
@@ -317,6 +321,128 @@ class Terminal(object):
return ch
def open_link(self, url):
"""
Open a media link using the definitions from the user's mailcap file.
Most urls are parsed using their file extension, but special cases
exist for websites that are prevalent on reddit such as Imgur and
Gfycat. If there are no valid mailcap definitions, RTV will fall back
to using the default webbrowser.
RTV checks for certain mailcap fields to determine how to open a link:
- If ``copiousoutput`` is specified, the curses application will
be paused and stdout will be piped to the system pager.
- If `needsterminal`` is specified, the curses application will
yield terminal control to the subprocess until it has exited.
- Otherwise, we assume that the subprocess is meant to open a new
x-window, and we swallow all stdout output.
Examples:
Stream youtube videos with VLC
Browse images and imgur albums with feh
Watch .webm videos through your terminal with mplayer
View images directly in your terminal with fbi or w3m
Play .mp3 files with sox player
Send HTML pages your pager using to html2text
...anything is possible!
"""
if not self.config['enable_media']:
return self.open_browser(url)
try:
with self.loader('Checking link', catch_exception=False):
command, entry = self.get_mailcap_entry(url)
except exceptions.MailcapEntryNotFound:
return self.open_browser(url)
_logger.info('Executing command: %s', command)
needs_terminal = 'needsterminal' in entry
copious_output = 'copiousoutput' in entry
if needs_terminal or copious_output:
# Blocking, pause rtv until the process returns
with self.suspend():
os.system('clear')
p = subprocess.Popen(
[command], stderr=subprocess.PIPE,
universal_newlines=True, shell=True)
code = p.wait()
if copious_output:
six.moves.input('Press any key to continue')
if code != 0:
_, stderr = p.communicate()
_logger.warning(stderr)
self.show_notification(
'Program exited with status={0}\n{1}'.format(
code, stderr.strip()))
else:
# Non-blocking, open a background process
with self.loader('Opening page', delay=0):
p = subprocess.Popen(
[command], shell=True, universal_newlines=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# Wait a little while to make sure that the command doesn't
# exit with an error. This isn't perfect, but it should be good
# enough to catch invalid commands.
time.sleep(1.0)
code = p.poll()
if code is not None and code != 0:
_, stderr = p.communicate()
raise exceptions.BrowserError(
'Program exited with status={0}\n{1}'.format(
code, stderr.strip()))
def get_mailcap_entry(self, url):
"""
Search through the mime handlers list and attempt to find the
appropriate command to open the provided url with.
Will raise a MailcapEntryNotFound exception if no valid command exists.
Params:
url (text): URL that will be checked
Returns:
command (text): The string of the command that should be executed
in a subprocess to open the resource.
entry (dict): The full mailcap entry for the corresponding command
"""
for parser in mime_parsers.parsers:
if parser.pattern.match(url):
# modified_url may be the same as the original url, but it
# could also be updated to point to a different page, or it
# could refer to the location of a temporary file with the
# page's downloaded content.
try:
modified_url, content_type = parser.get_mimetype(url)
except Exception as e:
# If Imgur decides to change its html layout, let it fail
# silently in the background instead of crashing.
_logger.warn('parser %s raised an exception', parser)
_logger.exception(e)
raise exceptions.MailcapEntryNotFound()
if not content_type:
_logger.info('Content type could not be determined')
raise exceptions.MailcapEntryNotFound()
elif content_type == 'text/html':
_logger.info('Content type text/html, deferring to browser')
raise exceptions.MailcapEntryNotFound()
command, entry = mailcap.findmatch(
self._mailcap_dict, content_type, filename=modified_url)
if not entry:
_logger.info('Could not find a valid mailcap entry')
raise exceptions.MailcapEntryNotFound()
return command, entry
# No parsers matched the url
raise exceptions.MailcapEntryNotFound()
def open_browser(self, url):
"""
Open the given url using the default webbrowser. The preferred browser
@@ -359,7 +485,7 @@ class Terminal(object):
break # Success
elif code is not None:
raise exceptions.BrowserError(
'Browser exited with status=%s' % code)
'Program exited with status=%s' % code)
time.sleep(0.01)
else:
raise exceptions.BrowserError(
@@ -453,6 +579,12 @@ class Terminal(object):
_logger.info('File deleted: %s', filepath)
def open_urlview(self, data):
"""
Pipe a block of text to urlview, which displays a list of urls
contained in the text and allows the user to open them with their
web browser.
"""
urlview = os.getenv('RTV_URLVIEWER') or 'urlview'
try:
with self.suspend():
@@ -461,6 +593,16 @@ class Terminal(object):
p.communicate(input=data.encode('utf-8'))
except KeyboardInterrupt:
p.terminate()
code = p.poll()
if code == 1:
# Clear the "No URLs found." message from stdout
sys.stdout.write("\033[F")
sys.stdout.flush()
if code == 1:
self.show_notification('No URLs found')
except OSError:
self.show_notification(
'Failed to open {0}'.format(urlview))

57
scripts/build_authors.py Executable file
View File

@@ -0,0 +1,57 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Scrape the project contributors list from Github and update AUTHORS.rst
"""
from __future__ import unicode_literals
import os
import time
import logging
import requests
_filepath = os.path.dirname(os.path.relpath(__file__))
FILENAME = os.path.abspath(os.path.join(_filepath, '..', 'AUTHORS.rst'))
URL = "https://api.github.com/repos/michael-lazar/rtv/contributors"
HEADER = """\
================
RTV Contributors
================
Thanks to the following people for their contributions to this project.
"""
def main():
logging.captureWarnings(True)
# Request the list of contributors
print('GET {}'.format(URL))
resp = requests.get(URL)
contributors = resp.json()
lines = []
for contributor in contributors:
time.sleep(1.0)
# Request each contributor individually to get the full name
print('GET {}'.format(contributor['url']))
resp = requests.get(contributor['url'])
user = resp.json()
name = user.get('name') or user['login']
url = user['html_url']
lines.append('* `{} <{}>`_'.format(name, url))
print('Writing to {}'.format(FILENAME))
text = HEADER + '\n'.join(lines)
text = text.encode('utf-8')
with open(FILENAME, 'wb') as fp:
fp.write(text)
if __name__ == '__main__':
main()

3
scripts/build_manpage.py Normal file → Executable file
View File

@@ -1,3 +1,5 @@
#!/usr/bin/env python
"""
Internal tool used to automatically generate an up-to-date version of the rtv
man page. Currently this script should be manually ran after each version bump.
@@ -6,6 +8,7 @@ In the future, it would be nice to have this functionality built into setup.py.
Usage:
$ python scripts/build_manpage.py
"""
import os
import sys
from datetime import datetime

View File

@@ -1,2 +1,12 @@
[wheel]
universal = 1
[metadata]
requires-dist =
tornado
praw>=3.5,<4
six
requests
kitchen
futures; python_version=="2.6" or python_version=="2.7"

View File

@@ -3,7 +3,8 @@ import setuptools
from version import __version__ as version
requirements = ['tornado', 'praw==3.5.0', 'six', 'requests', 'kitchen']
requirements = ['tornado', 'praw==3.5.0', 'six', 'requests', 'kitchen',
'beautifulsoup4', 'mailcap-fix']
# Python 2: add required concurrent.futures backport from Python 3.2
if sys.version_info.major <= 2:
@@ -20,7 +21,7 @@ setuptools.setup(
license='MIT',
keywords='reddit terminal praw curses',
packages=['rtv'],
package_data={'rtv': ['templates/*', 'rtv.cfg']},
package_data={'rtv': ['templates/*']},
data_files=[("share/man/man1", ["rtv.1"])],
extras_require={
':python_version=="2.6" or python_version=="2.7"': ['futures']},

View File

@@ -0,0 +1,46 @@
interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Connection: [keep-alive]
User-Agent: [python-requests/2.5.1 CPython/3.4.0 Linux/3.13.0-92-generic]
method: GET
uri: https://gfycat.com/cajax/get/DeliciousUnfortunateAdouri
response:
body:
string: !!binary |
H4sIAAAAAAAAA6yVTW/UMBCG/0o157B1vnd9qRAVCAkhBPTEIuQkE2eKnQR/tN1W/e/Im6QtSBxQ
9pYZv/N4xrLfPIBsD+8dauAPx88GODSoqKbBW9+3g3G+Fw5FM3hDEAXRR6EROFwusqtn2euXMq8r
NMAhKdMsSVhcQATeopnrqb8RDZoGNURwS43rgEO8SxhE0CHJzoWYbUPcGqHxs3ChLg2J3uu3IWeB
Q3Ys0WN2ZRRw6JwbLd+f788lid5tZHuohdvUg96f/7vnjR6z0AdWehUmACbO+DfHdV5X9j9AYxhr
qEjhKtSriTFPOAWfBuvQnAR7PUqIYDwBcGLMwGNpWrB1yLRg89wL7xSTL6yXrcbsdOiYLWgt7nJd
vaN2BdLSPf4waJ2h2mGzkdRO5GQ1WQulFt71KNfdeD2NLKld9QKndiS1X+g++EWcFOWWFWk+mcSS
Tcu83LFifvNzNovjmCVBWRsUDi8ny4mzYrvLiiJJIIIbwlsLPEtzVkbgyKmjJD+zWA99c6ZIk7u4
uIAI8M4Z8QG1FhZ475WKQDc5cBB1KvJEJKxi2G6rGqs8LuKSbUsmknyXhkslpAX+DTqS3S8vFLmD
pNZCBCQsBWt4YaDfI+htewscghVaEWw3DnYbDnLa2A7e1Djnm0Mv9PC05CuDTUNuSUxR+Bn8GX/F
uyeNop9H9z3iyM7htDb6SpHtsFnW0daGRkdDv0jqYTyYYPJvlCAtqH8Gi156IfF5s8fH3wAAAP//
AwCCMwEoqAYAAA==
headers:
Access-Control-Allow-Origin: ['*']
Age: ['198']
CF-RAY: [2c864ef7dd9251ca-SJC]
Cache-Control: ['max-age=3600, public', no-cache="set-cookie"]
Cache-control: ['max-age=3600, public', no-cache="set-cookie"]
Connection: [keep-alive]
Content-Encoding: [gzip]
Content-Type: [application/json]
Date: ['Tue, 26 Jul 2016 07:58:47 GMT']
Server: [cloudflare-nginx]
Set-Cookie: ['__cfduid=d7ad36c869d5bc651ffecbeaa256ec04a1469519927; expires=Wed,
26-Jul-17 07:58:47 GMT; path=/; domain=.gfycat.com; HttpOnly', AWSELB=45571F3D1CB564660610CD8E78AF6C77C484B1D1B8960B10499E0D136B44904A9FF7E1200AF9BC33EB2308C475B459AC4129063CBA85B180E94588869669B8CE501B3E8F88;PATH=/;MAX-AGE=200]
Vary: [CF-Visitor]
Via: [1.1 varnish]
X-Powered-By: [PHP/5.5.30]
X-Varnish: [3112173893 3112153573]
X-orig: ['{"scheme":"https"}']
X-prot: [http]
X-secure: ['YES']
status: {code: 200, message: OK}
version: 1

View File

@@ -0,0 +1,302 @@
interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Connection: [keep-alive]
User-Agent: [python-requests/2.5.1 CPython/3.4.0 Linux/3.13.0-92-generic]
method: GET
uri: http://imgur.com/yW0kbMi
response:
body:
string: !!binary |
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
ub3/w/8DeTBSHPINAQA=
headers:
Accept-Ranges: [bytes]
Age: ['0']
Cache-Control: ['max-age=60, stale-while-revalidate=600, stale-if-error=86400,
public']
Connection: [keep-alive]
Content-Encoding: [gzip]
Content-Length: ['15347']
Content-Type: [text/html; charset=utf-8]
Date: ['Tue, 26 Jul 2016 07:57:53 GMT']
Fastly-Debug-Digest: [18f5d645b7a2c7ea9f90280933e21eb8483b6d4c6e792eb11d0224013b5e3836]
Server: [cat factory 1.0]
Vary: [Accept-Encoding]
X-Cache: ['MISS, MISS']
X-Cache-Hits: ['0, 0']
X-Frame-Options: [DENY]
X-Served-By: ['cache-iad2146-IAD, cache-sjc3142-SJC']
status: {code: 200, message: OK}
version: 1

View File

@@ -0,0 +1,312 @@
interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Connection: [keep-alive]
User-Agent: [python-requests/2.5.1 CPython/3.4.0 Linux/3.13.0-92-generic]
method: GET
uri: http://imgur.com/yjP1v4B
response:
body:
string: !!binary |
H4sIAAAAAAAAA+19bXvbuLHo5+7z3P/AqKdr50Tvr5ZiOXEcJ+s2L27sbNquc3UoEpJoUyRDUra1
qf/7nRmAJEiCtOwkbc99jF1HJAgMBoPBYDAYALuPXr4/OP378aG2CJe2dvzxxZujA61SazQ+dQ4a
jZenL7W//XL69o3Wqje1k9C3jLDROHxX0SqLMPRGjcbV1VX9qlN3/Xnj9EPjGqG0MJt4rAWUp26G
ZmXvp10qxNad+bjCnIp2vbSdYKwA1RoOhxwC5WK6ufeTBmHXtpwLbeGzGc8VQLaZ64RBfe66c5vp
nhXUDXfZMILg2UxfWvZ6/N5jzpMT3QlGnWazCn9WtQsPXXzow0MfHwbwMMCHHXiAP6ui+cweV4Jw
bbNgwVhYERiEVmizPXxUBUoTP/xE//7C1lXtYLGaBo+0l662dlfale6EWuhqnq2vn2k17fXRK811
tKPlfOXvNngRvLglC3UNq1pjX1bW5bhiQHWZE9bCtccqmngbV0J2HTaQYE+Nhe4HLByvwlltp6I1
OCBV4MAdfcnGFd+dumEgAZy5tu1eVTXLMdl1CgyvWhbABVtfub4pg7CW+pwFVW22chwgAb1qCzcI
o+cAULWceRVorRuhBRSYW7Oqdmn5us2TQG5j5fsATmOX8C+9hwzTBCtIFISuvw6ZbRMYyF1aYQwy
ziYLDN/ysGQJ7X2tDoAEildWuICW8ixDc2faK6yJtvJsVzeZqU3XWisIX+hzz/LY+9kLX9cv3aB+
a4tvQE7D9da+NV+EEmIHUZzWbrb6nFuq2pFj1GOAeX75W+3jfu3AXXp6aE1tmWWODseH5pw9jTNL
XYyYX7dD5jt6CJmQ2yDC82zL0JFeDT8InkD/hU/IrQANsdFe67bN/HVF6qPYRRkzgzr+O135DvOp
i1IGkf7ZzPWXejgmgFFVEkQsAxuI40DN0vCceVRGoxHULYRFUDnXNGb6JWaqddrXnXadUgfW7wyE
DcVEnfl7FDHsXw/7qSIo5nsW0epft9JFUEy+CGwgVgvdlbGoUU7PZwDPcwNmblZSj1NL5ibOkMtA
av3aqWWzA9d2fYmf/tg2zH7H2CjvERYr5S1HqtuVkIqCVG1JSgv6kjiEQUBd6aXlNOa2O9XtOg4U
rW5/2B22BztNYr6kL3yPYjiDK8vJtV5ZfzsPXOeJy5bTpCVF74JBTyqUJ6lj8mcr3x6LREkCgVFj
fX7cuuy+iDvwbUIrM0yp+mlpBaBv3wF/SP2vQT9u50e12m8g9o8OteHnvYJWV7Wy5Rj2ymTIBg2L
1YKVcRFQczfzXNLY2330G3NMa/a5VpOEbgqRpGhDd1wH6GdnKFZCDalWvAd6vusxP1yPK+58BBSV
Ot3G0DinykHd6EtmWjoU5NhrDUZWxhxNd0xte6lf164sM1yMtH636V0/ztRnKZEzi4OMgSxTvJHp
LnXLmVwy35qtpWrpLTbQp2xHnw2bO4bRN2fw1OmZ5lBvNzs73UpqrEzRJ7BCNkH48jCZ5vZMptl0
pJvQ0WW9p9XudFrQ0e+aqdnq9IelmTxvYpmpTIN+u9XtDDpQsd6gMK9uj6AhfNcyMyxAdE/xgKD7
s5nvLsezKTQT833kwNtBI3p3Ip6U19ONi/SQAMgItli6Uws1l2Iolhv8iIohWKwUapksTfl+Z7iz
A43c2yT/XYlyxaa3ddVsF5W7RghKK/RJ4mYJxnPKXZqDd6ksGbHA0mxQxxG0IZ+DobTdpBnloAJK
cl0CdJuAj7p1Ai/Vte8LTpY8UiOtwkVK+bmlTf3QMmw2umc2bzW1LRiC5JzR9BdnzTPdYFPXvRDK
E4KM1JgcqaOQLQtJlJ5SXlomc+tuSOU2BMCioABHWlwllSrHz5LgFxy9qJ978xImSRVBKI5oaIkK
iotot9Sz30IwC8bnWxRiMB1Q5e8CpqTC2FyKGteXXvdORYwCBpNiNiER8YOK4LwgQpolGipgae5Q
9WdD9800cZIQF4BdT7Cb/F0FT8FecdiEJmpGU5UUIaUKm5SE5oTLZwLaH8M7lJlm7SiUsngJsBSD
R6GU0UuABaHP9GURsDJqqNjn1oJGAvREJaSIIxOA9KBiyBSfq40/JJBH2umCaUs3CDX9igXukglr
FE4fQB5qRw7qvCys5wedzID6PQuRZqa7HKximmWbT3DuJ5Hja4oSleeExnVYGcVG3MBYsKWOltdK
NZOYyA0pP7HpCaoTme8oghI4ibaQSUYKEKTjw13mo+ciVSzd3icbIKRLY0yJYkROmO4bC5E0DYkS
hro/Z3LlEgYMKOuzL+Ov/GkC9F1O0ELtzG9UsL6sYCZSsxxvRQB9NKn5zORtnIdRSUG4id/4026D
t5loS4p6xOecMIPCeecOzDtT7UoTx3P9UuexFS3wjez0E40MFvNcew1zftnEsJcUmJ907ja4UX13
6pprzbD1IBhXIsuNaV1qljmugGbu1bjsA2AQmx8/ovDTT3Gu0PWmOiogGE+xAjyWyPyawZCvJf7c
1eN5dcJAUSYOjYxZmu3OXa2imXqo1871mTuufH3+nMzCz5+Pnj/n8J8/rz5/jj0R4uAzzlPpK2Z+
/vzmJmtFkhDEJFRSrrq7DV3Sf3YDT3dUCC6Zs6pNQ0cjs5Xu++5VzXSv5M5I+eUyAb8a5sukoXQr
Ox+JAWbfeyqqNc7dKc4pBb0tYsp70gtBcXpdsS2faRzaI6TEbgPKVyKmCoXIToHe98UO89I3h11B
xEy3A4a4Ynw5isrIQhz1qbvCEfN+WFJuTkR61AhwOX4ZVGjqmXT3+9KLwHBMCBYtnLA7YSIRxfOC
+9MEMsuI4Pud8EiZC+9LDwDCkTAhh41aASBi3QEPMbjg7IuQ0U2+8HhvhMxL0EysQDRS/HoHlBog
021PGhoWRoM5tVVwX5wQHEfHYTDq4euzYnx2GypxJcs6mMriwFAwlMgpceRBwVmbuaAc+NpCX05X
MK77RaISgwwAR+VA5C5IjkHJ3qF7f+6mcjnN6JEGjmypUVCW7vnWpW6s742ByM9xEC+3YSGIFix1
264tQYGynBqMa4m9O4feH3XTWLiWwe7NXDGAiOE18V6IbAHfUFDG7lozHzS1SGvKWUo8ewU1DYCb
L1jdW3jPqLo0dflTZ/9P7Vfwv5wBXokQP+tL7ynNzOgJNCSQ7ePpKgxdh2L4YvYY4dJ7sHCvJggp
GNNIJSJ1n0nvfHY27vQqaDt3aUl7XHHcika1mLo+UHNcacJnXJGATmIFOEUaWQ4kZTUY+oyLpxpP
N3Jchz3VXJAiM9u9Gi0s02TOU40XMmo3veunGjfJ9+hZtDotL48GEFPRdHQAOPV1B9QdnzkGTJtC
f8WwA3PCqtsiJSJxDijmQkRyol+i3PEvNe5rUOMUFAyFNKsZ7grnS0QkOZ4vLtT4XIt/3Svkm1yk
gpFy+h6qeMl78kFW/bgfgEC7htMqHRrC11TqHmrGgLQespdCtsVUcGl6GCjEVJkCGOWGSdmyoh6n
OX5xOQLdYDXleT7Su0ZLsbcPwRuUB3Nx8sAoEgjw/dR9bc2OnEv3gpkZyYD0A7anlBxVjllELy4n
fsX5PhppXx+9+h44L9mSzdEXqQjppX7B9t9Cqnuh+xZya7qG+YuxzY+cKg6NxXSK7VQyWrS7oiCZ
e2mOEiXNMrwyR1RwCKMDGhD23rErzXODsDx3wvq1eFyPkI9GHmYz7gBE86jc9ElVQKq/ZxPEfS5g
AMvUYRrv6JfZjrmy46oFIIbyKSgV8BFC4g4DNT7tT3q7IgeGXfRoSapHeTAKF0nDhYvwcEFZjBSp
5uOpC+Bi2CWLhKgeQRa4cUuFZDyoCHPUF4nSBFyb2qB7w/BhsIVr08DCDSvCBgXqh8JajGG3gdUo
WA2QVTCBGrXlbdTCIOelTBEdFJ0hCgWf1N1MFSQhEVhzB0ZAVFBIVohoeX4vkvhsbgWolsrL2GTt
TNqQUj6zuKR7sR5DnpWt+ycQfwQMjd81yymWCRgizovK+3a0oiS3ILbyihHL9EFZcvHWIKuW9sdU
x9Miw5OcpJ4yCFGSFAReRR4fD6EgpGNmqkn8G7NJKqmN6szUvVamFA44GOhBtWKmTIIhLkYIRd4D
U8yaTaLb0xUOQ6L0HCRQRWFwk3pIOoloeJvNQhK6NS8n4+V0lKSs3+US8+ZQpMSQS03rqaUFbBJ2
F608VFCgblud3W0sWsUlKj9k6S6HgtrhaF9SMTUsDIUfygKNlnvoVJodzVQhEQWEsG6QrqxUB2Bs
ayj8VGPXqUsLxnEBAL1clWmL9COEfuy7M8tm7/TLjJIkgB6ZlLS90xs0B91hlAiGwRgEjlPHUBGF
iVYVFCiW5imcHGAobseykFKNTPR/2uuZt7VccVF3+1A48Kl5PMffYpRXJFUFZSJ1pDI2FnGx10oO
F0mSaKiuB4brMXri2kxupapBM4H303NQHEsYRl1zkSGNrZyUlhULpVv5Kk0emUsYxgjiEdRHGyvW
tzDYruvBnH+kaTjNzi9HYRBedBDarR11EjHFh6fOsK9Ogk7EBERrDdu9Nm5+UKaD2dxH38aUWw3l
Ui4k2FJnBSXRYDDXG2k0PVcn8nx0wppwj4OSWi/0gFda2xIlb+XSJcttGNKLbXEslUTl4ohMbjwu
KOHwL44q/J37Ly1XIYME9FOhpsH1IbSJXbHphRWiRrEOuOkFhnQCXDwYc3aCSc4SxKZyph+4KyBY
ZKpS0ZqWzTnHqRa9CUpeKux6vjv3WRAAE9orRvajpX49rrSa+GQ5GAMViJKlc6dBCa6neQc9xt1Z
3REKlyuxC/4Kgw7zj4iAxSuXaQRwYCGZgAv5UN5itZw6umV/zPnKFfgelPl8JHAFpI8f3mwGtcSj
IQFKbs2bg0SXkRRMqW2Fmizpr9mAvJdnMjkUflTwUDaUfiwKWXrgiHks/Npkx0rc2lJrDmrtpqKb
ROHuH8qCMFPEqEVeepuNQ8fMT/tbFAXuM50UQ+5bRTO1QqWtvJxb9Y9sBIaCJs+z1yYxuQGXmzjy
854o5CIU6Kjy5iHJIZkGuksSuoWDeRR2HVc1ZshBrhsKO5hZatlFE42M8XwQrjm4rQn3MGYhyeHP
scTUrECLnUxgqkOKeVQBjQOz1/VCaArKRWG3UVw9Rbb4JUv5mK60UkBEZTjtEFTh29Oo3VeONRNO
SVk9MwZCCc9XS0/SCK2Me0icKTYmxrn4IpXISTajqW5cQCx9UFoNiyBFVE6Dg79LtzZdTac2asuF
APUMNHYNU36fxbMxsalBUSsMebPs3LdKjLIYDnkJym9F4S3gp/2KmyuV2XKRuUlTlhkoLtWW8VpG
aVK0c9RimqiSZkPuYyqiPK8qyD05XmtHBtAq3GTjoGUK8UQrG61/qRT7KKSEnu4wu5azzchB0eGi
UPCppLrllY9pPrOumUk1EsviZOlM+D3/hWMhVw19gmrwbnEzWtyNKB4BRAvub9xLxvdZPVOvcqcc
lbjR5xx6fs3ARUe0YReZG7CUV1RK0cJy4rj0Z4CogV6thUxfCs8l6lESkdOaklxZIoKG5jbDzYpw
ZZ2Er5AwqnCHH6LBt9Qm60KU75MZj5Scw5DABz1K3RlIZEu3OVrcB+hbkJO8imJ/olLk0g4qCa2c
AOYCAQ55a2yvLysWcDnyDcgl3iv4VIya3ITcJU2iWMo9l1MN00QuujRWObiQc388uSsb4hk5r92O
J7moRGiinwma7UBjvLSMb2pQglXmvqLEJnZZERiJd1ADbAuj74/PRs4sSpy4j5pAKOZ7Hnt/fBLn
tchtLREdacGdlySkGpWKkoxnm0DedI0VjkykSaFdhTuHEy/uHx99W3WEF5zwfVMjVuDqlvTeaPzk
hyWU+ZttgFKJH5waPQUnyt5JMZKRf9G3Ybep05ISPZ8t3UvdRhUfBFyMm3gXJ06YzGZ8v8D98Yxg
cDQj+FHshsiuUOeNcET9GDT6cK2J+PsjF4MiSBzFDPgyDOOkir4CvWNV4hN6O24EgKNEj0VdPKNB
yEoUKoxcD6J+XwtcFD20UivP3lPd69/lJhV5yMWC6oLVeFRsvPv/xmtuQ3c20bKqSYmKEaRXpezP
+7zKqVMGBX0O8z535dW4N4zryyaLVC7J/wM3JQvLFLn4VSI776DZGrB+p7czbPeNAWvt9PRpu2cO
2vqwM+uZ/KCcHBKOK7wKK3v/56c/xJMHA4/6wHV0wGhmoRdLNNlxvZVHaf+wu+go0/K1VKzBokMJ
KbFccW5J4ROvXO4lCwJaseEU+D8/ZbNL/qoEPvUR1XcR/Ye4xyUm6tHTuCZxwQ6t+/BaKjEC5tp7
52pX+prmFLxMgdwfePVSOPBjfCIkpA/cdERnCcUtfCdUy/Fco8niaGupBSuumt+KaYpZic7RU/QQ
/ZYxTvIN0SK7dw2NAzH+URSxTrrXYA6HXdWkXJmBQCqTJyrw2gJmO8Tv2oFrMuK9fBpbnzJ7T3Qn
LiVJeJJVC72nEJ10DO9rxoIZF8ize9DrGNm7YHSFvmrD0MWhprFWSJN8TQzb8qYubpNV4JpqJpFO
EhBpyhlQZ/ljPIh7hTYoFXwl1ATJjBiMQRVEJ1IrhhbRk9NWSK78UkoeYJa9fIa20iyvKPJ5lMu1
TcFimK125YM6n/avSvhZkSGy66V7J995OK5MpraOPmQfQSOGfBrl07C62Al3G15u5Ihf07UihyHh
NZhDS0GPjDzNNwEeIEYnsnh6EDKaR9DhfsCv7hVOZsX8+4pN8eyIkYqauWLzxSh5Kcur359P/xN4
NEcdggWEXtKZDPLXKEU+FgOXTTjdg+ynkB9GQPoZKQVMFHa57sBLFdmUCTHscmttVCdT94FrX8K/
uw3+ZeOs5GdX2XuDP3fObKyCEHT4vQP6Lc++2+AVLPjKe3fcdMeRWIjH0z/mu6lIlOubMdACVsn6
bEcB92QpP2BINesL3bhAjc8BPk2e4wbW/ijzo5xa4eGLCx8RLGXpam9ODJsjfEpe3/hvEZKUQoEe
9xdXlvI9EHtjYUviv0WIUQoFYjhH+4GInaKLAqhhv6CaQC/orxAkWKaIxxNndYyIhAKUsmA1rptt
LOCdJtG6ckNi/vNmAyBVevMRUDzKkjSWoLqj2+vQMoIYsdyIiKc7wJAbidk4q+XM3FqcP6twRqp4
qIdBDbdLqfSIwoQ1Ap5sdy/KRU67YsErmQ/lmiKbLTpoFLLAJF3MI/i2HaBnsv6CSImtHJQ0Jg5J
WCwqu+xRVGJ+uhqnUkRFJM4QhTg1xoFPwfKlWss5tzGggs1Pc2nMrdnTqR6wfrf6oWm/fv/SXuz/
df/F/tE+D8eNRmP9S+/F/iG9vuGxL/bp/ejFh/39AU70w+IBOhUlVZzWnYMaHp4b0qKPCmU5vcU0
w/INbMlcQgypxAC05jpJDgUyUcjlC6/c++WDLhqVqM1WMNNEyxTNEu8GaIYq4X0wmFmX34yAxfVD
hg4QpfTGoGjQ0A1vdcCQs1GP0ZChK3vNEiSjsDv1gdOOqbDChLfASXf5++I8R1vfhkjLmd2Qie1c
K1zSKM1822cJLm7cum9VfGZ+U0Wo7Fuyl3wu+KSIVkXlYqJYlajEfySfExpNcB9vkQjCkKNXZa+m
QCQKhBB6WxZRpCi6AOWpjhroD8f4BRRD1mJthfa/guSKaFVULiaKVVUQFxhQ3vwLKnkgiipIVxRd
gPhMv/xXIP1Kv3R9K8RVGmVCRbQqSipZrXVkX6NaJ7pg5LScmKOjiBwsqbQkP432iapbiB/zfdfH
0UuT7C+31ClbSui69u2KItefQP+H9PO5zbgDiiITBjF34NMFXzctN7ZPmDDLEKYrelzAGI4RNKlA
x3bxEE0usNxTgFNUkjSnkeHtvcSXcotEMZLGarmy9RC0BGHBlt7vjJaUee8gfi5ETdF6FC21Benp
RP/YIQX9PC8kJogMVkmB6MKJdjUa+MiBM9D0kKKYY6KbBtONhWbq66p2tbCgAKJnlE1fRpuweFZy
MsFNIz4zGIA34+z1yt6zDSqRZUO+3ia8EYrImu9pPPPvrrskHyeZQjER8KuGn9VQMciYLS1nFYgB
u0zmYJDzzfFH2f3kIOdYwFBym+5a8qmIGjBvYZ4FvUjVqaMg44EZar7uzDlXVfaudCu0nDk5dYjF
f5gLleCCIcWivgXgBJfep4JF0SUchIMO+nioSCK+5QFieAPTU6wtcXa9nndjViCjiirBTdrmmxgK
xHEpNMiMtKkbLp4qMCxsZtXW4VTGdFT2NQ/Wp5OnmZ/YMooreKeRp6gYx3VgYMxbTLLl3g3srfBu
A1ALdX4hiQCj48JqDgIGmSJiNliQEsMuwc2zSLZgoCM9qCEpqFEWLWFILgo19IWqzXwLpX4ZhTCk
cpO7iEiYMjJLZxWkBe8JFoguia+E20W2YWX/8sg1YyLM7sJZJlhAXP7rXtYHPW1ovB2lU+67UoaR
cG9RI5T5+M34HJPLJnpflWDkRYnUOOU+fzNWr+nk9CdlOEWHq68CNVL579+M1elqObXLm45SqBFK
f/tmZD4w07RKW82nFGpk0t/uiAyMIFgXBVIn/MNHL7cBLIWZyL+CZGr0FAnuiGOM21vXjy/W0tRH
S6Vw002YY1vBhCuGUNUJyS+KU+IaZYjxFLZl+UeBNF+LEOIzhU4Udr0Ip+iI2Ncs5AvSXDlCh3pQ
iDnpZffrR5su3GUH5+RVPEoxme2V8UgmBm30X5kEFzin4kgXCXmxgJLJVjNsF531It7lJ3MrVlX2
KKF2XeSJmGDJnxQua6oPd9nCvj1bOeSbt/1YsXUdN7ej45021t7q4aIOCq7pLrcfP82ltGbblHBX
a9abPRUsDMrIBIVJlVV9vxo8VufGMIFhPhj/Fnx+irgZ40nddUiReho/jZMqfcVE+lj35+RmHTyl
7HVvFSy2dUUtomD8/LNRx5O319vYH6r64xsqb5qFbYxZnZ9/dcg3gG/7/uPqFGLnLBRRwYv1qT5/
B5Ne/Phb83NJuXXhmTmFqaDr1Kk+BqOeTg6alSfBk0r9PKg8BfyCtWOMHzWfTut8E9M712R1ywmY
H75gMO9g20Z1CpgXFjepQ4c/RBfaN3h0EJDvWT5qu0LHiFWn1UetxyP4HoYwTaUk23gdEP/4WFnI
zePtKwtY5qoaebpXxfbySrXSa5kt1uk3p82eYUxbzU6rCcFsVwoaJheZPhYAypIy7ua3mf+UP5L7
p/KuEvnJ6uf6dfYWSowDATgNGud0lHijVW/B/+KtDjNgbCYUmgKP8qL2foIehJ/cmXb+VwShjcdj
bQs3q80sh5lbUZ+KKFm/QkPd9lY5XNU+/ehWq/OgQan5UeiAfLPeThDf0p5oWxH2W0Dam5+kysin
jseEnYc/gK7t+v9mstYJ/W+i6qOjw897/KisjXB6oGqWqrVamrIFOsqdccMDL+h601Zvg2MuEtl0
r4LEpYa3l1MOPj3801aj+qFzafmuQ3tkVSfYCO+GE4IQ8NNaovBb5axxJiN7htiexZcjpvH9nD8E
RiQ8ODlJA8ZwG/DMzYsK6Fbwkl3mAVMoOL3GCk5WU35bVi4jp9fH0LLrUrLtrRjBrcd5gHgtbg6S
CFLOfEbDdMQZPfmQPrVHkRntdZahzr+VYi9VZmsO3ViRUyskG56soc6gaV9v8ul9ZuheaCz0yQVb
ZzJu9d+Y/wgW3VPyAXntvPzz9fAvf7v++OHTev5lh13vv7ROfnlzXHurQH0ZWAWIA9zbdk4o4E1Z
qBfBy3cVDMxB05NZduqR5bzgYImYuSQKcuUijFA/wN0AeKhUNnyt4O2S66W7Ciqj3+DNoMUTfpdC
tYKSoTKqiM1G8O7rxgXMKVFHhfgkbxV0P0O3T0LXh0ma+IyHL8pJ6J4Y6KjwyCGeMryBBlcw/VDj
xyXgZRVVMZsdVT4x28BNt6HLO9Qj+CbfpjOq/B1nhg67gr4D6eKNkWJOuBVAqwShfFG0xYI6IhC7
StKJvQDp68np/unRwc0ZP5ooOGssAG1Wg1qcNbAqSRY6QKiK7nCn+lQQ6+ZzlQ6QvQcZDX3pUfWK
yYhJWt3WzqDfbHU7kI5PvA9w4ghJVjXPHvYkEmMlYkmImbM0x1k1WQle8h0LEtEPIHl0r/T+ag4F
4VXTCD1N+k90JUiv+SctQP8+mJg/0j4w3IDM+IQ98HSDaY57dU+CI941Xo80wam7iPrwC4hHlT8a
s16z06lgO8z1JfKR3BI8yy0NIfIVtkL8XUnmEIcbTACPrnfWuGLsIkv2A1xx1ayQqCXRHNhYQ3MS
EEub+u5VgIYbmZO5gdCENoOpW64p9sXm9JWDa/3MfCS2Bi/0QOM4RWcrabizjh+l6a5s09kK0fHf
YpcE9R6NhLZZgx+plekRuRbqmp02DL7YQpGJubiNhGWpWjkPPqDmRKwu2iI2cedaMIZb2IazKfSj
ZnNHIv4bJMmKrtyKIOcFDef2KQooWq2jS+CjvRJQ7wBvNq9rBIsTPwPujrSNalLDZV6kLpJNGMLv
R7XICh+xL99zikws7ToVHJ2nbFR0IWHDq5iw6X4akfkV7VwVhBbI5OlM9YyN6/eraWLfz1UjgVxY
kThJDcUeHkPW6krckqpGXNI9Wjguh+41z4r0BFEiiO8uXTyF8H70iHLnyZF8yVMj1YuFAHvDdN+h
kUMiyaeFHmr70D2igjQ8xTV4lmvcfcfUrhZrTV9qR1rAmJByS0x5R+pRS4nihPRRSmU8T+Os8cwb
91vNVo43MzT30LENNyHRKMJPcy6keNEoMmXu1cqeKUlKDJYkSDoi9UPEVZ7CANbDfhd71O1tgBRH
crribnOtBtIHj/muagcwv3fwAVRJ3oXTrXJsAzPreOI3OieYeNDlfSRWqY6ElLup5jWjjCYEIIIV
KBBr7Hv8CEBstAJCqpJmGyevKLX7TdSJa7jVibue5CUibnjn0JeuA9MO3h6+4CxD92WF6Re2LlJP
f9FBGSLHfRgqQn4PxJeVBToAh46Mr9JqREv//fAkbvx4GEXjZ7N5v/bhpYq2SfLHsGczNjRBidqs
pfC4n1qg20w0AQjLstYqSJ5tsaQbUw7phAdJ4vDr6PRLZHldQ6B54uPeS9xvom1dAe7rLWyDOahS
rd6f8BQkmFtJp6ggvqQDlTTHKRrwYcQH0UXdN9MwZrPZ3hkoGwZv4oEPC+a7tzUTVZqohKnF2KBo
qn67rXfZBk1lOQd6wEDDfGsFATOPSoY+y1gvrcywl2mdYDUVUbpHs4pcXUtrF5ciKqaU2aA+W8ZZ
4xfdMSfH6Fxl5ieNp9iZsF/hWqDEGQc6qKMgyqLlwejcJtyeHZDiu6TjCRfYM5dEkTznvIMBfgZt
vcIzGAWbIDYax0YjBGVmEZRXCxuqb55d2j38D+JnPujmx0Cq94693lRUQsmv8XjYwrbEw2NFR0Pp
qGjKOzYdAMRTFenc4VzrqYauTr+fbbVDZ44lJc11Av1R1w5AiQ749P310augqr3Vr63laql90n1k
spwqjj6WeIIqOkPKtwHx04emNM7hlOeS+YGFK9oeaOdV3FOwMiDLzLdocQ7blm+Zoh3RMcgg1KeW
bYVrZI20QLilsTbWyd7BlMGOxuPbeuwr3P6tbGk8gqrV7TZ7nWaLLAB3bNSk1+MWc2zWZylwZeoS
8nerxyd04vKZ6K4NldaUruPSBQqzfTyLq6xiIFA7rR7y0QY0fc1wu/P+xnKJDBS1JTNr07moeqfT
GvSHg2Gm6lhFPNcnPrBSYeVRVIKGPcx2BRqyFURTKzzKHL625x9f6/BqBRN+LYig1KXuW/zI1FEF
M9asgJtBIC2MQKabcN5nhQGQXXvMt2hBOWfx+1qBr9i8lVEOf4fj7DNyk0NXmRqQAPHDlRXsRqDe
T0XHqIya9XYVoVk+4foSLz8Y4cnNvVqrXes0T5vNEf1fB23lHxWpWtzUKBcH5dTQeIa8UFRaDyuL
BXaaIDUl9DnRBLhLPNkUctRmNu5QKAPYKkG/NdgQfTT6FpeBkkEknOvL5WYpTWaXwhRk6PaGxWQA
SWZbBp5Qj1MkOpEVJb4aJBCCTLCcBrhM0mvt7LTaUHEU3zDiha8ATPS512/1ezukhEafPzqhZSff
e8NB9D1LYfi8M+jFn9VERVuV79rFCEvUQrMx1rQwsUzaiCwliT/fKLqUFbzm/X7fXGYXewpWOTzv
BQyJuQ5IAZdkoqGSRs5ofqpYVbAnupnvxzzk1wRunqaXLNH1hIrSxmJJChTgIwea2zFY1j2H42Q5
Vrg9qS+ZP2fbPI+07FdVLGXcHsGXRkD48AWPiFn54kYsgPCsryMoCvkkUqhCPYr5Sn3NEm958Zub
hcdZlaIZv0cz8ZqAutmoUa4SKU2gkfXXxX0dS/RZqyQqUGT75UjQmd8hLlrotK4h1NC8jvqJ8Vyk
s+A0iA4gIh8jSED6DJl4YTofGeTxDFRNp7k+0317Te1ChTEftdmsgsjVO8kTTcFCdXHsDrYkMFSF
xBMQRyzdQEtlx6ibx48zXMoZb45ORLp9hPyXTZHn6smVZQIir3Q8JG0N7I3E4viJuPonSpHn4Szw
NCjO+XzFbHuL6zVb1fwCeBZK/PKNK+1UuYkTeOgw2FpeLtduJd1P4xShPg/Gv33OIPIjPPVaKlAY
lJEYGg3tdGEF0Z6mdyfv3x1qxys7gPIB78J8CfZXVc2EYgnja0C3tFpRwMTnYzPjaxf5kj1+el5X
twF84G5yKJnytIjCOfe7Q0NR5cm2MBhVxmMoEWUMogcdwg1dw7WfVfBERwM1ZfytPH5SqTtBy6s7
LGx46O+gjytPRFMXF2nWp665RidDBrP/hWWb2+ePn2Z82uQA7XaVc85DQuZjI5c97brKB5NSwAwS
COiJa58ArPL1065vB4eZeXpo4euy5DeRh2DsK5STI1HIRd7BAVANEUN5Jy7yxokOrwUJXeKSI2Bz
SUC+qMvfHRj4gSjR0z//CcQSPpqYgjtuQVeCNMmznCqOrRtLJG/6HVKi6MCEogjeA451X19iwUkn
27LMrZG21WkOhpKGsgVjqo0D5isHvhb2zhCkc76/RkUGMGWjE2OAPjDLfX18uo9IbG9hgsB2wwC9
tOScN5pBJp5thqzX+G9r7rg++++G1MjSgLOFxld3FQKGbdahaEEdtZgk2lc1rH/skqb00I1FSspP
V5fzFcmgJDUJI8iS4yY5DbUKJEpLJh0lEWYF0VLXl/rveFuuGayhYy+J+XT9ut1AKqICSZ7Acd6g
0A8Yqy4Q5L0kz6MF/M9Zl3fRur9yTrDtUlw0oTMp56gH0jGsIPhmV7GixKNWzhJw8nEZIvMlWE35
pDRI55sInXISKZVcLZwQ76DWuMJbHCbiUF6MEDdw4UzLuqzNvRDoVkunQtUUqTKxnBANtkAvcZ8D
mgF+Zzhb+a0DE5h2r/m5Sk/9ZvMzGgmg2IkFyFfOGt0BzJT6zbPGL5OjCaS5htQVXMtjU0zxteKC
SL/GBxgykvpyMpHCi6e5oq2QJ6ziiSrQNynHnApxVjZOk/Ao5Mqo19nptDqtVguNI9DNJ4l5zr2u
mUIwkYOGOV0TTG6suzprtOqAJ+V6BoOgwLxGSk/lBqZElcC99J0EV2EPyqPK08moAkzCtdVrtftN
Dk2H2d3tsDBVqtK4C1O0XWfYGbT7aKGG4fTK9S8gbtjttOu4pIBtdERlDkSB/mqKhy9vUGiUUi44
vsISvu/02miOwrMR6X3YbA3aEAEdkEd0291BiyfhnNJqVlu9z4QGdA92fXhtLHAD8AbIpNPLKHGS
Roi8hEnxoD0YNm+omJkNmiEhbk7RIwR3hOGrkjv1qRdz5w06z9BMd4JHqkZdSrZOWc4kNoLxmCXN
UOK3pAMXFPj2pVxenD7dr1MvZMWQZQJt40QK8TJpt1L84unLOCHv162mFJGXMJn6xKImF5FGESbu
c0AbJnMinQcTxAnpPlEELvdEs1tmEJUKiHJy8obsekARF9c0EoShw04Qcqo1pBhs8ND1JmLpelQg
4aQksXjTZ2h/HlXENcuydBu0d6pDFG7DQRMfimTbG7owmA7anEDS62Fzsn/66l8h5rr/uWKuN/y+
Yg49/srEXLvd+/eIOZS/Ecu0q70fI+Ww2FjK9bs7w2GZlOOlfEcplon43yymIjw3kVP8S6EsEjEq
YRRtihwVSCL4NoE0CjGk3h8pSaVW/3ZNC9Jc98lB4juKIJRAWQHU/s8VQP3W9xVAfCmwWAC1et1/
jwBC56iIO4Y/RPqg7E3pWO0H6YMA7i59eDpJ+CSIfzfpgzxP80WaNcf0hkkx7h6B5iTehiblzQ9z
4/NVENLcntoS+0uzPpQYGd5Qpul+6OACJSQCMYY/MP8WvR69xWLjiriBi7bTnTUgAvv7WQMy4SQc
QMWyKAHwjWKjiqegQxUmd+LJm2jVPslW1lgi4vu1VYJSXP69eAy7PLZvUvn/3V3tRjJfTYhd0WSS
M2Oo27pgdvEwx7sXa5WLLxGh6BEFRPmGOV5VrUo9mK3K5CJQ7TupUw9mq++pUhWZrUBKpTkwI7bS
Vg61AOQ4fUcBl4n43yzBIjw3EWH8S6GYEjGbyqkH49O/Tlg9GJ++p6xSG5+gHMnK8SCJ7iqJeLof
OhUskEQPxqcfLoAejE/fTfqojU9QzgVbAz1MLFkWIUnflHvXZ3lGiXNlnFAi/Fa72e8Mdtr92Cd9
fX7cuuy+gOyCHJzL2zu9QXPQRbf4KJ7zXSsIX+hzz/LY+9kLX9cv3czOuKp2sFhNg0faS9orrV3p
TogujnhIHu5+CwzcxzhqdWkCQH7IljOfiGh0ERYe3ZVRZ2fYr/eG3Z0dTnbINWz32gNyJKY7FEad
YavTb8O7FUwWbiyQEg/7lLQw8NT8qHtJUegQGC6odDmWXzNL0UtryYS3JLmFkitoBd2dkW3r/EVA
abd2cOcXz9sZAq11BzJJIt12Xc/CAwv468oLsGlwesZv0EDydHHEwQtG8FOr10eZzmVuhTYm0ob0
M7oYGY1ZIJbCcDDYOWss2HpiYBNMTHcCLTDBFpiELpoW1rSvKrMBG71gxbUL8Nrfqfc62ilyBLrS
QAMtUUmP91w2tebOqN0edXA0BpJPcA8p3T+UStRqA9qjLnZl7hZTkA6AtYajDgpQkP8CJaobIkDD
7G8of6NdaLzt+BBWIbGLxzchLelNkGNCLAtN0RlmRkrayQWJX0VF4DvxPAjYJcihCXcd5+VQBCRJ
vU/dEHekiCiPhtoJuQRHDUovk8Bd+UYMisfhiT9RjAOMxn2MK+iSD80T0rWP4nOy80Ma8SdAkjhi
Ju5kMJPhlIydowfr0X+gxiMiFApPAVEerEdlE7IH69GD9ejBevQjJViE5yYijH8pFFMiZlM59WA9
+tcJqwfr0feUVQ/Wox8hiXi6B+vRZiLowXpUKoAerEe3W4/oXk3OSXyXTbTPGLfLHH84fHH0cnJ6
9Pbw/cdTbay1kk1K3vQcN4HQj7w1CSPqX1a49yV+FBuSNj1bumjDVbT3pGS3VWwMo4e7bdOU9ifK
u0blPURl20k5fbey26glo1ocN9K2FNa1zBZxvO5xksk90qjLp9IFrh/K7xigADKuZUAKu4schUkX
bphJyDf7yDGU0NTXmYRkVJEjNDWSIAbkVwqqdFbwixvSBu0kDqmVKRZNmvI7BkgmTJyZxHjj9McP
b+QoSGwyPEDq44ejA3fpQe91wu2tP7VfRc2YOcMZ+4z8jmGkNbPIH/uuHKFhIi7WM+2g+8aCn5oe
BUA/g7duXp5QQilOTbMPtNNXjlMXq88YmpUCKU51QjFQd98wWJBOpwBoOa85uaQ4ZUJsLyaD0/JV
SUNOswCFB9P2g2n7wbSNjPBg2n4wbf97pmMiQjEbKyDKg2m7zFr0YNp+MG0/mLZ/pASL8NxEhPEv
hWJKxGwqpx5M2/86YfVg2v6esurBtP0jJBFP92Da3kwE8enDg2m7QAA9mLbvZtpOm6PwHEhPjtDy
5ig5RBNy2dQ60rbwAqqMzS5KaZlJXN6yh7eeWa4THPIDjkQqIpjaHEYilpmnsu1upP2muOwOg8lm
+soOs6mJYyuJYQJEFppQprT3tdJudXFwiebylU6vN2gNKnjEKs9mrOgczXSuPs2T4kytneYOThzi
TLqPJ7JmSmoiWyV5uu1WpyflsRy0IOh0BGg2b6uJ7JHkbYJY2JHyOmhQy2bqIdPGeXbarZ2ulCW+
+ilTUn+A3SEpqtXb2WlL+dCMki+rQ7pDnKvdbbX6cq7AsJhD25SzxfVSVBkM2m2Z+g4fIbPZdlAq
JJmGOx05E5ns6AawTGGtFJI7w2ann2qAwLPEfW2Z2qVw3Ok1Wy08I1zNtFFwndMFe2WvTxc+CxZ4
oRiyY6vZ3ekN+pIJPd1FeZ8+RZtsFAcdiQT3SAOA2hIPRY3uAOA4RTcDxJfExSBv5AMFy1ZxUDIE
uUUcHj1SnHaXipDOqduwvEhrwRJphD9w3QsLpFZl/x8fTw4/ALlNK6ADWWEchDFBv4zJWBn1q3gi
4/qjY31ZsXf6ZRBrIxeWd4yg36EKFikarreyQal5ZflovxP6iBz5Mi6qmfkiFdpCm51lSzGAl2dd
M5sOiQxQPO8fnB69fzc5erv/+nDy8fjX96eHUJNU7Mv3n96l4w/ev317+O40FzH5cHj85u/56Czc
KD4P+dX+r+8/HFHMwZujg79g0uP9d/Apjjn95eNbNBeLHCe/7H+QANArgDk4fPH+/V+y8aefjk5P
qa1S0cdH7yD28ESqEf/w4fDly6Nc7OHb/aM3EHn09hgyneCHCCkp6vg9wZNi5Lr8enT4afLx3cvD
D0TkKAYXcCcoqJM3lBvJG3br5A2FQ/KGo0LyhopJ8oYSMnnDmyiSNxS6yRsuwkilZ5BJQ22nv9JN
GhJ2zcnxm48nFVBJZ5aNN2mZp2j/JsZ7G4nJN3g08ieQZHK6E26cD1DXwAPwIQMZ81FZ0PFsCUgS
6fEETr/CU/KneghSKMDLVDEzdkkcP0031JFwDAa5heeSdshWSC684c3TbbzLYOZShOuiQTIaeeMh
Z27NEB4/rMGdzZgT8FEPxJllhAamWeoXbOXpgBNfWcDp0KVFQw/0vsBYuK5tuEKjDBfujF1bNulV
SBPsxxdsSfnoUgSBaAC/IY0MX1YwMTINyzcoFy5mmezqEuUvaQnwfsVvRQpCn4FOyshwcQVVQvWU
V+TKRUJdhTMkt27+zlBvBf3Ss96Iw3Mr0m1Yv/sXydL+WQNSnTUukZSxInys+6gS4lng5una4/Pd
5mck/aU45htq4IBCBrOrcLFaTiuxwozK6rDX79DUysMjWik7TmGEbtsbDPutSLPFb612u93pQQqG
IkzENautVrXdrLbhX5hrN6u9VrUHD51qr1ttNVvw14a/Dvx1q91mE/5a8NeGvw78YVwP/vrwN4C/
HfgbVrsAttuCeADcbcNvp/mZbhm5vZo00fu2WraGnVamuF61sEBoHA9UCOKdbykVaNv/jPdsxM1L
X77iQpcooKhYPrnNE4E3eZ4JYKKQHHKaPv1bxCvO8KboDRxEorCBo0gUSoZ+3dHtdQh9NKdtRMEK
xKUEfABXprHd+TGOvSO8esCTHGZsIIfu46JuogVFIaKRHEowjU9KV6MZV2Q/mk0CMh/3a/3+oDVs
7tTaagRykSUYcIeJQgyw0aDMGE9loi84D8t92QATnM6uDPZnfebmTq/PpyZngMPkohZlFlVQFnoQ
6i/IhryNs1NFs6UiVDxfcJR9FNCVi27befn+rTaOTvmNY/75z1RUvvwoZd1naEbbpvfM+ciktNc/
0m1Mai7GoG7aKHi+dakb61H+tP463jpeF7Ywnqq4EAz8LvY8HIwvz2mJuxEkXitNz5Z0FepIXIMw
qYuI8lwLnfy+WBAU9vwbxRXzikOtX6yPzO0tfg1WTWCMR6vL2eKXRDBSdCrRXaSjNdO2MZ07y97k
8Gg81rbQ2jqzHJjyqORl5sIGvLHknRtaM4srEcH21wpehkFWJvj96HEbDw7WMEC3mvhDv/SAAxsm
O+BWGTmd+PKrRWogZhBZRPbkNUp6uJyiPfi3VpzVpztEcUDFkYyXQIMaPsqo5fDiRXyO831gHvpI
EGwRBTrtIB3zFv5orORFhjAAL8gWj/PGU/HGvwm7XvQRSIjXo6M70m+d4bA6hL8W/tPBfwbRK3yA
obozaDc7/Z1edzDo7TQHCG4JvEgD8dfosbYM5rywmypalsQtPK9gKL5kvrZvu3Qros+8FVebK6Oa
fLfPO7YKkXzpJICOlAZ90Dy0wacTDVOJ8FbmbAqsi5Tk1F8FeTBYdSnRkena1u+5VEgbKdUH5rhX
Ti4VEU9K9hqvu3dXqDOnMM9T9+Zz9gz80v4oPZZ3ySIXWmFWKfGgvR32xt6y/wXFojVje8vHWwD0
+K6Vj6Fl163gA8W+tAJ06tp+rD3TWtpIa8oDJg5PQmhq0T1Hh/wd5dyR4r4j5TgazUJQEuiOuS1g
ZouKVRkaChVw9qMEEgQZgKBvJPEKoADalsmE76T4JldBgoSXO6TSKWFE2FSTGshVi1rdojx0DY8y
XfLUaKD/MTlmBZrD6K5MK9T0EIcZDb261nJavJIoBB0NHf80Wl3SQA9YCDdOR79MeGI7vuOjTmDU
lzVgQCoQ974SmBSRM/q+nR9PlgxmxCa37ac+5vQ+qIXwKSJnX1HrEC+6CV2osbGG+TEaGOXaYBCk
r0MNttA85x5hpYHXaT1Bm9SnoEClO0kW8Wq6XrnbT5IigNVBGbaZEcYFFNIPQ67O8rOi3XHwzmtG
gTUHofc4DT5dI6zkkWOsouvUFP3jJilGpllRdYh3snXK6Ae3aclQS2hYGJU0oPIS7wo1UJfQDk73
A0qk0pgx/h4CEcN/SdycRhzYC6ZqZJ0mVND1N5VAkoznMNN4484V7EzmCdA+EYRC91zSZWygZl6H
oI2LhhQNkhaX+CY+vIVMpo5XfBVNr7bQzRqviVH710dhy4ThGtO1Cr7H7uJb2cUuOWzBtBeVebx5
Rq39YtjCZfbyJMrIaJ/GoRMSHjgKNnGth9QydRbXKC8IMH4bOQyIquXS3TxORWW7Iv5uZJnQPbzn
kMayhm0+OQ9AsUjr318rzw3XQR6NTG6gBhgLttTrro+2uufCB/wFyl/DXy2neGkUfECuwkcxe6ig
+S1KjPFH8B1tMm5gCb2H50G98Dmt3fJbs+Q7EMk1uRE5KkdqIr3e3Hy+RdspoUNxH7z3/EMtW/gW
jOwtaop5ypFps7cuPLi+nLq8ivSUfsWwaV0xSIIDOzUdVnyKNyayFB4YJpO5jgLXwfWmLRzmUAzJ
1zApeDD5Jj49imWcWQ2qFowLdDVbUJ2dB2NTmn5KdyoFeJlS1Rs3/i/nyDpeTbidXK32+NkWftga
0U+w9dSabT+SYdFUFsp6/JUKSRsYQIk5B03XHFsmPqDu6z3ZAj6M3NHr4RUfcJAledPhLrKtp4By
4Y1JvEaPn97cxDoLUI0TAeknYNauzvEmq3TrEuFKn3Yf1WraCza3HHQaOMGdIxpQS6vVEgh3YQO6
OhAg0R4U0JSSZ+k6sCjEH+veKlhsf9WM1khDvy/NaMPDYDBoDnvNSs5QWKysYUAUUtdrFVyTVdUY
XlKoMFgU3cKliRvMcndlRYEaHb7GLZW/tE+D3h9d6wdTjVhWBdMKzDoiSTmtPNaeaJU6UccAjdZn
3PhIrDMFrRZApq7cigKzCzkpwBpnSFl0VV0c47jZKIoG0condxHCBah6z4zWuP2z0R6L1vzZuBy3
6/BzPm5VtIZUUiNfFHHnIWj1Od5MJ/nrSndCQw/Ce/Mu8e0XUmyQd+LHDNcWcx5NEYF9ihkv6rOZ
FsBM38A23FVPYh0GYkVwzxckCzmkUVvQa55rCIFivqbPSD6t9Bo5DNTzDKD2bb0qJkXqbjsMmL2Q
gRGVKiVJ6S0yB0cNJyRKCvYX3TDCUcWrzXZcf/X+y8tX9lt2VJEgqXpCEpXnT4o2rUstCNc28JfJ
jQgjx3XY08pe0ktgDMDlkWyLUGQjgxDtgNOmrg+63LgC4o/vfxtXoL9c4c4yetJtiIn5vtLY220A
Iht2p6S/hMr+BAoFm7ruhUaLOtoBtMS9ehWGZJyeVadVVr2sOtWwGjz+CmPrrD6bfnnss3DlO0+d
Mb2OpbmLU8crIN/S7PlZCqr8Ba8utdfbTlX358R0weORgzu+V2JgSeJvaESf1SdYLv8ZO2kOdCgP
xDp1NFgzc/yoCc+XzMfLvMdbILy2ngrweDduOJ5mOjp7/DQUl71C1hTwkLSCy6cBZFL2DEZdolgb
QMrFF5WmQMfKQawbbDUaMEw4MK2tz0SD0tWwzJl8PGnMpqKnkN6QVvyALNtbqE2igjHotjuDwc6w
3ev0ep1mVn5RWrobGxMfgzaH1uwcSNSH8+KNjxQg3EAlfuazeU4bxpAu4cBdejYL2QdQWgKIRThZ
nIq0XYqJ+wV1T2XnUnRnYcuM5C7eyBETFbty6D8Dza/VHPS7g24LZnOdZrPf/ZldjiOS/BwVDAMf
dteS/lnW/+6iomuGrQfBmLb3FNlivWs0wxoLwEoywP4A+O0s/LvUJMEofig0csCUOs9EODYtA7zL
dutMHD0wEQ/bk8dxFP6H/0/whd7iWJHmn/Df2dZE+x+IPDvTGtrkfyhOxE/+KaVL/tue0C//N0ox
oXf+d3Y2mVQ1/oPxEUapMJk04typ8E/AuHHmbKW7AQas8xOo9HjDcObgdfHovU52RwaqlxO4Nnuk
/cVxrzRyc8QjC6pnIIJwV/oz7ZPYlo6eGRpIKQ26iwbzXsyNzslnaO/xw5UXaLrvwsz3mSZ0lYRT
zt1pcOZkkSkKypoKREG4zLeh1lmRQJf6gnz9KrtHbif2EHrIMCj9k+ZSHO3x4nS82b42Y8ycglzK
DHm7etQxMJU2DZ1asIThip507j8FIy/eSjyuTKa27lwkWogIC5/NEnEDkjMQNwARuXA2GTTMRuvg
7f6fD9td/9x75Q/eOv3Z7167t+j+/ZfZouMs2vv6u7++mvztU+s4uGjgLBszPmNodar3+/1Wv9Ps
DsY/84hud9Dv9JqDZg4ZNMzVIKvBzNqGiH3odb8cGoez43NzaP+53Wv+Zd8z/n7y13/8Evzj+MOn
07+7U+vX/vrN4eHR5oiJd9xp1e93e/1mhvAYojZJt0hDl1WjWFMSFp7dBl6Bvge/i3Bp7/30/wBF
bIpnsRYBAA==
headers:
Accept-Ranges: [bytes]
Age: ['0']
Cache-Control: ['max-age=60, stale-while-revalidate=600, stale-if-error=86400,
public']
Connection: [keep-alive]
Content-Encoding: [gzip]
Content-Length: ['15910']
Content-Type: [text/html; charset=utf-8]
Date: ['Tue, 26 Jul 2016 07:58:04 GMT']
Fastly-Debug-Digest: [1f17717c273fe110239d9d8921b6fbac562454bbc2b5292ad246f8baa5694b1a]
Server: [cat factory 1.0]
Vary: [Accept-Encoding]
X-Cache: ['MISS, MISS']
X-Cache-Hits: ['0, 0']
X-Frame-Options: [DENY]
X-Served-By: ['cache-iad2140-IAD, cache-lax1426-LAX']
status: {code: 200, message: OK}
version: 1

View File

@@ -0,0 +1,280 @@
interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Connection: [keep-alive]
User-Agent: [python-requests/2.5.1 CPython/3.4.0 Linux/3.13.0-92-generic]
method: GET
uri: http://imgur.com/a/qx9t5
response:
body:
string: !!binary |
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
aRRbSmKHZ7+JX+84gN9FtHQPvvt/W3Q7t6z0AAA=
headers:
Accept-Ranges: [bytes]
Age: ['0']
Cache-Control: ['max-age=60, stale-while-revalidate=600, stale-if-error=86400,
public']
Connection: [keep-alive]
Content-Encoding: [gzip]
Content-Length: ['14108']
Content-Type: [text/html; charset=utf-8]
Date: ['Tue, 26 Jul 2016 07:58:13 GMT']
Fastly-Debug-Digest: [87d2462f5a6289536c8556424f45dffeb48cbf4dfe05b56d2928dea4676bd32a]
Server: [cat factory 1.0]
Vary: [Accept-Encoding]
X-Cache: ['MISS, MISS']
X-Cache-Hits: ['0, 0']
X-Frame-Options: [DENY]
X-Served-By: ['cache-iad2120-IAD, cache-sjc3129-SJC']
status: {code: 200, message: OK}
version: 1

View File

@@ -0,0 +1,34 @@
interactions:
- request:
body: null
headers:
Accept: ['*/*']
Accept-Encoding: ['gzip, deflate']
Connection: [keep-alive]
User-Agent: [python-requests/2.5.1 CPython/3.4.0 Linux/3.13.0-92-generic]
method: HEAD
uri: https://i.reddituploads.com/a065472e47a4405da159189ee48bff46?fit=max&h=1536&w=1536&s=5639918a0c696b9bb3ec694dc3cf59ac
response:
body: {string: ''}
headers:
Accept-Ranges: [bytes]
Age: ['487842']
CF-RAY: [2c864e7ed22a11b9-SJC]
Cache-Control: [max-age=300]
Connection: [keep-alive]
Content-Length: ['183244']
Content-Type: [image/jpeg]
Date: ['Tue, 26 Jul 2016 07:58:27 GMT']
Fastly-Debug-Digest: [0794b9fa1afd174c22abcd8455ed8cb859c900325e06b732b74cebd3063257a3]
Last-Modified: ['Wed, 20 Jul 2016 16:27:46 GMT']
Server: [cloudflare-nginx]
Set-Cookie: ['__cfduid=d34105474396dd4ddf576bcff4e4a81911469519907; expires=Wed,
26-Jul-17 07:58:27 GMT; path=/; domain=.reddituploads.com; HttpOnly']
X-Cache: ['HIT, HIT']
X-Cache-Hits: ['1, 1']
X-Content-Type-Options: [nosniff]
X-Imgix-Request-ID: [d0b67b89a628e062b1ed849e4e0f1d09b11fab9b]
X-Imgix-Wait: ['0']
X-Served-By: ['cache-lax1421-LAX, cache-sjc3128-SJC']
status: {code: 200, message: OK}
version: 1

View File

@@ -184,7 +184,7 @@ def reddit(vcr, request):
@pytest.fixture()
def terminal(stdscr, config):
term = Terminal(stdscr, ascii=config['ascii'])
term = Terminal(stdscr, config=config)
# Disable the python 3.4 addch patch so that the mock stdscr calls are
# always made the same way
term.addch = lambda window, *args: window.addch(*args)

View File

@@ -5,7 +5,7 @@ import os
import codecs
from tempfile import NamedTemporaryFile
from rtv.config import Config, copy_default_config, DEFAULT_CONFIG
from rtv.config import Config, copy_default_config, copy_default_mailcap
try:
from unittest import mock
@@ -20,6 +20,19 @@ def test_copy_default_config():
with mock.patch('rtv.config.six.moves.input', return_value='y'):
copy_default_config(fp.name)
assert fp.read()
# Check that the permissions were changed
permissions = os.stat(fp.name).st_mode & 0o777
assert permissions == 0o664
def test_copy_default_mailcap():
"Make sure the example mailcap file was included in the package"
with NamedTemporaryFile() as fp:
with mock.patch('rtv.config.six.moves.input', return_value='y'):
copy_default_mailcap(fp.name)
assert fp.read()
# Check that the permissions were changed
permissions = os.stat(fp.name).st_mode & 0o777
assert permissions == 0o664
@@ -56,7 +69,8 @@ def test_config_get_args():
'--monochrome',
'--non-persistent',
'--clear-auth',
'--copy-config',]
'--copy-config',
'--enable-media']
with mock.patch('sys.argv', ['rtv']):
config_dict = Config.get_args()
@@ -77,6 +91,7 @@ def test_config_get_args():
assert config['link'] == 'https://reddit.com/permalink •'
assert config['config'] == 'configfile.cfg'
assert config['copy_config'] is True
assert config['enable_media'] is True
def test_config_from_file():
@@ -89,7 +104,8 @@ def test_config_from_file():
'clear_auth': True,
'log': 'logfile.log',
'link': 'https://reddit.com/permalink •',
'subreddit': 'cfb'}
'subreddit': 'cfb',
'enable_media': True}
bindings = {
'REFRESH': 'r, <KEY_F5>',

View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import pytest
from rtv.mime_parsers import parsers
ARGS = 'url,modified_url,mime_type'
URLS = {
'simple_png': (
'http://www.example.com/i/image.png',
'http://www.example.com/i/image.png',
'image/png'),
'simple_mpeg': (
'http://www.example.com/v/video.mpeg',
'http://www.example.com/v/video.mpeg',
'video/mpeg'),
'simple_unknown': (
'http://www.example.com/i/image',
'http://www.example.com/i/image',
None),
'gfycat': (
'https://gfycat.com/DeliciousUnfortunateAdouri',
'https://giant.gfycat.com/DeliciousUnfortunateAdouri.webm',
'video/webm'),
'youtube': (
'https://www.youtube.com/watch?v=FjNdYp2gXRY',
'https://www.youtube.com/watch?v=FjNdYp2gXRY',
'video/x-youtube'),
'gifv': (
'http://i.imgur.com/i/image.gifv',
'http://i.imgur.com/i/image.mp4',
'video/mp4'),
'reddit_uploads': (
'https://i.reddituploads.com/a065472e47a4405da159189ee48bff46?fit=max'
'&h=1536&w=1536&s=5639918a0c696b9bb3ec694dc3cf59ac',
'https://i.reddituploads.com/a065472e47a4405da159189ee48bff46?fit=max'
'&h=1536&w=1536&s=5639918a0c696b9bb3ec694dc3cf59ac',
'image/jpeg'),
'imgur_1': (
'http://imgur.com/yW0kbMi',
'https://i.imgur.com/yW0kbMi.jpg',
'image/jpeg'),
'imgur_2': (
'http://imgur.com/yjP1v4B',
'https://i.imgur.com/yjP1v4Bh.jpg',
'image/jpeg'),
'imgur_album': (
'http://imgur.com/a/qx9t5',
'http://i.imgur.com/uEt0YLI.jpg',
'image/x-imgur-album'),
}
@pytest.mark.parametrize(ARGS, URLS.values(), ids=list(URLS))
def test_parser(url, modified_url, mime_type, reddit):
# Include the reddit fixture so the cassettes get generated
for parser in parsers:
if parser.pattern.match(url):
assert parser.get_mimetype(url) == (modified_url, mime_type)
break
else:
# The base parser should catch all urls before this point
assert False

View File

@@ -18,9 +18,9 @@ except ImportError:
import mock
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen(terminal, stdscr, ascii):
terminal.ascii = ascii
@pytest.mark.parametrize('use_ascii', [True, False])
def test_objects_load_screen(terminal, stdscr, use_ascii):
terminal.config['ascii'] = use_ascii
# Ensure the thread is properly started/stopped
with terminal.loader(delay=0, message=u'Hello', trail=u'...'):
@@ -32,9 +32,9 @@ def test_objects_load_screen(terminal, stdscr, ascii):
assert stdscr.subwin.nlines == 3
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_exception_unhandled(terminal, stdscr, ascii):
terminal.ascii = ascii
@pytest.mark.parametrize('use_ascii', [True, False])
def test_objects_load_screen_exception_unhandled(terminal, stdscr, use_ascii):
terminal.config['ascii'] = use_ascii
# Raising an exception should clean up the loader properly
with pytest.raises(Exception):
@@ -45,9 +45,9 @@ def test_objects_load_screen_exception_unhandled(terminal, stdscr, ascii):
assert not terminal.loader._animator.is_alive()
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_exception_handled(terminal, stdscr, ascii):
terminal.ascii = ascii
@pytest.mark.parametrize('use_ascii', [True, False])
def test_objects_load_screen_exception_handled(terminal, stdscr, use_ascii):
terminal.config['ascii'] = use_ascii
# Raising a handled exception should get stored on the loaders
with terminal.loader(delay=0):
@@ -56,13 +56,13 @@ def test_objects_load_screen_exception_handled(terminal, stdscr, ascii):
assert not terminal.loader._is_running
assert not terminal.loader._animator.is_alive()
assert isinstance(terminal.loader.exception, requests.ConnectionError)
error_message = 'ConnectionError'.encode('ascii' if ascii else 'utf-8')
error_message = 'ConnectionError'.encode('ascii' if use_ascii else 'utf-8')
stdscr.subwin.addstr.assert_called_with(1, 1, error_message)
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_exception_not_caught(terminal, stdscr, ascii):
terminal.ascii = ascii
@pytest.mark.parametrize('use_ascii', [True, False])
def test_objects_load_screen_exception_not_caught(terminal, stdscr, use_ascii):
terminal.config['ascii'] = use_ascii
with pytest.raises(KeyboardInterrupt):
with terminal.loader(delay=0, catch_exception=False):
@@ -73,9 +73,9 @@ def test_objects_load_screen_exception_not_caught(terminal, stdscr, ascii):
assert terminal.loader.exception is None
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_keyboard_interrupt(terminal, stdscr, ascii):
terminal.ascii = ascii
@pytest.mark.parametrize('use_ascii', [True, False])
def test_objects_load_screen_keyboard_interrupt(terminal, stdscr, use_ascii):
terminal.config['ascii'] = use_ascii
# Raising a KeyboardInterrupt should be also be stored
with terminal.loader(delay=0):
@@ -86,9 +86,9 @@ def test_objects_load_screen_keyboard_interrupt(terminal, stdscr, ascii):
assert isinstance(terminal.loader.exception, KeyboardInterrupt)
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_escape(terminal, stdscr, ascii):
terminal.ascii = ascii
@pytest.mark.parametrize('use_ascii', [True, False])
def test_objects_load_screen_escape(terminal, stdscr, use_ascii):
terminal.config['ascii'] = use_ascii
stdscr.getch.return_value = terminal.ESCAPE
@@ -109,9 +109,9 @@ def test_objects_load_screen_escape(terminal, stdscr, ascii):
assert kill.called
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_initial_delay(terminal, stdscr, ascii):
terminal.ascii = ascii
@pytest.mark.parametrize('use_ascii', [True, False])
def test_objects_load_screen_initial_delay(terminal, stdscr, use_ascii):
terminal.config['ascii'] = use_ascii
# If we don't reach the initial delay nothing should be drawn
with terminal.loader(delay=0.1):
@@ -119,9 +119,9 @@ def test_objects_load_screen_initial_delay(terminal, stdscr, ascii):
assert not stdscr.subwin.addstr.called
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_nested(terminal, ascii):
terminal.ascii = ascii
@pytest.mark.parametrize('use_ascii', [True, False])
def test_objects_load_screen_nested(terminal, use_ascii):
terminal.config['ascii'] = use_ascii
with terminal.loader(message='Outer'):
with terminal.loader(message='Inner'):
@@ -134,9 +134,9 @@ def test_objects_load_screen_nested(terminal, ascii):
assert not terminal.loader._animator.is_alive()
@pytest.mark.parametrize('ascii', [True, False])
def test_objects_load_screen_nested_complex(terminal, stdscr, ascii):
terminal.ascii = ascii
@pytest.mark.parametrize('use_ascii', [True, False])
def test_objects_load_screen_nested_complex(terminal, stdscr, use_ascii):
terminal.config['ascii'] = use_ascii
with terminal.loader(message='Outer') as outer_loader:
assert outer_loader.depth == 1
@@ -155,7 +155,7 @@ def test_objects_load_screen_nested_complex(terminal, stdscr, ascii):
assert terminal.loader.depth == 0
assert not terminal.loader._is_running
assert not terminal.loader._animator.is_alive()
error_message = 'ConnectionError'.encode('ascii' if ascii else 'utf-8')
error_message = 'ConnectionError'.encode('ascii' if use_ascii else 'utf-8')
stdscr.subwin.addstr.assert_called_once_with(1, 1, error_message)

View File

@@ -329,20 +329,19 @@ def test_submission_urlview(submission_page, terminal, refresh_token):
submission_page.config.refresh_token = refresh_token
submission_page.oauth.authorize()
# Positive Case
# Submission case
data = submission_page.content.get(submission_page.nav.absolute_index)
data['body'] = 'test comment body'
with mock.patch.object(terminal, 'open_urlview') as open_urlview, \
mock.patch('subprocess.Popen'):
data['body'] = 'test comment body'
with mock.patch.object(terminal, 'open_urlview') as open_urlview:
submission_page.controller.trigger('b')
open_urlview.assert_called_with('test comment body')
open_urlview.assert_called_with('test comment body')
# Negative Case
# Subreddit case
data = submission_page.content.get(submission_page.nav.absolute_index)
data['text'] = ''
data['body'] = ''
data['url_full'] = 'http://test.url.com'
data['url_full'] = 'http://test.url.com'
with mock.patch.object(terminal, 'open_urlview') as open_urlview, \
mock.patch('subprocess.Popen'):
submission_page.controller.trigger('b')
open_urlview.assert_called_with('http://test.url.com')
open_urlview.assert_called_with('http://test.url.com')

View File

@@ -101,11 +101,11 @@ def test_subreddit_open(subreddit_page, terminal, config):
config.history.add.assert_called_with(data['url_full'])
# Open the selected link externally
with mock.patch.object(terminal, 'open_browser'), \
with mock.patch.object(terminal, 'open_link'), \
mock.patch.object(config.history, 'add'):
data['url_type'] = 'external'
subreddit_page.controller.trigger('o')
assert terminal.open_browser.called
assert terminal.open_link.called
config.history.add.assert_called_with(data['url_full'])
# Open the selected link within rtv

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import re
import os
import curses
import codecs
@@ -10,7 +11,7 @@ import pytest
from rtv.docs import HELP, COMMENT_EDIT_FILE
from rtv.objects import Color
from rtv.exceptions import TemporaryFileError
from rtv.exceptions import TemporaryFileError, MailcapEntryNotFound
try:
from unittest import mock
@@ -51,7 +52,7 @@ def test_terminal_properties(terminal, config):
assert terminal.get_arrow(None) is not None
assert terminal.get_arrow(True) is not None
assert terminal.get_arrow(False) is not None
assert terminal.ascii == config['ascii']
assert terminal.config == config
assert terminal.loader is not None
assert terminal.MIN_HEIGHT is not None
@@ -93,7 +94,7 @@ def test_terminal_functions(terminal):
def test_terminal_clean_ascii(terminal):
terminal.ascii = True
terminal.config['ascii'] = True
# unicode returns ascii
text = terminal.clean('hello ❤')
@@ -113,7 +114,7 @@ def test_terminal_clean_ascii(terminal):
def test_terminal_clean_unicode(terminal):
terminal.ascii = False
terminal.config['ascii'] = False
# unicode returns utf-8
text = terminal.clean('hello ❤')
@@ -146,20 +147,20 @@ def test_terminal_clean_ncols(terminal):
assert text.decode('utf-8') == ''
@pytest.mark.parametrize('ascii', [True, False])
def test_terminal_clean_unescape_html(terminal, ascii):
@pytest.mark.parametrize('use_ascii', [True, False])
def test_terminal_clean_unescape_html(terminal, use_ascii):
# HTML characters get decoded
terminal.ascii = ascii
terminal.config['ascii'] = use_ascii
text = terminal.clean('&lt;')
assert isinstance(text, six.binary_type)
assert text.decode('ascii' if ascii else 'utf-8') == '<'
assert text.decode('ascii' if use_ascii else 'utf-8') == '<'
@pytest.mark.parametrize('ascii', [True, False])
def test_terminal_add_line(terminal, stdscr, ascii):
@pytest.mark.parametrize('use_ascii', [True, False])
def test_terminal_add_line(terminal, stdscr, use_ascii):
terminal.ascii = ascii
terminal.config['ascii'] = use_ascii
terminal.add_line(stdscr, 'hello')
assert stdscr.addstr.called_with(0, 0, 'hello'.encode('ascii'))
@@ -176,10 +177,17 @@ def test_terminal_add_line(terminal, stdscr, ascii):
stdscr.reset_mock()
@pytest.mark.parametrize('ascii', [True, False])
def test_show_notification(terminal, stdscr, ascii):
@pytest.mark.parametrize('use_ascii', [True, False])
def test_show_notification(terminal, stdscr, use_ascii):
terminal.ascii = ascii
terminal.config['ascii'] = use_ascii
# Multi-line messages should be automatically split
text = 'line 1\nline 2\nline3'
terminal.show_notification(text)
assert stdscr.subwin.nlines == 5
assert stdscr.subwin.addstr.call_count == 3
stdscr.reset_mock()
# The whole message should fit in 40x80
text = HELP.strip().splitlines()
@@ -198,10 +206,10 @@ def test_show_notification(terminal, stdscr, ascii):
assert stdscr.subwin.addstr.call_count == 13
@pytest.mark.parametrize('ascii', [True, False])
def test_text_input(terminal, stdscr, ascii):
@pytest.mark.parametrize('use_ascii', [True, False])
def test_text_input(terminal, stdscr, use_ascii):
terminal.ascii = ascii
terminal.config['ascii'] = use_ascii
stdscr.nlines = 1
# Text will be wrong because stdscr.inch() is not implemented
@@ -219,10 +227,10 @@ def test_text_input(terminal, stdscr, ascii):
assert terminal.text_input(stdscr, allow_resize=False) is None
@pytest.mark.parametrize('ascii', [True, False])
def test_prompt_input(terminal, stdscr, ascii):
@pytest.mark.parametrize('use_ascii', [True, False])
def test_prompt_input(terminal, stdscr, use_ascii):
terminal.ascii = ascii
terminal.config['ascii'] = use_ascii
window = stdscr.derwin()
window.getch.side_effect = [ord('h'), ord('i'), terminal.RETURN]
@@ -270,10 +278,10 @@ def test_prompt_y_or_n(terminal, stdscr):
assert curses.flash.called
@pytest.mark.parametrize('ascii', [True, False])
def test_open_editor(terminal, ascii):
@pytest.mark.parametrize('use_ascii', [True, False])
def test_open_editor(terminal, use_ascii):
terminal.ascii = ascii
terminal.config['ascii'] = use_ascii
comment = COMMENT_EDIT_FILE.format(content='#| This is a comment! ❤')
data = {'filename': None}
@@ -341,6 +349,110 @@ def test_open_editor_error(terminal):
os.remove(data['filename'])
def test_open_link_mailcap(terminal):
url = 'http://www.test.com'
class MockMimeParser(object):
pattern = re.compile('')
mock_mime_parser = MockMimeParser()
with mock.patch.object(terminal, 'open_browser'), \
mock.patch('rtv.terminal.mime_parsers') as mime_parsers:
mime_parsers.parsers = [mock_mime_parser]
# Pass through to open_browser if media is disabled
terminal.config['enable_media'] = False
terminal.open_link(url)
assert terminal.open_browser.called
terminal.open_browser.reset_mock()
# Invalid content type
terminal.config['enable_media'] = True
mock_mime_parser.get_mimetype = lambda url: (url, None)
terminal.open_link(url)
assert terminal.open_browser.called
terminal.open_browser.reset_mock()
# Text/html defers to open_browser
mock_mime_parser.get_mimetype = lambda url: (url, 'text/html')
terminal.open_link(url)
assert terminal.open_browser.called
terminal.open_browser.reset_mock()
def test_open_link_subprocess(terminal):
url = 'http://www.test.com'
with mock.patch('time.sleep'), \
mock.patch('os.system'), \
mock.patch('six.moves.input') as six_input, \
mock.patch.object(terminal, 'get_mailcap_entry'):
six_input.return_values = 'y'
def reset_mock():
six_input.reset_mock()
os.system.reset_mock()
terminal.stdscr.subwin.addstr.reset_mock()
def get_error():
# Check if an error message was printed to the terminal
status = 'Program exited with status'.encode('utf-8')
return any(status in args[0][2] for args in
terminal.stdscr.subwin.addstr.call_args_list)
# Non-blocking success
reset_mock()
entry = ('echo ""', 'echo %s')
terminal.get_mailcap_entry.return_value = entry
terminal.open_link(url)
assert not six_input.called
assert not get_error()
# Non-blocking failure
reset_mock()
entry = ('fake .', 'fake %s')
terminal.get_mailcap_entry.return_value = entry
terminal.open_link(url)
assert not six_input.called
assert get_error()
# needsterminal success
reset_mock()
entry = ('echo ""', 'echo %s; needsterminal')
terminal.get_mailcap_entry.return_value = entry
terminal.open_link(url)
assert not six_input.called
assert not get_error()
# needsterminal failure
reset_mock()
entry = ('fake .', 'fake %s; needsterminal')
terminal.get_mailcap_entry.return_value = entry
terminal.open_link(url)
assert not six_input.called
assert get_error()
# copiousoutput success
reset_mock()
entry = ('echo ""', 'echo %s; needsterminal; copiousoutput')
terminal.get_mailcap_entry.return_value = entry
terminal.open_link(url)
assert six_input.called
assert not get_error()
# copiousoutput failure
reset_mock()
entry = ('fake .', 'fake %s; needsterminal; copiousoutput')
terminal.get_mailcap_entry.return_value = entry
terminal.open_link(url)
assert six_input.called
assert get_error()
def test_open_browser(terminal):
url = 'http://www.test.com'
@@ -363,7 +475,7 @@ def test_open_browser(terminal):
def test_open_pager(terminal, stdscr):
data = "Hello World!"
data = "Hello World!"
def side_effect(args, stdin=None):
assert stdin is not None