Module musar.config

Wrapper for configurable options and parameters.

Expand source code
"""Wrapper for configurable options and parameters.
"""
import re
import logging
from . import accessors
from . import constraints
from . import scopes
from . import rules
from . import cleaners
from . import formats
from .misc import TextEditor


class Options:
    """Namespace for simple parameters.

    Attributes
    ----------
    cover_target_size : Tuple[int, int]
        Size covers will be resized to in `musar.cleaners.Resize`.
    cover_target_format : str
        Image file format for the cover (in Pillow).
        See `musar.accessors.Cover`.
    cover_target_encoding : str
        Color format for the cover (in Pillow).
        See `musar.accessors.Cover`.
    youtube_dl_path : str
        Path to the *youtube-dl* executable.
    mp3tag_path : str
        Path to the *Mp3tag* executable.
    download_folder : str
        Root folder for downloaded files.
    ffmpeg_path : str
        Path to the *FFmpeg* executable.

    """

    def __init__(self):
        self.cover_target_size = 600, 600
        self.cover_target_format = "jpeg"
        self.cover_target_encoding = "RBG"
        self.youtube_dl_path = "youtube-dl"
        self.mp3tag_path = "Mp3tag.exe"
        self.download_folder = "downloads"
        self.ffmpeg_path = "ffmpeg"

    def __str__(self):
        return "\n".join([
            "cover_target_size=%d,%d" % self.cover_target_size,
            "cover_target_format=%s" % self.cover_target_format,
            "cover_target_encoding=%s" % self.cover_target_encoding,
            "youtube_dl_path=%s" % self.youtube_dl_path,
            "mp3tag_path=%s" % self.mp3tag_path,
            "download_folder=%s" % self.download_folder,
            "ffmpeg_path=%s" % self.ffmpeg_path
        ])

    def load(self, line):
        """Load a configuration file line.

        Parameters
        ----------
        line : str
            Line to parse.

        """
        split = line.strip().split("=")
        if len(split) != 2:
            return
        key, value = tuple(map(lambda s: s.strip(), split))
        if key not in self.__dict__:
            return
        if key == "cover_target_size":
            value = tuple(map(lambda s: int(s.strip()), value.split(",")))
        setattr(self, key, value)


class Config:  # pylint: disable=R0902
    """Global configuration.

    Attributes
    ----------
    rules : List[musar.rules.Rule]
        Rules that tracks should follow.
    formats : List[musar.formats.Format]
        Formats for cleaning the tag values.
    options : musar.config.Options
        General parameters.
    extensions : Set[str]
        Set of extensions for `musar.action_convert`.
    accessor_mgr : musar.accessors.Manager
        Accessors manager.
    constraint_mgr : musar.constraints.Manager
        Constraints manager.
    scope_mgr : musar.scopes.Manager
        Scopes manager.
    cleaner_mgr : musar.cleaners.Manager
        Cleaners manager.

    """

    def __init__(self):
        self.rules = list()
        self.formats = list()
        self.options = Options()
        self.extensions = set()
        self.accessor_mgr = accessors.Manager(self)
        self.constraint_mgr = constraints.Manager()
        self.scope_mgr = scopes.Manager()
        self.cleaner_mgr = cleaners.Manager(self)

    def __str__(self):
        return "[RULES]\n" + "\n".join(map(str, self.rules))\
            + "\n\n[FORMATS]\n" + "\n".join(map(str, self.formats))\
            + "\n\n[OPTIONS]\n" + str(self.options)\
            + "\n\n[EXTENSIONS]\n" + "\n".join(self.extensions)

    @classmethod
    def from_file(cls, path):
        """Create a `Config` object from a text file.

        Parameters
        ----------
        cls : type
            Class to create.
        path : str
            Path to the text file to parse.

        Returns
        -------
        musar.config.Config
            Created config

        """
        config = Config()
        config.load(path)
        return config

    def _load_rule(self, line):
        split = line.strip().split(" ")
        if len(split) != 3:
            return
        accessor_name, constraint_name, scope_name = split
        if accessor_name not in self.accessor_mgr:
            return
        if constraint_name not in self.constraint_mgr:
            return
        if scope_name not in self.scope_mgr:
            return
        self.rules.append(rules.Rule(
            self.accessor_mgr[accessor_name],
            self.constraint_mgr[constraint_name],
            self.scope_mgr[scope_name]
        ))

    def _load_format(self, line):
        split = line.strip().split(" ")
        if len(split) == 0:
            return
        if split[0] not in self.accessor_mgr:
            return
        cleaners_ = list()
        for cleaner_name in split[1:]:
            if cleaner_name not in self.cleaner_mgr:
                continue
            cleaners_.append(self.cleaner_mgr[cleaner_name])
        self.formats.append(formats.Format(
            self.accessor_mgr[split[0]],
            *cleaners_
        ))

    def _load_extension(self, line):
        self.extensions.add(line.strip())

    def _load_text(self, text):
        current_category = None
        for line in text.split("\n"):
            if line.strip() == "" or line.strip().startswith("#"):
                continue
            match = re.search(r"\[(.*?)\]", line)
            if match is not None:
                current_category = match.group(1)
                continue
            if current_category == "RULES":
                self._load_rule(line)
            elif current_category == "FORMATS":
                self._load_format(line)
            elif current_category == "OPTIONS":
                self.options.load(line)
            elif current_category == "EXTENSIONS":
                self._load_extension(line)

    def load(self, path):
        """Parse config from an external file.

        Parameters
        ----------
        path : str
            Path to the external config file.

        """
        logging.info("Parsing file %s for config %s", path, repr(self))
        with open(path) as infile:
            self._load_text(infile.read())

    def reset(self):
        """Reset the accessors memories.
        """
        logging.info("Resetting config %s", repr(self))
        for accessor in self.accessor_mgr.values():
            del accessor.memory
            accessor.memory = dict()

    def edit(self):
        """Prompt user with a text editor for live editing the current config.
        This will not modify the actual file.
        """
        logging.info("Editing config %s", repr(self))
        current_config = self.__str__()
        editor = TextEditor()
        new_config = editor(current_config)
        self.rules = list()
        self.formats = list()
        self.options = Options()
        self.extensions = set()
        self._load_text(new_config.strip())

Classes

class Config

Global configuration.

Attributes

rules : List[Rule]
Rules that tracks should follow.
formats : List[Format]
Formats for cleaning the tag values.
options : Options
General parameters.
extensions : Set[str]
Set of extensions for action_convert().
accessor_mgr : Manager
Accessors manager.
constraint_mgr : Manager
Constraints manager.
scope_mgr : Manager
Scopes manager.
cleaner_mgr : Manager
Cleaners manager.
Expand source code
class Config:  # pylint: disable=R0902
    """Global configuration.

    Attributes
    ----------
    rules : List[musar.rules.Rule]
        Rules that tracks should follow.
    formats : List[musar.formats.Format]
        Formats for cleaning the tag values.
    options : musar.config.Options
        General parameters.
    extensions : Set[str]
        Set of extensions for `musar.action_convert`.
    accessor_mgr : musar.accessors.Manager
        Accessors manager.
    constraint_mgr : musar.constraints.Manager
        Constraints manager.
    scope_mgr : musar.scopes.Manager
        Scopes manager.
    cleaner_mgr : musar.cleaners.Manager
        Cleaners manager.

    """

    def __init__(self):
        self.rules = list()
        self.formats = list()
        self.options = Options()
        self.extensions = set()
        self.accessor_mgr = accessors.Manager(self)
        self.constraint_mgr = constraints.Manager()
        self.scope_mgr = scopes.Manager()
        self.cleaner_mgr = cleaners.Manager(self)

    def __str__(self):
        return "[RULES]\n" + "\n".join(map(str, self.rules))\
            + "\n\n[FORMATS]\n" + "\n".join(map(str, self.formats))\
            + "\n\n[OPTIONS]\n" + str(self.options)\
            + "\n\n[EXTENSIONS]\n" + "\n".join(self.extensions)

    @classmethod
    def from_file(cls, path):
        """Create a `Config` object from a text file.

        Parameters
        ----------
        cls : type
            Class to create.
        path : str
            Path to the text file to parse.

        Returns
        -------
        musar.config.Config
            Created config

        """
        config = Config()
        config.load(path)
        return config

    def _load_rule(self, line):
        split = line.strip().split(" ")
        if len(split) != 3:
            return
        accessor_name, constraint_name, scope_name = split
        if accessor_name not in self.accessor_mgr:
            return
        if constraint_name not in self.constraint_mgr:
            return
        if scope_name not in self.scope_mgr:
            return
        self.rules.append(rules.Rule(
            self.accessor_mgr[accessor_name],
            self.constraint_mgr[constraint_name],
            self.scope_mgr[scope_name]
        ))

    def _load_format(self, line):
        split = line.strip().split(" ")
        if len(split) == 0:
            return
        if split[0] not in self.accessor_mgr:
            return
        cleaners_ = list()
        for cleaner_name in split[1:]:
            if cleaner_name not in self.cleaner_mgr:
                continue
            cleaners_.append(self.cleaner_mgr[cleaner_name])
        self.formats.append(formats.Format(
            self.accessor_mgr[split[0]],
            *cleaners_
        ))

    def _load_extension(self, line):
        self.extensions.add(line.strip())

    def _load_text(self, text):
        current_category = None
        for line in text.split("\n"):
            if line.strip() == "" or line.strip().startswith("#"):
                continue
            match = re.search(r"\[(.*?)\]", line)
            if match is not None:
                current_category = match.group(1)
                continue
            if current_category == "RULES":
                self._load_rule(line)
            elif current_category == "FORMATS":
                self._load_format(line)
            elif current_category == "OPTIONS":
                self.options.load(line)
            elif current_category == "EXTENSIONS":
                self._load_extension(line)

    def load(self, path):
        """Parse config from an external file.

        Parameters
        ----------
        path : str
            Path to the external config file.

        """
        logging.info("Parsing file %s for config %s", path, repr(self))
        with open(path) as infile:
            self._load_text(infile.read())

    def reset(self):
        """Reset the accessors memories.
        """
        logging.info("Resetting config %s", repr(self))
        for accessor in self.accessor_mgr.values():
            del accessor.memory
            accessor.memory = dict()

    def edit(self):
        """Prompt user with a text editor for live editing the current config.
        This will not modify the actual file.
        """
        logging.info("Editing config %s", repr(self))
        current_config = self.__str__()
        editor = TextEditor()
        new_config = editor(current_config)
        self.rules = list()
        self.formats = list()
        self.options = Options()
        self.extensions = set()
        self._load_text(new_config.strip())

Static methods

def from_file(path)

Create a Config object from a text file.

Parameters

cls : type
Class to create.
path : str
Path to the text file to parse.

Returns

Config
Created config
Expand source code
@classmethod
def from_file(cls, path):
    """Create a `Config` object from a text file.

    Parameters
    ----------
    cls : type
        Class to create.
    path : str
        Path to the text file to parse.

    Returns
    -------
    musar.config.Config
        Created config

    """
    config = Config()
    config.load(path)
    return config

Methods

def edit(self)

Prompt user with a text editor for live editing the current config. This will not modify the actual file.

Expand source code
def edit(self):
    """Prompt user with a text editor for live editing the current config.
    This will not modify the actual file.
    """
    logging.info("Editing config %s", repr(self))
    current_config = self.__str__()
    editor = TextEditor()
    new_config = editor(current_config)
    self.rules = list()
    self.formats = list()
    self.options = Options()
    self.extensions = set()
    self._load_text(new_config.strip())
def load(self, path)

Parse config from an external file.

Parameters

path : str
Path to the external config file.
Expand source code
def load(self, path):
    """Parse config from an external file.

    Parameters
    ----------
    path : str
        Path to the external config file.

    """
    logging.info("Parsing file %s for config %s", path, repr(self))
    with open(path) as infile:
        self._load_text(infile.read())
def reset(self)

Reset the accessors memories.

Expand source code
def reset(self):
    """Reset the accessors memories.
    """
    logging.info("Resetting config %s", repr(self))
    for accessor in self.accessor_mgr.values():
        del accessor.memory
        accessor.memory = dict()
class Options

Namespace for simple parameters.

Attributes

cover_target_size : Tuple[int, int]
Size covers will be resized to in Resize.
cover_target_format : str
Image file format for the cover (in Pillow). See Cover.
cover_target_encoding : str
Color format for the cover (in Pillow). See Cover.
youtube_dl_path : str
Path to the youtube-dl executable.
mp3tag_path : str
Path to the Mp3tag executable.
download_folder : str
Root folder for downloaded files.
ffmpeg_path : str
Path to the FFmpeg executable.
Expand source code
class Options:
    """Namespace for simple parameters.

    Attributes
    ----------
    cover_target_size : Tuple[int, int]
        Size covers will be resized to in `musar.cleaners.Resize`.
    cover_target_format : str
        Image file format for the cover (in Pillow).
        See `musar.accessors.Cover`.
    cover_target_encoding : str
        Color format for the cover (in Pillow).
        See `musar.accessors.Cover`.
    youtube_dl_path : str
        Path to the *youtube-dl* executable.
    mp3tag_path : str
        Path to the *Mp3tag* executable.
    download_folder : str
        Root folder for downloaded files.
    ffmpeg_path : str
        Path to the *FFmpeg* executable.

    """

    def __init__(self):
        self.cover_target_size = 600, 600
        self.cover_target_format = "jpeg"
        self.cover_target_encoding = "RBG"
        self.youtube_dl_path = "youtube-dl"
        self.mp3tag_path = "Mp3tag.exe"
        self.download_folder = "downloads"
        self.ffmpeg_path = "ffmpeg"

    def __str__(self):
        return "\n".join([
            "cover_target_size=%d,%d" % self.cover_target_size,
            "cover_target_format=%s" % self.cover_target_format,
            "cover_target_encoding=%s" % self.cover_target_encoding,
            "youtube_dl_path=%s" % self.youtube_dl_path,
            "mp3tag_path=%s" % self.mp3tag_path,
            "download_folder=%s" % self.download_folder,
            "ffmpeg_path=%s" % self.ffmpeg_path
        ])

    def load(self, line):
        """Load a configuration file line.

        Parameters
        ----------
        line : str
            Line to parse.

        """
        split = line.strip().split("=")
        if len(split) != 2:
            return
        key, value = tuple(map(lambda s: s.strip(), split))
        if key not in self.__dict__:
            return
        if key == "cover_target_size":
            value = tuple(map(lambda s: int(s.strip()), value.split(",")))
        setattr(self, key, value)

Methods

def load(self, line)

Load a configuration file line.

Parameters

line : str
Line to parse.
Expand source code
def load(self, line):
    """Load a configuration file line.

    Parameters
    ----------
    line : str
        Line to parse.

    """
    split = line.strip().split("=")
    if len(split) != 2:
        return
    key, value = tuple(map(lambda s: s.strip(), split))
    if key not in self.__dict__:
        return
    if key == "cover_target_size":
        value = tuple(map(lambda s: int(s.strip()), value.split(",")))
    setattr(self, key, value)