mirror of
https://github.com/ihabunek/toot.git
synced 2024-11-03 04:17:21 -05:00
Merge pull request #377 from ihabunek/settings
Implement a settings file
This commit is contained in:
commit
d71cc7e3b6
@ -5,6 +5,7 @@
|
||||
- [Installation](installation.md)
|
||||
- [Usage](usage.md)
|
||||
- [Advanced](advanced.md)
|
||||
- [Settings](settings.md)
|
||||
- [TUI](tui.md)
|
||||
- [Contributing](contributing.md)
|
||||
- [Documentation](documentation.md)
|
||||
|
52
docs/settings.md
Normal file
52
docs/settings.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Settings
|
||||
|
||||
Toot can be configured via a [TOML](https://toml.io/en/) settings file.
|
||||
|
||||
> Introduced in toot 0.37.0
|
||||
|
||||
> **Warning:** Settings are experimental and things may change without warning.
|
||||
|
||||
Toot will look for the settings file at:
|
||||
|
||||
* `~/.config/toot/settings.toml` (Linux & co.)
|
||||
* `%APPDATA%\toot\settings.toml` (Windows)
|
||||
|
||||
Toot will respect the `XDG_CONFIG_HOME` environement variable if it's set and
|
||||
look for the settings file in `$XDG_CONFIG_HOME/toot` instead of
|
||||
`~/.config/toot`.
|
||||
|
||||
## Common options
|
||||
|
||||
The `[common]` section includes common options which are applied to all commands.
|
||||
|
||||
```toml
|
||||
[common]
|
||||
# Whether to use ANSI color in output
|
||||
color = true
|
||||
|
||||
# Enable debug logging, shows HTTP requests
|
||||
debug = true
|
||||
|
||||
# Redirect debug log to the given file
|
||||
debug_file = "/tmp/toot.log"
|
||||
|
||||
# Log request and response bodies in the debug log
|
||||
verbose = false
|
||||
|
||||
# Do not write to output
|
||||
quiet = false
|
||||
```
|
||||
|
||||
## Overriding command defaults
|
||||
|
||||
Defaults for command arguments can be override by specifying a `[command.<name>]` section.
|
||||
|
||||
For example, to override `toot post`.
|
||||
|
||||
```toml
|
||||
[command.post]
|
||||
editor = "vim"
|
||||
sensitive = true
|
||||
visibility = "unlisted"
|
||||
scheduled_in = "30 minutes"
|
||||
```
|
1
setup.py
1
setup.py
@ -38,6 +38,7 @@ setup(
|
||||
"beautifulsoup4>=4.5.0,<5.0",
|
||||
"wcwidth>=0.1.7",
|
||||
"urwid>=2.0.0,<3.0",
|
||||
"tomlkit>=0.10.0,<1.0"
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
|
@ -8,7 +8,7 @@ To enable integration tests, export the following environment variables to match
|
||||
your test server and database:
|
||||
|
||||
```
|
||||
export TOOT_TEST_HOSTNAME="localhost:3000"
|
||||
export TOOT_TEST_BASE_URL="localhost:3000"
|
||||
export TOOT_TEST_DATABASE_DSN="dbname=mastodon_development"
|
||||
```
|
||||
"""
|
||||
@ -26,6 +26,11 @@ from toot.exceptions import ApiError, ConsoleError
|
||||
from toot.output import print_out
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
import toot.settings
|
||||
toot.settings.DISABLE_SETTINGS = True
|
||||
|
||||
|
||||
# Mastodon database name, used to confirm user registration without having to click the link
|
||||
DATABASE_DSN = os.getenv("TOOT_TEST_DATABASE_DSN")
|
||||
|
||||
|
@ -1,3 +1,7 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from os.path import join, expanduser
|
||||
from collections import namedtuple
|
||||
|
||||
__version__ = '0.37.0'
|
||||
@ -9,3 +13,22 @@ DEFAULT_INSTANCE = 'https://mastodon.social'
|
||||
|
||||
CLIENT_NAME = 'toot - a Mastodon CLI client'
|
||||
CLIENT_WEBSITE = 'https://github.com/ihabunek/toot'
|
||||
|
||||
TOOT_CONFIG_DIR_NAME = "toot"
|
||||
|
||||
|
||||
def get_config_dir():
|
||||
"""Returns the path to toot config directory"""
|
||||
|
||||
# On Windows, store the config in roaming appdata
|
||||
if sys.platform == "win32" and "APPDATA" in os.environ:
|
||||
return join(os.getenv("APPDATA"), TOOT_CONFIG_DIR_NAME)
|
||||
|
||||
# Respect XDG_CONFIG_HOME env variable if set
|
||||
# https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
if "XDG_CONFIG_HOME" in os.environ:
|
||||
config_home = expanduser(os.environ["XDG_CONFIG_HOME"])
|
||||
return join(config_home, TOOT_CONFIG_DIR_NAME)
|
||||
|
||||
# Default to ~/.config/toot/
|
||||
return join(expanduser("~"), ".config", TOOT_CONFIG_DIR_NAME)
|
||||
|
@ -1,44 +1,22 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from functools import wraps
|
||||
from os.path import dirname, join, expanduser
|
||||
from os.path import dirname, join
|
||||
|
||||
from toot import User, App
|
||||
from toot import User, App, get_config_dir
|
||||
from toot.exceptions import ConsoleError
|
||||
from toot.output import print_out
|
||||
|
||||
|
||||
TOOT_CONFIG_DIR_NAME = "toot"
|
||||
TOOT_CONFIG_FILE_NAME = "config.json"
|
||||
|
||||
|
||||
def get_config_dir():
|
||||
"""Returns the path to toot config directory"""
|
||||
|
||||
# On Windows, store the config in roaming appdata
|
||||
if sys.platform == "win32" and "APPDATA" in os.environ:
|
||||
return join(os.getenv("APPDATA"), TOOT_CONFIG_DIR_NAME)
|
||||
|
||||
# Respect XDG_CONFIG_HOME env variable if set
|
||||
# https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
if "XDG_CONFIG_HOME" in os.environ:
|
||||
config_home = expanduser(os.environ["XDG_CONFIG_HOME"])
|
||||
return join(config_home, TOOT_CONFIG_DIR_NAME)
|
||||
|
||||
# Default to ~/.config/toot/
|
||||
return join(expanduser("~"), ".config", TOOT_CONFIG_DIR_NAME)
|
||||
|
||||
|
||||
def get_config_file_path():
|
||||
"""Returns the path to toot config file."""
|
||||
return join(get_config_dir(), TOOT_CONFIG_FILE_NAME)
|
||||
|
||||
|
||||
CONFIG_FILE = get_config_file_path()
|
||||
|
||||
|
||||
def user_id(user):
|
||||
return "{}@{}".format(user.username, user.instance)
|
||||
|
||||
@ -63,15 +41,18 @@ def make_config(path):
|
||||
|
||||
|
||||
def load_config():
|
||||
if not os.path.exists(CONFIG_FILE):
|
||||
make_config(CONFIG_FILE)
|
||||
path = get_config_file_path()
|
||||
|
||||
with open(CONFIG_FILE) as f:
|
||||
if not os.path.exists(path):
|
||||
make_config(path)
|
||||
|
||||
with open(path) as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def save_config(config):
|
||||
with open(CONFIG_FILE, 'w') as f:
|
||||
path = get_config_file_path()
|
||||
with open(path, "w") as f:
|
||||
return json.dump(config, f, indent=True, sort_keys=True)
|
||||
|
||||
|
||||
|
@ -7,9 +7,10 @@ import sys
|
||||
from argparse import ArgumentParser, FileType, ArgumentTypeError, Action
|
||||
from collections import namedtuple
|
||||
from itertools import chain
|
||||
from toot import config, commands, CLIENT_NAME, CLIENT_WEBSITE, __version__
|
||||
from toot import config, commands, CLIENT_NAME, CLIENT_WEBSITE, __version__, settings
|
||||
from toot.exceptions import ApiError, ConsoleError
|
||||
from toot.output import print_out, print_err
|
||||
from toot.settings import get_setting
|
||||
|
||||
VISIBILITY_CHOICES = ["public", "unlisted", "private", "direct"]
|
||||
VISIBILITY_CHOICES_STR = ", ".join(f"'{v}'" for v in VISIBILITY_CHOICES)
|
||||
@ -882,12 +883,24 @@ def get_argument_parser(name, command):
|
||||
if command.require_auth:
|
||||
combined_args += common_auth_args
|
||||
|
||||
defaults = get_setting(f"commands.{name}", dict, {})
|
||||
|
||||
for args, kwargs in combined_args:
|
||||
# Set default value from settings if exists
|
||||
default = get_default_value(defaults, args)
|
||||
if default is not None:
|
||||
kwargs["default"] = default
|
||||
parser.add_argument(*args, **kwargs)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def get_default_value(defaults, args):
|
||||
# Hacky way to determine command name from argparse args
|
||||
name = args[-1].lstrip("-").replace("-", "_")
|
||||
return defaults.get(name)
|
||||
|
||||
|
||||
def run_command(app, user, name, args):
|
||||
command = next((c for c in COMMANDS if c.name == name), None)
|
||||
|
||||
@ -919,9 +932,8 @@ def run_command(app, user, name, args):
|
||||
|
||||
|
||||
def main():
|
||||
# Enable debug logging if --debug is in args
|
||||
if "--debug" in sys.argv:
|
||||
filename = os.getenv("TOOT_LOG_FILE")
|
||||
if settings.get_debug():
|
||||
filename = settings.get_debug_file()
|
||||
logging.basicConfig(level=logging.DEBUG, filename=filename)
|
||||
logging.getLogger("urllib3").setLevel(logging.INFO)
|
||||
|
||||
|
@ -3,6 +3,8 @@ import re
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
from functools import lru_cache
|
||||
from toot import settings
|
||||
from toot.entities import Instance, Notification, Poll, Status
|
||||
from toot.utils import get_text, parse_html
|
||||
from toot.wcstring import wc_wrap
|
||||
@ -99,6 +101,7 @@ def strip_tags(message):
|
||||
return re.sub(STYLE_TAG_PATTERN, "", message)
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def use_ansi_color():
|
||||
"""Returns True if ANSI color codes should be used."""
|
||||
|
||||
@ -115,23 +118,24 @@ def use_ansi_color():
|
||||
if "--no-color" in sys.argv:
|
||||
return False
|
||||
|
||||
# Check in settings
|
||||
color = settings.get_setting("common.color", bool)
|
||||
if color is not None:
|
||||
return color
|
||||
|
||||
# Use color by default
|
||||
return True
|
||||
|
||||
|
||||
USE_ANSI_COLOR = use_ansi_color()
|
||||
|
||||
QUIET = "--quiet" in sys.argv
|
||||
|
||||
|
||||
def print_out(*args, **kwargs):
|
||||
if not QUIET:
|
||||
args = [colorize(a) if USE_ANSI_COLOR else strip_tags(a) for a in args]
|
||||
if not settings.get_quiet():
|
||||
args = [colorize(a) if use_ansi_color() else strip_tags(a) for a in args]
|
||||
print(*args, **kwargs)
|
||||
|
||||
|
||||
def print_err(*args, **kwargs):
|
||||
args = [f"<red>{a}</red>" for a in args]
|
||||
args = [colorize(a) if USE_ANSI_COLOR else strip_tags(a) for a in args]
|
||||
args = [colorize(a) if use_ansi_color() else strip_tags(a) for a in args]
|
||||
print(*args, file=sys.stderr, **kwargs)
|
||||
|
||||
|
||||
|
84
toot/settings.py
Normal file
84
toot/settings.py
Normal file
@ -0,0 +1,84 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from functools import lru_cache
|
||||
from os.path import exists, join
|
||||
from tomlkit import parse
|
||||
from toot import get_config_dir
|
||||
from typing import Optional, Type
|
||||
|
||||
|
||||
DISABLE_SETTINGS = False
|
||||
|
||||
TOOT_SETTINGS_FILE_NAME = "settings.toml"
|
||||
|
||||
|
||||
def get_settings_path():
|
||||
return join(get_config_dir(), TOOT_SETTINGS_FILE_NAME)
|
||||
|
||||
|
||||
def load_settings() -> dict:
|
||||
# Used for testing without config file
|
||||
if DISABLE_SETTINGS:
|
||||
return {}
|
||||
|
||||
path = get_settings_path()
|
||||
|
||||
if not exists(path):
|
||||
return {}
|
||||
|
||||
with open(path) as f:
|
||||
return parse(f.read())
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def get_settings():
|
||||
return load_settings()
|
||||
|
||||
|
||||
def get_setting(key: str, type: Type, default=None):
|
||||
"""
|
||||
Get a setting value. The key should be a dot-separated string,
|
||||
e.g. "commands.post.editor" which will correspond to the "editor" setting
|
||||
inside the `[commands.post]` section.
|
||||
"""
|
||||
settings = get_settings()
|
||||
return _get_setting(settings, key.split("."), type, default)
|
||||
|
||||
|
||||
def _get_setting(dct, keys, type: Type, default=None):
|
||||
if len(keys) == 0:
|
||||
if isinstance(dct, type):
|
||||
return dct
|
||||
else:
|
||||
# TODO: warn? cast? both?
|
||||
return default
|
||||
|
||||
key = keys[0]
|
||||
if isinstance(dct, dict) and key in dct:
|
||||
return _get_setting(dct[key], keys[1:], type, default)
|
||||
|
||||
return default
|
||||
|
||||
|
||||
def get_debug() -> bool:
|
||||
if "--debug" in sys.argv:
|
||||
return True
|
||||
|
||||
return get_setting("common.debug", bool, False)
|
||||
|
||||
|
||||
def get_debug_file() -> Optional[str]:
|
||||
from_env = os.getenv("TOOT_LOG_FILE")
|
||||
if from_env:
|
||||
return from_env
|
||||
|
||||
return get_setting("common.debug_file", str)
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def get_quiet():
|
||||
if "--quiet" in sys.argv:
|
||||
return True
|
||||
|
||||
return get_setting("common.quiet", str, False)
|
Loading…
Reference in New Issue
Block a user