Squashed commit of the following:

Updated the supported python versions list.
    Fixed regression in displaying xposts. #173.
    Fixing a few style things.
    Added a more robust test for the tornado handler.
    Trying without pytest-cov
    Updated travis for coverage.
    Remove python 3.2 support because no unicode literals, following what praw supports.
    "Side effect is not iterable."
    Added requirements for travis.
    Renamed travis file correctly.
    Adding test configurations, got tox working.
    Adding vcr cassettes to the repo.
    Renamed requirements files.
    Split up tests and cleaned up test names.
    Tests done, still one failure.
    Treat cassettes as binary to prevent bad merging.
    Fixed a few broken tests.
    Added a timeout to notifications.
    Prepping subreddit page.
    Finished submission page tests.
    Working on submission tests.
    Fixed vcr matching on urls with params, started submission tests.
    Log cleanup.
    Still trying to fix a broken test.
    -Fixed a few pytest bugs and tweaked logging.
    Still working on subscription tests.
    Finished page tests, on to subscription page.
    Finished content tests and starting page tests.
    Added the test refresh-token file to gitignore.
    Moved functional test file out of the repository.
    Continuing work on subreddit content tests.
    Tests now match module names, cassettes are split into individual tests for faster loading.
    Linter fixes.
    Cleanup.
    Added support for nested loaders.
    Added pytest options, starting subreddit content tests.
    Back on track with loader, continuing content tests.
    Finishing submission content tests and discovered snag with loader exception handling.
    VCR up and running, continuing to implement content tests.
    Playing around with vcr.py
    Moved helper functions into terminal and new objects.py
    Fixed a few broken tests.
    Working on navigator tests.
    Reorganizing some things.
    Mocked webbrowser._tryorder for terminal test.
    Completed oauth tests.
    Progress on the oauth tests.
    Working on adding fake tornado request.
    Starting on OAuth tool tests.
    Finished curses helpers tests.
    Still working on curses helpers tests.
    Almost finished with tests on curses helpers.
    Adding tests and working on mocking stdscr.
    Starting to add tests for curses functions.
    Merge branch 'future_work' of https://github.com/michael-lazar/rtv into future_work
    Refactoring controller, still in progress.
    Renamed auth handler.
    Rename CursesHelper to CursesBase.
    Added temporary file with a possible template for func testing.
    Mixup between basename and dirname.
    Merge branch 'future_work' of https://github.com/michael-lazar/rtv into future_work
    py3 compatability for mock.
    Beginning to refactor the curses session.
    Started adding tests, improved unicode handling in the config.
    Cleanup, fixed a few typos.
    Major refactor, almost done!.
    Started a config class.
    Merge branch 'master' into future_work
    The editor now handles unicode characters in all situations.
    Fixed a few typos from previous commits.
    __main__.py formatting.
    Cleaned up history logic and moved to the config file.
This commit is contained in:
Michael Lazar
2015-12-02 22:37:50 -08:00
parent b91bb86e36
commit a7b789bfd9
70 changed files with 42141 additions and 1560 deletions

View File

@@ -1,38 +1,30 @@
"""
Global configuration settings
"""
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import codecs
import argparse
from six.moves import configparser
from . import docs, __version__
HOME = os.path.expanduser('~')
PACKAGE = os.path.dirname(__file__)
XDG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.join(HOME, '.config'))
CONFIG = os.path.join(XDG_HOME, 'rtv', 'rtv.cfg')
TOKEN = os.path.join(XDG_HOME, 'rtv', 'refresh-token')
unicode = True
persistent = True
# https://github.com/reddit/reddit/wiki/OAuth2
# Client ID is of type "installed app" and the secret should be left empty
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']
HISTORY = os.path.join(XDG_HOME, 'rtv', 'history.log')
TEMPLATE = os.path.join(PACKAGE, 'templates')
def build_parser():
parser = argparse.ArgumentParser(
prog='rtv', description=docs.SUMMARY, epilog=docs.CONTROLS+docs.HELP,
prog='rtv', description=docs.SUMMARY,
epilog=docs.CONTROLS+docs.HELP,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
'-V', '--version', action='version', version='rtv '+__version__,
)
'-V', '--version', action='version', version='rtv '+__version__)
parser.add_argument(
'-s', dest='subreddit',
help='name of the subreddit that will be opened on start')
@@ -40,57 +32,163 @@ def build_parser():
'-l', dest='link',
help='full URL of a submission that will be opened on start')
parser.add_argument(
'--ascii', action='store_true',
'--ascii', action='store_const', const=True,
help='enable ascii-only mode')
parser.add_argument(
'--log', metavar='FILE', action='store',
help='log HTTP requests to a file')
parser.add_argument(
'--non-persistent', dest='persistent', action='store_false',
'--non-persistent', dest='persistent', action='store_const',
const=False,
help='Forget all authenticated users when the program exits')
parser.add_argument(
'--clear-auth', dest='clear_auth', action='store_true',
'--clear-auth', dest='clear_auth', action='store_const', const=True,
help='Remove any saved OAuth tokens before starting')
return parser
def load_config():
class OrderedSet(object):
"""
Attempt to load settings from the local config file.
A simple implementation of an ordered set. A set is used to check
for membership, and a list is used to maintain ordering.
"""
config = configparser.ConfigParser()
if os.path.exists(CONFIG):
config.read(CONFIG)
def __init__(self, elements=None):
elements = elements or []
self._set = set(elements)
self._list = elements
config_dict = {}
if config.has_section('rtv'):
config_dict = dict(config.items('rtv'))
def __contains__(self, item):
return item in self._set
# Convert 'true'/'false' to boolean True/False
if 'ascii' in config_dict:
config_dict['ascii'] = config.getboolean('rtv', 'ascii')
if 'clear_auth' in config_dict:
config_dict['clear_auth'] = config.getboolean('rtv', 'clear_auth')
if 'persistent' in config_dict:
config_dict['persistent'] = config.getboolean('rtv', 'persistent')
def __len__(self):
return len(self._list)
return config_dict
def __getitem__(self, item):
return self._list[item]
def add(self, item):
self._set.add(item)
self._list.append(item)
def load_refresh_token(filename=TOKEN):
if os.path.exists(filename):
with open(filename) as fp:
return fp.read().strip()
else:
return None
class Config(object):
DEFAULT = {
'ascii': False,
'persistent': True,
'clear_auth': False,
'log': None,
'link': None,
'subreddit': 'front',
'history_size': 200,
# https://github.com/reddit/reddit/wiki/OAuth2
# Client ID is of type "installed app" and the secret should be empty
'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'],
'template_path': TEMPLATE,
}
def save_refresh_token(token, filename=TOKEN):
with open(filename, 'w+') as fp:
fp.write(token)
def __init__(self,
config_file=CONFIG,
history_file=HISTORY,
token_file=TOKEN,
**kwargs):
self.config_file = config_file
self.history_file = history_file
self.token_file = token_file
self.config = kwargs
def clear_refresh_token(filename=TOKEN):
if os.path.exists(filename):
os.remove(filename)
# `refresh_token` and `history` are saved/loaded at separate locations,
# so they are treated differently from the rest of the config options.
self.refresh_token = None
self.history = OrderedSet()
def __getitem__(self, item):
return self.config.get(item, self.DEFAULT.get(item))
def __setitem__(self, key, value):
self.config[key] = value
def __delitem__(self, key):
self.config.pop(key, None)
def update(self, **kwargs):
self.config.update(kwargs)
def from_args(self):
parser = build_parser()
args = vars(parser.parse_args())
# Filter out argument values that weren't supplied
args = {key: val for key, val in args.items() if val is not None}
self.update(**args)
def from_file(self):
config = configparser.ConfigParser()
if os.path.exists(self.config_file):
with codecs.open(self.config_file, encoding='utf-8') as fp:
config.readfp(fp)
config_dict = {}
if config.has_section('rtv'):
config_dict = dict(config.items('rtv'))
# Convert 'true'/'false' to boolean True/False
if 'ascii' in config_dict:
config_dict['ascii'] = config.getboolean('rtv', 'ascii')
if 'clear_auth' in config_dict:
config_dict['clear_auth'] = config.getboolean('rtv', 'clear_auth')
if 'persistent' in config_dict:
config_dict['persistent'] = config.getboolean('rtv', 'persistent')
self.update(**config_dict)
def load_refresh_token(self):
if os.path.exists(self.token_file):
with open(self.token_file) as fp:
self.refresh_token = fp.read().strip()
else:
self.refresh_token = None
def save_refresh_token(self):
self._ensure_filepath(self.token_file)
with open(self.token_file, 'w+') as fp:
fp.write(self.refresh_token)
def delete_refresh_token(self):
if os.path.exists(self.token_file):
os.remove(self.token_file)
self.refresh_token = None
def load_history(self):
if os.path.exists(self.history_file):
with codecs.open(self.history_file, encoding='utf-8') as fp:
self.history = OrderedSet([line.strip() for line in fp])
else:
self.history = OrderedSet()
def save_history(self):
self._ensure_filepath(self.history_file)
with codecs.open(self.history_file, 'w+', encoding='utf-8') as fp:
fp.writelines('\n'.join(self.history[-self['history_size']:]))
def delete_history(self):
if os.path.exists(self.history_file):
os.remove(self.history_file)
self.history = OrderedSet()
@staticmethod
def _ensure_filepath(filename):
"""
Ensure that the directory exists before trying to write to the file.
"""
filepath = os.path.dirname(filename)
if not os.path.exists(filepath):
os.makedirs(filepath)