Skip to content

Commit

Permalink
expand XAutoPF functionality (#57)
Browse files Browse the repository at this point in the history
* create xautopf playlist parser and move current parsing logic to it

* refactor xautopf save logic to parser

* add limiter and sorter parsers

* add matcher parser

* add parser save test

* add default xml for XAutoPF files
  • Loading branch information
geo-martino authored Apr 2, 2024
1 parent d7df41a commit 2ea38ce
Show file tree
Hide file tree
Showing 25 changed files with 974 additions and 663 deletions.
9 changes: 9 additions & 0 deletions docs/release-history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ Added
* Introduced :py:class:`.MusifyItemSettable` class to allow distinction
between items that can have their properties set and those that can't
* Extend :py:class:`.FilterMatcher` with group_by tag functionality
* Now fully supports parsing of processors relating to XAutoPF objects with full I/O of settings
to/from their related XML files on disk.
* Now supports creating new XAutoPF files from scratch without the file needing to already exist.
For XML values not directly controlled by Musify, users can use the 'default_xml' class attribute
to control the initial default values applied in this scenario.

Changed
-------
Expand All @@ -62,6 +67,10 @@ Changed
+ prioritises fields settings over shuffle settings
* :py:meth:`.Comparer._in_range` now uses inclusive range i.e. ``a <= x <= b`` where ``x`` is the value to compare
and ``a`` and ``b`` are the limits. Previously used exclusive range i.e. ``a < x < b``
* Removed ``from_xml`` and ``to_xml`` methods from all :py:class:`.MusicBeeProcessor` subclasses.
Moved this logic to :py:class:`.XMLPlaylistParser` as distinct 'get' methods for each processor type.
* Moved loading of XML file logic from :py:class:`.XAutoPF` to :py:class:`.XMLPlaylistParser`.
:py:class:`.XMLPlaylistParser` is now solely responsible for all XML parsing and handling for XAutoPF files

Fixed
-----
Expand Down
7 changes: 4 additions & 3 deletions musify/core/printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ class PrettyPrinter(ABC):

@staticmethod
def _pascal_to_snake(value: str) -> str:
"""Convert snake_case to CamelCase."""
"""Convert PascalCase to snake_case."""
value = re.sub(r"([A-Z])", lambda m: f"_{m.group(1).lower()}", value.strip("_ "))
value = re.sub(r"[_ ]+", "_", value).strip("_ ")
return value.lower()

@staticmethod
def _snake_to_pascal(value: str) -> str:
"""Convert snake_case to CamelCase."""
return re.sub(r"_(.)", lambda m: m.group(1).upper(), value.strip())
"""Convert snake_case to PascalCase."""
value = re.sub(r"_(.)", lambda m: m.group(1).upper(), value.strip().lower())
return re.sub(r"^(.)", lambda m: m.group(1).upper(), value.strip())

@abstractmethod
def as_dict(self) -> dict[str, Any]:
Expand Down
2 changes: 1 addition & 1 deletion musify/file/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def save(self, dry_run: bool = True, *args, **kwargs) -> Any:
"""
Save this object to file.
:param dry_run: Run function, but do not modify file at all.
:param dry_run: Run function, but do not modify the file on the disk.
"""
raise NotImplementedError

Expand Down
2 changes: 1 addition & 1 deletion musify/libraries/local/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def save_tracks(
:param tags: Tags to be updated.
:param replace: Destructively replace tags in each file.
:param dry_run: Run function, but do not modify file at all.
:param dry_run: Run function, but do not modify the file on the disk.
:return: A map of the :py:class:`LocalTrack` saved to its result as a :py:class:`SyncResultTrack` object
"""
bar = self.logger.get_progress_bar(iterable=self.tracks, desc="Updating tracks", unit="tracks")
Expand Down
7 changes: 5 additions & 2 deletions musify/libraries/local/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ def __init__(self, message: str | None = None, kind: str | None = None):
super().__init__(formatted)


class LocalProcessorError(LocalError):
"""Exception raised for errors related to track processors."""
###########################################################################
## Track errors
###########################################################################
class TagError(LocalError):
"""Exception raised for errors related to track tag errors."""


###########################################################################
Expand Down
2 changes: 1 addition & 1 deletion musify/libraries/local/library/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ def save_playlists(self, dry_run: bool = True) -> dict[str, Result]:
"""
For each Playlist in this Library, saves its associate tracks and its settings (if applicable) to file.
:param dry_run: Run function, but do not modify file at all.
:param dry_run: Run function, but do not modify the file on the disk.
:return: A map of the playlist name to the results of its sync as a :py:class:`Result` object.
"""
return {name: pl.save(dry_run=dry_run) for name, pl in self.playlists.items()}
Expand Down
4 changes: 2 additions & 2 deletions musify/libraries/local/library/musicbee.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def save(self, dry_run: bool = True, *_, **__) -> dict[str, Any]:
"""
Generate and save the XML library file for this MusicBee library.
:param dry_run: Run function, but do not modify file at all.
:param dry_run: Run function, but do not modify the file on the disk.
:return: Map representation of the saved XML file.
"""
self.logger.debug(f"Save {self.name} library file: START")
Expand Down Expand Up @@ -509,7 +509,7 @@ def unparse(self, data: Mapping[str, Any], dry_run: bool = True) -> None:
Un-parse a map of XML data to XML and save to file.
:param data: Map of XML data to export.
:param dry_run: Run function, but do not modify file at all.
:param dry_run: Run function, but do not modify the file on the disk.
"""
et: etree._ElementTree = etree.parse(self.path)
parsed: dict[str, Any] = xmltodict.parse(etree.tostring(et.getroot(), encoding='utf-8', method='xml'))
Expand Down
2 changes: 1 addition & 1 deletion musify/libraries/local/playlist/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def save(self, dry_run: bool = True, *args, **kwargs) -> Result:
"""
Write the tracks in this Playlist and its settings (if applicable) to file.
:param dry_run: Run function, but do not modify file at all.
:param dry_run: Run function, but do not modify the file on the disk.
:return: :py:class:`Result` object with stats on the changes to the playlist.
"""
raise NotImplementedError
Expand Down
2 changes: 1 addition & 1 deletion musify/libraries/local/playlist/m3u.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def save(self, dry_run: bool = True, *_, **__) -> SyncResultM3U:
"""
Write the tracks in this Playlist and its settings (if applicable) to file.
:param dry_run: Run function, but do not modify file at all.
:param dry_run: Run function, but do not modify the file on the disk.
:return: The results of the sync as a :py:class:`SyncResultM3U` object.
"""
start_paths = {path.casefold() for path in self.path_mapper.unmap_many(self._original, check_existence=False)}
Expand Down
Loading

0 comments on commit 2ea38ce

Please sign in to comment.