Add annotations in config file

This commit is contained in:
nathom 2021-04-14 18:30:27 -07:00
parent 0304fae688
commit a3dda3768a

View file

@ -3,6 +3,7 @@ import copy
import logging import logging
import os import os
import re import re
from functools import cache
from pprint import pformat, pprint from pprint import pformat, pprint
from ruamel.yaml import YAML from ruamel.yaml import YAML
@ -15,6 +16,7 @@ from .constants import (
TRACK_FORMAT, TRACK_FORMAT,
) )
from .exceptions import InvalidSourceError from .exceptions import InvalidSourceError
from .utils import safe_get
yaml = YAML() yaml = YAML()
@ -149,6 +151,9 @@ class Config:
logger.debug("Config saved: %s", self._path) logger.debug("Config saved: %s", self._path)
yaml.dump(info, cfg) yaml.dump(info, cfg)
docs = ConfigDocumentation()
docs.dump(self._path)
@property @property
def tidal_creds(self): def tidal_creds(self):
"""Return a TidalClient compatible dict of credentials.""" """Return a TidalClient compatible dict of credentials."""
@ -194,20 +199,17 @@ class Config:
return f"Config({pformat(self.session)})" return f"Config({pformat(self.session)})"
class ConfigDocumentationHelper: class ConfigDocumentation:
"""A helper class that writes documentation for the config file. """Documentation is stored in this docstring.
qobuz: qobuz:
quality: 1: 320kbps MP3, 2: 16/44.1, 3: 24/<=96, 4: 24/>=96 quality: 1: 320kbps MP3, 2: 16/44.1, 3: 24/<=96, 4: 24/>=96
app_id: Do not change app_id: Do not change
secrets: Do not change secrets: Do not change
tidal: tidal:
quality: 0, 1, 2, or 3 quality: 0, 1, 2, or 3
user_id: Do not change user_id: Do not change any of the fields below
country_code: Do not change token_expiry: Tokens last 1 week after refresh. This is the Unix timestamp of the expiration time.
access_token: Do not change deezer: Deezer doesn't require login
refresh_token: Do not change
token_expiry: Do not change
deezer: Does not require login
quality: 0, 1, or 2 quality: 0, 1, or 2
soundcloud: soundcloud:
quality: Only 0 is available quality: Only 0 is available
@ -233,32 +235,75 @@ class ConfigDocumentationHelper:
track: Available keys: "tracknumber", "artist", "albumartist", "composer", and "title" track: Available keys: "tracknumber", "artist", "albumartist", "composer", and "title"
lastfm: Last.fm playlists are downloaded by searching for the titles of the tracks lastfm: Last.fm playlists are downloaded by searching for the titles of the tracks
source: The source on which to search for the tracks. source: The source on which to search for the tracks.
concurrent_downoads: Download (and convert) tracks all at once, instead of sequentially. If you are converting the tracks, and/or have fast internet, this will substantially improve processing speed. concurrent_downloads: Download (and convert) tracks all at once, instead of sequentially. If you are converting the tracks, and/or have fast internet, this will substantially improve processing speed.
""" """
comments = _set_to_none(copy.deepcopy(Config.defaults))
def __init__(self): def __init__(self):
self.docs = [] self.docs = []
doctext = self.__doc__ doctext = self.__doc__
# get indent level, key, and documentation
keyval = re.compile(r"( *)([\w_]+):\s*(.*)") keyval = re.compile(r"( *)([\w_]+):\s*(.*)")
lines = (line[4:] for line in doctext.split("\n")[1:-1]) lines = (line[4:] for line in doctext.split("\n")[1:-1])
for line in lines: for line in lines:
info = list(keyval.match(line).groups()) info = list(keyval.match(line).groups())
if len(info) == 3: if len(info) == 3:
info[0] = len(info[0]) // 4 info[0] = len(info[0]) // 4 # here use standard 4 spaces/tab
else: else: # line doesn't start with spaces
info.insert(0, 0) info.insert(0, 0)
self.docs.append(info) self.docs.append(info)
pprint(self.docs)
def write(self): def dump(self, path: str):
pass """Write comments to an uncommented YAML file.
def __setitem__(self, key, val): :param path:
self.comments[key] = val :type path: str
"""
is_comment = re.compile(r"^\s*#.*")
with open(path) as f:
# includes newline at the end
lines = f.readlines()
def __getitem__(self, key): with open(path, "w") as f:
return self.comments[key] while lines != []:
line = lines.pop(0)
found = False
to_remove = None
for level, key, doc in self.docs:
# using 1 indent = 2 spaces like ruamel.yaml
spaces = level * " "
comment = f"{spaces}# {doc}"
if is_comment.match(line):
# update comment
found = True
break
re_obj = self._get_key_regex(spaces, key)
match = re_obj.match(line)
if match is not None: # line contains the key
if doc != "":
f.write(f"{comment}\n{line}")
found = True
to_remove = [level, key, doc]
break
if not found: # field with no comment
f.write(line)
if to_remove is not None:
# key, doc pairs are unique
self.docs.remove(to_remove)
@cache
def _get_key_regex(self, spaces, key):
regex = rf"{spaces}{key}:(?:$|\s+?(.+))"
return re.compile(regex)
def strip_comments(self, path: str):
with open(path, "r") as f:
lines = [line for line in f.readlines() if not line.strip().startswith("#")]
with open(path, "w") as f:
f.write("".join(lines))