diff --git a/streamrip/config.py b/streamrip/config.py index c3c2c64..247c5fe 100644 --- a/streamrip/config.py +++ b/streamrip/config.py @@ -3,6 +3,7 @@ import copy import logging import os import re +from functools import cache from pprint import pformat, pprint from ruamel.yaml import YAML @@ -15,6 +16,7 @@ from .constants import ( TRACK_FORMAT, ) from .exceptions import InvalidSourceError +from .utils import safe_get yaml = YAML() @@ -149,6 +151,9 @@ class Config: logger.debug("Config saved: %s", self._path) yaml.dump(info, cfg) + docs = ConfigDocumentation() + docs.dump(self._path) + @property def tidal_creds(self): """Return a TidalClient compatible dict of credentials.""" @@ -194,20 +199,17 @@ class Config: return f"Config({pformat(self.session)})" -class ConfigDocumentationHelper: - """A helper class that writes documentation for the config file. +class ConfigDocumentation: + """Documentation is stored in this docstring. qobuz: quality: 1: 320kbps MP3, 2: 16/44.1, 3: 24/<=96, 4: 24/>=96 app_id: Do not change secrets: Do not change tidal: quality: 0, 1, 2, or 3 - user_id: Do not change - country_code: Do not change - access_token: Do not change - refresh_token: Do not change - token_expiry: Do not change - deezer: Does not require login + user_id: Do not change any of the fields below + token_expiry: Tokens last 1 week after refresh. This is the Unix timestamp of the expiration time. + deezer: Deezer doesn't require login quality: 0, 1, or 2 soundcloud: quality: Only 0 is available @@ -233,32 +235,75 @@ class ConfigDocumentationHelper: track: Available keys: "tracknumber", "artist", "albumartist", "composer", and "title" lastfm: Last.fm playlists are downloaded by searching for the titles of 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): self.docs = [] doctext = self.__doc__ + # get indent level, key, and documentation keyval = re.compile(r"( *)([\w_]+):\s*(.*)") lines = (line[4:] for line in doctext.split("\n")[1:-1]) for line in lines: info = list(keyval.match(line).groups()) if len(info) == 3: - info[0] = len(info[0]) // 4 - else: + info[0] = len(info[0]) // 4 # here use standard 4 spaces/tab + else: # line doesn't start with spaces info.insert(0, 0) self.docs.append(info) - pprint(self.docs) - def write(self): - pass + def dump(self, path: str): + """Write comments to an uncommented YAML file. - def __setitem__(self, key, val): - self.comments[key] = val + :param path: + :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): - return self.comments[key] + with open(path, "w") as f: + 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))