From 290c844e87461f7ade642af17a54f512679fb3d0 Mon Sep 17 00:00:00 2001 From: Josef Friedrich Date: Fri, 26 Jan 2024 21:11:57 +0100 Subject: [PATCH] Refactor --clean --- mscxyz/cli.py | 7 +-- mscxyz/fields.py | 28 ++++++++++- mscxyz/meta.py | 38 ++++---------- tests/helper.py | 6 +++ tests/test_meta.py | 120 ++++++++++++++++----------------------------- 5 files changed, 85 insertions(+), 114 deletions(-) diff --git a/mscxyz/cli.py b/mscxyz/cli.py index 52eb2c0..3e06fbe 100644 --- a/mscxyz/cli.py +++ b/mscxyz/cli.py @@ -815,13 +815,8 @@ def list_styles(version: int) -> None: for a in args.meta_set: score.meta.set_field(destination_field=a[0], format_string=a[1]) - # elif args.subcommand == "meta": - # score = Score(file) - # if __no_error(lxml.etree.XMLSyntaxError, score.errors): - # pre: dict[str, str] = score.meta.interface.export_to_dict() - if args.meta_clean: - score.meta.clean_metadata(fields_spec=args.meta_clean) + score.fields.clean(args.meta_clean) if args.meta_json: score.meta.export_json() diff --git a/mscxyz/fields.py b/mscxyz/fields.py index 2f47249..41965ae 100644 --- a/mscxyz/fields.py +++ b/mscxyz/fields.py @@ -7,7 +7,7 @@ import typing from dataclasses import dataclass from pathlib import Path -from typing import Any, Mapping, Union +from typing import Any, Iterator, Mapping, Sequence, Union from mscxyz.meta import FormatStringNoFieldError, UnmatchedFormatStringError from mscxyz.settings import DefaultArguments @@ -42,6 +42,8 @@ class Field: color: Color = "white" """The color of the field in the debug output.""" + readonly: bool = False + class FieldsManager: score: "Score" @@ -202,18 +204,21 @@ class FieldsManager: "for example ``2.03``, ``3.01`` or ``4.20``.", attr_path="version", verbosity=2, + readonly=True, ), Field( name="version_major", description="The major MuseScore version, for example ``2``, ``3`` or ``4``.", attr_path="version_major", verbosity=2, + readonly=True, ), Field( name="path", description="The absolute path of the MuseScore file, for example ``/home/xyz/score.mscz``.", attr_path="path", verbosity=2, + readonly=True, ), Field( name="backup_file", @@ -221,12 +226,14 @@ class FieldsManager: "The string ``_bak`` is appended to the file name before the extension.", attr_path="backup_file", verbosity=3, + readonly=True, ), Field( name="json_file", description="The absolute path of the JSON file in which the metadata can be exported.", attr_path="json_file", verbosity=3, + readonly=True, ), Field( name="dirname", @@ -234,6 +241,7 @@ class FieldsManager: "example: ``/home/xyz/score_files``.", attr_path="dirname", verbosity=2, + readonly=True, ), Field( name="filename", @@ -241,18 +249,21 @@ class FieldsManager: "``score.mscz``.", attr_path="filename", verbosity=2, + readonly=True, ), Field( name="basename", description="The basename of the score file, for example: ``score``.", attr_path="basename", verbosity=2, + readonly=True, ), Field( name="extension", description="The extension (``mscx`` or ``mscz``) of the score file.", attr_path="extension", verbosity=2, + readonly=True, ), ) @@ -270,6 +281,9 @@ def __init__(self, score: "Score") -> None: self.__fields_by_name[field.name] = field self.pre = self.export_to_dict() + def __iter__(self) -> Iterator[Field]: + return iter(self.fields) + @property def names(self) -> tuple[str, ...]: return tuple(self.__fields_by_name.keys()) @@ -361,6 +375,18 @@ def distribute(self, source_fields: str, format_string: str) -> None: self.set(field, value) return + def clean(self, fields_spec: str) -> None: + fields: Sequence[str] + if fields_spec == "all": + fields = self.names + else: + fields = fields_spec.split(",") + + for name in fields: + field = self.get_field(name) + if not field.readonly: + self.set(name, None) + def export_json(self) -> Path: """ Export the data as a JSON file. diff --git a/mscxyz/meta.py b/mscxyz/meta.py index 45ecc1d..b2e17e0 100644 --- a/mscxyz/meta.py +++ b/mscxyz/meta.py @@ -171,8 +171,6 @@ def __get_text(self, field: str) -> str | None: return self.score.xml.get_text(element) def __set_text(self, field: str, value: str | None) -> None: - if value is None: - return None element: _Element = self.__get_element(field) element.text = value @@ -414,19 +412,8 @@ def work_title(self, value: str | None) -> None: self.__set_text("workTitle", value) def clean(self) -> None: - fields = ( - "arranger", - "copyright", - "creationDate", - "movementNumber", - "platform", - "poet", - "source", - "translator", - "workNumber", - ) - for field in fields: - setattr(self, field, "") + for field in self.fields: + setattr(self, field, None) class Vbox: @@ -642,6 +629,10 @@ def title(self) -> str | None: def title(self, value: str | None) -> None: self.__set_text("Title", value) + def clean(self) -> None: + for field in self.fields: + setattr(self, field, None) + class Combined: """Combines the metadata fields of the embedded ``metaTag`` and ``VBox`` elements.""" @@ -878,21 +869,12 @@ def set_field(self, destination_field: str, format_string: str) -> None: field_value = tmep.parse(format_string, self.interface.export_to_dict()) setattr(self.interface, destination_field, field_value) - def clean_metadata(self, fields_spec: typing.Literal["all"] | str) -> None: + def clean(self) -> None: """ - Clean the metadata fields of the object. - - :param fields_spec: Specification of the fields to clean. It can be either - ``all`` to clean all fields, or a comma-separated string specifying - individual fields. + Clean all metadata fields of the object. """ - fields: list[str] - if fields_spec == "all": - fields = self.interface_read_write.fields - else: - fields = fields_spec.split(",") - for field in fields: - setattr(self.interface_read_write, field, "") + self.metatag.clean() + self.vbox.clean() def delete_duplicates(self) -> None: """ diff --git a/tests/helper.py b/tests/helper.py index 194b692..75b6f76 100644 --- a/tests/helper.py +++ b/tests/helper.py @@ -210,6 +210,12 @@ def execute(self) -> Cli: self.__execute() return self + def fields(self) -> FieldsManager: + self.__execute() + if self.__score is None: + raise Exception("No score object") + return self.__score.fields + def score(self) -> Score: self.__execute() if self.__score is None: diff --git a/tests/test_meta.py b/tests/test_meta.py index e089db7..5c450d4 100644 --- a/tests/test_meta.py +++ b/tests/test_meta.py @@ -7,8 +7,6 @@ import pytest -import mscxyz -import mscxyz.meta from mscxyz import meta, supported_versions, utils from mscxyz.meta import ( Interface, @@ -256,64 +254,6 @@ def test_field_readonly_relpath_backup(self) -> None: ) -class TestClassInterface: - def setup_method(self) -> None: - self.fields: list[str] = [ - "combined_composer", - "combined_lyricist", - "combined_subtitle", - "combined_title", - "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", - "readonly_abspath", - "readonly_basename", - "readonly_dirname", - "readonly_extension", - "readonly_filename", - "readonly_relpath", - "readonly_relpath_backup", - "vbox_composer", - "vbox_lyricist", - "vbox_subtitle", - "vbox_title", - ] - - self.tmp: str = helper.get_file("meta-all-values.mscx") - self.xml_tree = Score(self.tmp) - self.interface = Interface(self.xml_tree) - - def test_static_method_get_all_fields(self) -> None: - assert Interface.get_all_fields() == self.fields - - @pytest.mark.skip(reason="Test needs to be rewritten") - def test_get(self) -> None: - for field in self.fields: - assert getattr(self.interface, field), field - - def test_set(self) -> None: - self.interface.vbox_title = "lol" - assert self.interface.vbox_title == "lol" - - def test_exception(self) -> None: - with pytest.raises(mscxyz.meta.ReadOnlyFieldError): - self.interface.readonly_extension = "lol" - - def get_meta_tag(filename: str, version: int) -> Metatag: score = helper.get_score(filename, version) return score.meta.metatag @@ -351,7 +291,7 @@ def test_clean(self, version: int) -> None: m.arranger = "A" assert m.arranger == "A" m.clean() - assert m.arranger == "" + assert m.arranger is None def get_vbox(filename: str, version: int) -> Vbox: @@ -457,31 +397,29 @@ def test_distribute_field_exception_unmatched(self) -> None: class TestOptionClean: def test_clean_all(self) -> None: - c = Cli("--clean-meta", "all").append_score("meta-all-values.mscz").execute() - i = c.post.meta.interface_read_write - for field in i.fields: - assert getattr(i, field) is None, field + f = Cli("--clean-meta", "all").append_score("meta-all-values.mscz").fields() + for field in f: + if not field.readonly: + assert f.get(field.name) is None, field def test_clean_single_field(self) -> None: - c = ( + f = ( Cli("--clean-meta", "vbox_title") .append_score("meta-all-values.mscz") .execute() - ) - i = c.post.meta.interface_read_write - assert i.vbox_title is None, "vbox_title" - assert i.vbox_composer == "vbox_composer", "vbox_composer" + ).fields() + assert f.get("vbox_title") is None, "vbox_title" + assert f.get("vbox_composer") == "vbox_composer", "vbox_composer" def test_clean_some_fields(self) -> None: - c = ( + f = ( Cli("--clean-meta", "vbox_title,vbox_composer") .append_score("meta-all-values.mscz") .execute() - ) - i = c.post.meta.interface_read_write - assert i.vbox_title is None, "vbox_title" - assert i.vbox_composer is None, "vbox_composer" - assert i.vbox_subtitle == "vbox_subtitle", "vbox_subtitle" + ).fields() + assert f.get("vbox_title") is None, "vbox_title" + assert f.get("vbox_composer") is None, "vbox_composer" + assert f.get("vbox_subtitle") == "vbox_subtitle", "vbox_subtitle" class TestStdout: @@ -817,9 +755,33 @@ def setup_method(self) -> None: self.meta: Meta = helper.get_meta("meta-all-values.mscx") def test_method_clean_metadata(self) -> None: - self.meta.interface.combined_lyricist = "test" - self.meta.clean_metadata("all") - assert self.meta.interface.combined_lyricist is None + self.meta.vbox.lyricist = "test" + self.meta.clean() + assert self.meta.vbox.title is None + assert self.meta.vbox.subtitle is None + assert self.meta.vbox.composer is None + assert self.meta.vbox.lyricist is None + assert self.meta.metatag.arranger is None + assert self.meta.metatag.audio_com_url is None + assert self.meta.metatag.composer is None + assert self.meta.metatag.copyright is None + assert self.meta.metatag.creation_date is None + assert self.meta.metatag.lyricist is None + assert self.meta.metatag.movement_number is None + assert self.meta.metatag.movement_title is None + assert self.meta.metatag.msc_version is None + assert self.meta.metatag.platform is None + assert self.meta.metatag.poet is None + assert self.meta.metatag.source is None + assert self.meta.metatag.source_revision_id is None + assert self.meta.metatag.subtitle is None + assert self.meta.metatag.translator is None + assert self.meta.metatag.work_number is None + assert self.meta.metatag.work_title is None + assert self.meta.title is None + assert self.meta.subtitle is None + assert self.meta.composer is None + assert self.meta.lyricist is None def test_method_delete_duplicates(self) -> None: self.meta.interface.combined_lyricist = "test"