Encapsulate config constants in Config class
This commit is contained in:
@@ -8,7 +8,7 @@ from tempfile import mkdtemp, NamedTemporaryFile
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from tuir.theme import Theme
|
from tuir.theme import Theme
|
||||||
from tuir.config import DEFAULT_THEMES
|
from tuir.config import Config
|
||||||
from tuir.exceptions import ConfigError
|
from tuir.exceptions import ConfigError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -115,7 +115,7 @@ def test_theme_element_selected_attributes():
|
|||||||
|
|
||||||
def test_theme_default_cfg_matches_builtin():
|
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')
|
default_theme = Theme.from_file(filename, 'built-in')
|
||||||
|
|
||||||
# The default theme file should match the hardcoded values
|
# The default theme file should match the hardcoded values
|
||||||
|
|||||||
280
tuir/config.py
280
tuir/config.py
@@ -13,157 +13,33 @@ from six.moves import configparser
|
|||||||
from . import docs, __version__
|
from . import docs, __version__
|
||||||
from .objects import KeyMap
|
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):
|
class Config(object):
|
||||||
"""
|
"""
|
||||||
This class manages the loading and saving of configs and other files.
|
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):
|
def __init__(self, history_file=HISTORY, token_file=TOKEN, **kwargs):
|
||||||
|
|
||||||
self.history_file = history_file
|
self.history_file = history_file
|
||||||
self.token_file = token_file
|
self.token_file = token_file
|
||||||
self.config = kwargs
|
self.config = kwargs
|
||||||
|
|
||||||
default, bindings = self.get_file(DEFAULT_CONFIG)
|
default, bindings = self.get_file(self.DEFAULT_CONFIG)
|
||||||
self.default = default
|
self.default = default
|
||||||
self.keymap = KeyMap(bindings)
|
self.keymap = KeyMap(bindings)
|
||||||
|
|
||||||
@@ -245,7 +121,7 @@ class Config(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if filename is None:
|
if filename is None:
|
||||||
filename = CONFIG
|
filename = Config.CONFIG
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
if os.path.exists(filename):
|
if os.path.exists(filename):
|
||||||
@@ -301,3 +177,127 @@ class Config(object):
|
|||||||
filepath = os.path.dirname(filename)
|
filepath = os.path.dirname(filename)
|
||||||
if not os.path.exists(filepath):
|
if not os.path.exists(filepath):
|
||||||
os.makedirs(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)
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ from six.moves.urllib.parse import urlparse, parse_qs
|
|||||||
from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
|
||||||
from . import docs
|
from . import docs
|
||||||
from .config import TEMPLATES
|
from .config import Config
|
||||||
from .exceptions import InvalidRefreshToken
|
from .exceptions import InvalidRefreshToken
|
||||||
from .packages.praw.errors import HTTPException, OAuthException
|
from .packages.praw.errors import HTTPException, OAuthException
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
INDEX = os.path.join(TEMPLATES, 'index.html')
|
INDEX = os.path.join(Config.TEMPLATES, 'index.html')
|
||||||
|
|
||||||
|
|
||||||
class OAuthHTTPServer(HTTPServer):
|
class OAuthHTTPServer(HTTPServer):
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from contextlib import contextmanager
|
|||||||
import six
|
import six
|
||||||
from six.moves import configparser
|
from six.moves import configparser
|
||||||
|
|
||||||
from .config import THEMES, DEFAULT_THEMES
|
from .config import Config
|
||||||
from .exceptions import ConfigError
|
from .exceptions import ConfigError
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
@@ -290,7 +290,7 @@ class Theme(object):
|
|||||||
self._selected = None
|
self._selected = None
|
||||||
|
|
||||||
@classmethod
|
@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.
|
Compile all of the themes configuration files in the search path.
|
||||||
"""
|
"""
|
||||||
@@ -316,13 +316,13 @@ class Theme(object):
|
|||||||
themes.append(theme)
|
themes.append(theme)
|
||||||
|
|
||||||
themes.extend([Theme(use_color=True), Theme(use_color=False)])
|
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')
|
load_themes(path, 'installed')
|
||||||
|
|
||||||
return themes, errors
|
return themes, errors
|
||||||
|
|
||||||
@classmethod
|
@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.
|
Prints a human-readable summary of the installed themes to stdout.
|
||||||
|
|
||||||
@@ -363,7 +363,7 @@ class Theme(object):
|
|||||||
print('')
|
print('')
|
||||||
|
|
||||||
@classmethod
|
@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.
|
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):
|
if os.path.isfile(filename):
|
||||||
return cls.from_file(filename, 'installed')
|
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):
|
if os.path.isfile(filename):
|
||||||
return cls.from_file(filename, 'preset')
|
return cls.from_file(filename, 'preset')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user