diff --git a/tests/test_theme.py b/tests/test_theme.py index 9d0102f..50e566b 100644 --- a/tests/test_theme.py +++ b/tests/test_theme.py @@ -8,7 +8,7 @@ from tempfile import mkdtemp, NamedTemporaryFile import pytest from tuir.theme import Theme -from tuir.config import DEFAULT_THEMES +from tuir.config import Config from tuir.exceptions import ConfigError try: @@ -115,7 +115,7 @@ def test_theme_element_selected_attributes(): def test_theme_default_cfg_matches_builtin(): - filename = os.path.join(DEFAULT_THEMES, 'default.cfg.example') + filename = os.path.join(Config.DEFAULT_THEMES, 'default.cfg.example') default_theme = Theme.from_file(filename, 'built-in') # The default theme file should match the hardcoded values diff --git a/tuir/config.py b/tuir/config.py index b612ae5..574d87f 100644 --- a/tuir/config.py +++ b/tuir/config.py @@ -13,157 +13,33 @@ from six.moves import configparser from . import docs, __version__ from .objects import KeyMap -PACKAGE = os.path.dirname(__file__) -HOME = os.path.expanduser('~') -TEMPLATES = os.path.join(PACKAGE, 'templates') -DEFAULT_CONFIG = os.path.join(TEMPLATES, 'tuir.cfg') -DEFAULT_MAILCAP = os.path.join(TEMPLATES, 'mailcap') -DEFAULT_THEMES = os.path.join(PACKAGE, 'themes') -XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.join(HOME, '.config')) -XDG_DATA_HOME = os.getenv('XDG_DATA_HOME', os.path.join(HOME, '.local', 'share')) -CONFIG = os.path.join(XDG_CONFIG_HOME, 'tuir', 'tuir.cfg') -MAILCAP = os.path.join(HOME, '.mailcap') -TOKEN = os.path.join(XDG_DATA_HOME, 'tuir', 'refresh-token') -HISTORY = os.path.join(XDG_DATA_HOME, 'tuir', 'history.log') -THEMES = os.path.join(XDG_CONFIG_HOME, 'tuir', 'themes') - - -def build_parser(): - parser = argparse.ArgumentParser( - prog='tuir', description=docs.SUMMARY, - epilog=docs.CONTROLS, - usage=docs.USAGE, - formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument( - 'link', metavar='URL', nargs='?', - help='[optional] Full URL of a submission to open') - parser.add_argument( - '-s', dest='subreddit', - help='Name of the subreddit that will be loaded on start') - parser.add_argument( - '-l', dest='link_deprecated', - help=argparse.SUPPRESS) # Deprecated, use the positional arg instead - parser.add_argument( - '--log', metavar='FILE', action='store', - help='Log HTTP requests to the given file') - parser.add_argument( - '--config', metavar='FILE', action='store', - help='Load configuration settings from the given file') - parser.add_argument( - '--ascii', action='store_const', const=True, - help='Enable ascii-only mode') - parser.add_argument( - '--monochrome', action='store_const', const=True, - help='Disable color') - parser.add_argument( - '--theme', metavar='FILE', action='store', - help='Color theme to use, see --list-themes for valid options') - parser.add_argument( - '--list-themes', metavar='FILE', action='store_const', const=True, - help='List all of the available color themes') - parser.add_argument( - '--non-persistent', dest='persistent', action='store_const', const=False, - help='Forget the authenticated user when the program exits') - parser.add_argument( - '--no-autologin', dest='autologin', action='store_const', const=False, - help='Do not authenticate automatically on startup') - parser.add_argument( - '--clear-auth', dest='clear_auth', action='store_const', const=True, - help='Remove any saved user data before launching') - parser.add_argument( - '--copy-config', dest='copy_config', action='store_const', const=True, - help='Copy the default configuration to {HOME}/.config/tuir/tuir.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') - parser.add_argument( - '-V', '--version', action='version', version='tuir ' + __version__) - parser.add_argument( - '--no-flash', dest='flash', action='store_const', const=False, - help='Disable screen flashing') - parser.add_argument( - '--debug-info', dest='debug_info', action='store_const', const=True, - help='Show system and environment information and exit') - 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 tuir 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(destination): - try: - ch = six.moves.input( - 'File %s already exists, overwrite? y/[n]):' % destination) - if ch not in ('Y', 'y'): - return - except KeyboardInterrupt: - return - - filepath = os.path.dirname(destination) - if not os.path.exists(filepath): - os.makedirs(filepath) - - print('Copying default %s to %s' % (name, destination)) - shutil.copy(source, destination) - os.chmod(destination, 0o664) - - -class OrderedSet(object): - """ - A simple implementation of an ordered set. A set is used to check - for membership, and a list is used to maintain ordering. - """ - - def __init__(self, elements=None): - elements = elements or [] - self._set = set(elements) - self._list = elements - - def __contains__(self, item): - return item in self._set - - def __len__(self): - return len(self._list) - - def __getitem__(self, item): - return self._list[item] - - def add(self, item): - self._set.add(item) - self._list.append(item) - class Config(object): """ This class manages the loading and saving of configs and other files. """ + PACKAGE = os.path.dirname(__file__) + HOME = os.path.expanduser('~') + TEMPLATES = os.path.join(PACKAGE, 'templates') + DEFAULT_CONFIG = os.path.join(TEMPLATES, 'tuir.cfg') + DEFAULT_MAILCAP = os.path.join(TEMPLATES, 'mailcap') + DEFAULT_THEMES = os.path.join(PACKAGE, 'themes') + XDG_CONFIG_HOME = os.getenv('XDG_CONFIG_HOME', os.path.join(HOME, '.config')) + XDG_DATA_HOME = os.getenv('XDG_DATA_HOME', os.path.join(HOME, '.local', 'share')) + CONFIG = os.path.join(XDG_CONFIG_HOME, 'tuir', 'tuir.cfg') + MAILCAP = os.path.join(HOME, '.mailcap') + TOKEN = os.path.join(XDG_DATA_HOME, 'tuir', 'refresh-token') + HISTORY = os.path.join(XDG_DATA_HOME, 'tuir', 'history.log') + THEMES = os.path.join(XDG_CONFIG_HOME, 'tuir', 'themes') + def __init__(self, history_file=HISTORY, token_file=TOKEN, **kwargs): self.history_file = history_file self.token_file = token_file self.config = kwargs - default, bindings = self.get_file(DEFAULT_CONFIG) + default, bindings = self.get_file(self.DEFAULT_CONFIG) self.default = default self.keymap = KeyMap(bindings) @@ -245,7 +121,7 @@ class Config(object): """ if filename is None: - filename = CONFIG + filename = Config.CONFIG config = configparser.ConfigParser() if os.path.exists(filename): @@ -301,3 +177,127 @@ class Config(object): filepath = os.path.dirname(filename) if not os.path.exists(filepath): os.makedirs(filepath) + + +def build_parser(): + parser = argparse.ArgumentParser( + prog='tuir', description=docs.SUMMARY, + epilog=docs.CONTROLS, + usage=docs.USAGE, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + 'link', metavar='URL', nargs='?', + help='[optional] Full URL of a submission to open') + parser.add_argument( + '-s', dest='subreddit', + help='Name of the subreddit that will be loaded on start') + parser.add_argument( + '-l', dest='link_deprecated', + help=argparse.SUPPRESS) # Deprecated, use the positional arg instead + parser.add_argument( + '--log', metavar='FILE', action='store', + help='Log HTTP requests to the given file') + parser.add_argument( + '--config', metavar='FILE', action='store', + help='Load configuration settings from the given file') + parser.add_argument( + '--ascii', action='store_const', const=True, + help='Enable ascii-only mode') + parser.add_argument( + '--monochrome', action='store_const', const=True, + help='Disable color') + parser.add_argument( + '--theme', metavar='FILE', action='store', + help='Color theme to use, see --list-themes for valid options') + parser.add_argument( + '--list-themes', metavar='FILE', action='store_const', const=True, + help='List all of the available color themes') + parser.add_argument( + '--non-persistent', dest='persistent', action='store_const', const=False, + help='Forget the authenticated user when the program exits') + parser.add_argument( + '--no-autologin', dest='autologin', action='store_const', const=False, + help='Do not authenticate automatically on startup') + parser.add_argument( + '--clear-auth', dest='clear_auth', action='store_const', const=True, + help='Remove any saved user data before launching') + parser.add_argument( + '--copy-config', dest='copy_config', action='store_const', const=True, + help='Copy the default configuration to {HOME}/.config/tuir/tuir.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') + parser.add_argument( + '-V', '--version', action='version', version='tuir ' + __version__) + parser.add_argument( + '--no-flash', dest='flash', action='store_const', const=False, + help='Disable screen flashing') + parser.add_argument( + '--debug-info', dest='debug_info', action='store_const', const=True, + help='Show system and environment information and exit') + return parser + + +def copy_default_mailcap(filename=Config.MAILCAP): + """ + Copy the example mailcap configuration to the specified file. + """ + return _copy_settings_file(Config.DEFAULT_MAILCAP, filename, 'mailcap') + + +def copy_default_config(filename=Config.CONFIG): + """ + Copy the default tuir user configuration to the specified file. + """ + return _copy_settings_file(Config.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(destination): + try: + ch = six.moves.input( + 'File %s already exists, overwrite? y/[n]):' % destination) + if ch not in ('Y', 'y'): + return + except KeyboardInterrupt: + return + + filepath = os.path.dirname(destination) + if not os.path.exists(filepath): + os.makedirs(filepath) + + print('Copying default %s to %s' % (name, destination)) + shutil.copy(source, destination) + os.chmod(destination, 0o664) + + +class OrderedSet(object): + """ + A simple implementation of an ordered set. A set is used to check + for membership, and a list is used to maintain ordering. + """ + + def __init__(self, elements=None): + elements = elements or [] + self._set = set(elements) + self._list = elements + + def __contains__(self, item): + return item in self._set + + def __len__(self): + return len(self._list) + + def __getitem__(self, item): + return self._list[item] + + def add(self, item): + self._set.add(item) + self._list.append(item) diff --git a/tuir/oauth.py b/tuir/oauth.py index 304fca2..5e2c6ed 100644 --- a/tuir/oauth.py +++ b/tuir/oauth.py @@ -14,13 +14,13 @@ from six.moves.urllib.parse import urlparse, parse_qs from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from . import docs -from .config import TEMPLATES +from .config import Config from .exceptions import InvalidRefreshToken from .packages.praw.errors import HTTPException, OAuthException _logger = logging.getLogger(__name__) -INDEX = os.path.join(TEMPLATES, 'index.html') +INDEX = os.path.join(Config.TEMPLATES, 'index.html') class OAuthHTTPServer(HTTPServer): diff --git a/tuir/theme.py b/tuir/theme.py index dacd244..57b3288 100644 --- a/tuir/theme.py +++ b/tuir/theme.py @@ -10,7 +10,7 @@ from contextlib import contextmanager import six from six.moves import configparser -from .config import THEMES, DEFAULT_THEMES +from .config import Config from .exceptions import ConfigError _logger = logging.getLogger(__name__) @@ -290,7 +290,7 @@ class Theme(object): self._selected = None @classmethod - def list_themes(cls, path=THEMES): + def list_themes(cls, path=Config.THEMES): """ Compile all of the themes configuration files in the search path. """ @@ -316,13 +316,13 @@ class Theme(object): themes.append(theme) themes.extend([Theme(use_color=True), Theme(use_color=False)]) - load_themes(DEFAULT_THEMES, 'preset') + load_themes(Config.DEFAULT_THEMES, 'preset') load_themes(path, 'installed') return themes, errors @classmethod - def print_themes(cls, path=THEMES): + def print_themes(cls, path=Config.THEMES): """ Prints a human-readable summary of the installed themes to stdout. @@ -363,7 +363,7 @@ class Theme(object): print('') @classmethod - def from_name(cls, name, path=THEMES): + def from_name(cls, name, path=Config.THEMES): """ Search for the given theme on the filesystem and attempt to load it. @@ -378,7 +378,7 @@ class Theme(object): if os.path.isfile(filename): return cls.from_file(filename, 'installed') - filename = os.path.join(DEFAULT_THEMES, '{0}.cfg'.format(name)) + filename = os.path.join(Config.DEFAULT_THEMES, '{0}.cfg'.format(name)) if os.path.isfile(filename): return cls.from_file(filename, 'preset')