From d7fe8bb778f5a4ea6fb3733f95c13fcd2deb1efd Mon Sep 17 00:00:00 2001 From: Josef Friedrich Date: Tue, 23 Jan 2024 16:43:59 +0100 Subject: [PATCH] Extend the fields class --- mscxyz/fields.py | 203 ++++++++++++++++++++++++++++++++++++++++--- mscxyz/score.py | 26 +++--- tests/test_fields.py | 68 +++++++++++++-- 3 files changed, 265 insertions(+), 32 deletions(-) diff --git a/mscxyz/fields.py b/mscxyz/fields.py index 57588c5..5aa1000 100644 --- a/mscxyz/fields.py +++ b/mscxyz/fields.py @@ -6,12 +6,16 @@ import typing from dataclasses import dataclass from pathlib import Path -from typing import Any +from typing import Any, Union if typing.TYPE_CHECKING: from mscxyz.score import Score +FieldValue = Union[str, int, float] +FieldsExport = dict[str, FieldValue] + + @dataclass class Field: name: str @@ -32,14 +36,180 @@ class FieldsManager: score: "Score" fields = ( + # Combined Field(name="title", description="The combined title", attr_path="meta.title"), - Field(name="abspath", description="", attr_path="abspath"), - Field(name="basename", description="", attr_path="basename"), - Field(name="dirname", description="", attr_path="dirname"), - Field(name="extension", description="", attr_path="extension"), - Field(name="filename", description="", attr_path="filename"), - Field(name="relpath", description="", attr_path="relpath"), - Field(name="relpath_backup", description="", attr_path="relpath_backup"), + Field( + name="subtitle", + description="The combined subtitle", + attr_path="meta.subtitle", + ), + Field( + name="composer", + description="The combined composer", + attr_path="meta.composer", + ), + Field( + name="lyricist", + description="The combined lyricist", + attr_path="meta.lyricist", + ), + # vbox + Field( + name="vbox_title", + description="The title field of the score as it appears in the center of the first vertical frame (VBox).", + attr_path="meta.vbox.title", + ), + Field( + name="vbox_subtitle", + description="The subtitle field of the score as it appears in the center of the first vertical frame (VBox).", + attr_path="meta.vbox.subtitle", + ), + Field( + name="vbox_composer", + description="The composer field of the score as it appears in the center of the first vertical frame (VBox).", + attr_path="meta.vbox.composer", + ), + Field( + name="vbox_lyricist", + description="The lyricist field of the score as it appears in the center of the first vertical frame (VBox).", + attr_path="meta.vbox.lyricist", + ), + # metatag + Field( + name="metatag_arranger", + description="The arranger field stored as project properties.", + attr_path="meta.metatag.arranger", + ), + Field( + name="metatag_audio_com_url", + description="The audio.com URL field stored as project properties.", + attr_path="meta.metatag.audio_com_url", + ), + Field( + name="metatag_composer", + description="The composer field stored as project properties.", + attr_path="meta.metatag.composer", + ), + Field( + name="metatag_copyright", + description="The copyright field stored as project properties.", + attr_path="meta.metatag.copyright", + ), + Field( + name="metatag_creation_date", + description="The creation date field stored as project properties.", + attr_path="meta.metatag.creation_date", + ), + Field( + name="metatag_lyricist", + description="The lyricist field stored as project properties.", + attr_path="meta.metatag.lyricist", + ), + Field( + name="metatag_movement_number", + description="The movement number field stored as project properties.", + attr_path="meta.metatag.movement_number", + ), + Field( + name="metatag_movement_title", + description="The movement title field stored as project properties.", + attr_path="meta.metatag.movement_title", + ), + Field( + name="metatag_msc_version", + description="The MuseScore version field stored as project properties.", + attr_path="meta.metatag.msc_version", + ), + Field( + name="metatag_platform", + description="The platform field stored as project properties.", + attr_path="meta.metatag.platform", + ), + Field( + name="metatag_poet", + description="The poet field stored as project properties.", + attr_path="meta.metatag.poet", + ), + Field( + name="metatag_source", + description="The source field stored as project properties.", + attr_path="meta.metatag.source", + ), + Field( + name="metatag_source_revision_id", + description="The source revision ID field stored as project properties.", + attr_path="meta.metatag.source_revision_id", + ), + Field( + name="metatag_subtitle", + description="The subtitle field stored as project properties.", + attr_path="meta.metatag.subtitle", + ), + Field( + name="metatag_translator", + description="The translator field stored as project properties.", + attr_path="meta.metatag.translator", + ), + Field( + name="metatag_work_number", + description="The work number field stored as project properties.", + attr_path="meta.metatag.work_number", + ), + Field( + name="metatag_work_title", + description="The work title field stored as project properties.", + attr_path="meta.metatag.work_title", + ), + # Readonly + Field( + name="version", + description="The MuseScore version as a floating point number, " + "for example ``2.03``, ``3.01`` or ``4.20``.", + attr_path="version", + ), + Field( + name="version_major", + description="The major MuseScore version, for example ``2``, ``3`` or ``4``.", + attr_path="version_major", + ), + Field( + name="path", + description="The absolute path of the MuseScore file, for example ``/home/xyz/score.mscz``.", + attr_path="path", + ), + Field( + name="backup_file", + description="The absolute path of the backup file. " + "The string ``_bak`` is appended to the file name before the extension.", + attr_path="backup_file", + ), + Field( + name="json_file", + description="The absolute path of the JSON file in which the metadata can be exported.", + attr_path="json_file", + ), + Field( + name="dirname", + description="The name of the containing directory of the MuseScore file, for " + "example: ``/home/xyz/score_files``.", + attr_path="dirname", + ), + Field( + name="filename", + description="The filename of the MuseScore file, for example:" + "``score.mscz``.", + attr_path="filename", + ), + Field( + name="basename", + description="The basename of the score file, for example: ``score``.", + attr_path="basename", + ), + Field( + name="extension", + description="The extension (``mscx`` or ``mscz``) of the score file.", + attr_path="extension", + ), ) __fields_by_name: dict[str, Field] @@ -113,19 +283,24 @@ def show(self, pre: dict[str, str], post: dict[str, str]) -> None: # print("{}: {}".format(utils.color(field, field_color), " ".join(line))) + def export_to_dict(self) -> FieldsExport: + output: FieldsExport = {} + for field in self.names: + value = self.get(field) + if value is not None: + if isinstance(value, Path): + value = str(value) + output[field] = value + return output + def export_json(self) -> Path: """ Export the data as a JSON file. :return: The path to the exported JSON file. """ - data: dict[str, str] = {} result_path: Path = self.score.json_file - # for field in self.interface.fields: - # data[field] = self.interface.__getattr__(field) output = open(result_path, "w") - json.dump(data, output, indent=4) + json.dump(self.export_to_dict(), output, indent=4) output.close() return result_path - - pass diff --git a/mscxyz/score.py b/mscxyz/score.py index fe482aa..ca7bd07 100644 --- a/mscxyz/score.py +++ b/mscxyz/score.py @@ -29,7 +29,7 @@ class Score: """ path: Path - """The absolute path of the input file. + """The absolute path of the MuseScore file, for example ``/home/xyz/score.mscz``. """ xml_file: str @@ -46,7 +46,7 @@ class Score: xml: XmlManipulator version: float - """The MuseScore version, for example 2.03 or 3.01""" + """The MuseScore version as a floating point number, for example ``2.03``, ``3.01`` or ``4.20``.""" zip_container: Optional[utils.ZipContainer] = None @@ -84,17 +84,18 @@ def xml_string(self) -> str: @property def version_major(self) -> int: - """The major MuseScore version, for example 2 or 3""" + """The major MuseScore version, for example ``2``, ``3`` or ``4``""" return int(self.version) @property def backup_file(self) -> Path: - """The path of the backup file.""" + """The absolute path of the backup file. + The string ``_bak`` is appended to the file name before the extension.""" return self.change_path(suffix="bak") @property def json_file(self) -> Path: - """The path of the JSON file in which the metadata is saved.""" + """The absolute path of the JSON file in which the metadata can be exported.""" return self.change_path(extension="json") @property @@ -106,20 +107,19 @@ def dirname(self) -> str: @property def filename(self) -> str: """The filename of the MuseScore file, for example: - ``simple.mscx``.""" + ``score.mscz``.""" return self.path.name - @property - def extension(self) -> str: - """The extension (``mscx`` or ``mscz``) of the score file, for - example: ``mscx``.""" - return self.filename.split(".")[-1].lower() - @property def basename(self) -> str: - """The basename of the score file, for example: ``simple``.""" + """The basename of the score file, for example: ``score``.""" return self.filename.replace("." + self.extension, "") + @property + def extension(self) -> str: + """The extension (``mscx`` or ``mscz``) of the score file.""" + return self.filename.split(".")[-1].lower() + def change_path( self, suffix: Optional[Any] = None, extension: Optional[str] = None ) -> Path: diff --git a/tests/test_fields.py b/tests/test_fields.py index 3ed6f31..880d36f 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -2,6 +2,9 @@ from __future__ import annotations +import os +import re + from mscxyz.fields import FieldsManager @@ -9,14 +12,69 @@ class TestClassFieldsManager: def test_property_names(self, fields: FieldsManager) -> None: assert fields.names == ( "title", - "abspath", - "basename", + "subtitle", + "composer", + "lyricist", + "vbox_title", + "vbox_subtitle", + "vbox_composer", + "vbox_lyricist", + "metatag_arranger", + "metatag_audio_com_url", + "metatag_composer", + "metatag_copyright", + "metatag_creation_date", + "metatag_lyricist", + "metatag_movement_number", + "metatag_movement_title", + "metatag_msc_version", + "metatag_platform", + "metatag_poet", + "metatag_source", + "metatag_source_revision_id", + "metatag_subtitle", + "metatag_translator", + "metatag_work_number", + "metatag_work_title", + "version", + "version_major", + "path", + "backup_file", + "json_file", "dirname", - "extension", "filename", - "relpath", - "relpath_backup", + "basename", + "extension", ) + def test_method_export_to_dict(self, fields: FieldsManager) -> None: + result = fields.export_to_dict() + for key in result: + value = result[key] + if isinstance(value, str) and value.startswith(os.path.sep): + value = re.sub(f"^{os.path.sep}.*{os.path.sep}", "/../..", value) + result[key] = value + if "dirname" in result: + result["dirname"] = "dir" + assert result == { + "title": "Title", + "composer": "Composer", + "vbox_title": "Title", + "vbox_composer": "Composer", + "metatag_composer": "Composer", + "metatag_msc_version": "4.20", + "metatag_platform": "Linux", + "metatag_work_title": "Title", + "version": 4.20, + "version_major": 4, + "path": "/../..score.mscz", + "backup_file": "/../..score_bak.mscz", + "json_file": "/../..score.json", + "dirname": "dir", + "basename": "score", + "extension": "mscz", + "filename": "score.mscz", + } + def test_method_get(self, fields: FieldsManager) -> None: assert fields.get("title") == "Title"