From 261582661b858e826ce2709b93d47d8ddbd0edba Mon Sep 17 00:00:00 2001 From: bensteUEM Date: Sat, 1 Jun 2024 22:14:12 +0200 Subject: [PATCH] implement validate_language_marker and validate_language_marker_psalm - part of #61 --- SngFileLanguagePart.py | 83 ++++++++++++-- .../709 Herr, sei nicht ferne.sng | 8 +- testData/Test/sample_languages.sng | 23 +++- tests/test_sng_language.py | 104 +++++++++++++++++- 4 files changed, 205 insertions(+), 13 deletions(-) diff --git a/SngFileLanguagePart.py b/SngFileLanguagePart.py index 7ea6148..7d7561c 100644 --- a/SngFileLanguagePart.py +++ b/SngFileLanguagePart.py @@ -1,6 +1,7 @@ """This file is used to define SngFile class and somee helper methods related to it's usage.""" import abc +import itertools import logging logger = logging.getLogger(__name__) @@ -153,15 +154,83 @@ def validate_header_language_count(self, fix: bool = False) -> bool: def validate_language_marker(self, fix: bool = False) -> bool: """Validate the language markers used in content. + checks that the number of langauges in content matches the configuration from header + Args: - fix: attempt to fix language marker usage Defaults to False. + fix: attempt to fix language marker usage. Defaults to False. + * in case there are no language markers and the expected number is greater ##1 and ##2 will be added alternatly + * in case some lines do have language markers and others don't the former will apply, skipping lines that already have a language marker + * this method MUSTN'T be applied on Psalms because they can have different language orders blocks longer than one line Returns: if language markers match LangCount header """ - if fix: - pass - # TODO@benste: Implement - # https://github.com/bensteUEM/SongBeamerQS/issues/61 - not_implemented_link = "https://github.com/bensteUEM/SongBeamerQS/issues/61" - raise NotImplementedError(not_implemented_link) + if self.is_psalm(): + return self.validate_language_marker_psalm(fix=fix) + + languages_expected = int(self.header["LangCount"]) + + # if only single language + if len(self.get_content_unique_lang_markers()) == 1 == languages_expected: + return True + + language_markers_expected = [f"##{i+1} " for i in range(languages_expected)] + valid = True + for block in self.content.values(): + for slide in block[1:]: + valid &= self.validate_language_marker_slide( + slide, language_markers_expected=language_markers_expected, fix=fix + ) + return valid + + def validate_language_marker_slide( + self, slide: list[str], language_markers_expected: list, fix: bool = False + ) -> bool: + """More complex cases of validate_language_marker. + + Args: + slide: the slide (list of lines) to check + language_markers_expected: prepared language markers that should be used + fix: attempt to fix language marker usage. Defaults to False. + + Returns: + if language markers match LangCount header + """ + # reset iterator for each slide + language_markers_iterator = itertools.cycle(language_markers_expected) + for index, line in enumerate(slide): + # skip lines with existing language marker + if line.startswith("##"): + continue + # add next language marker from iterator + if fix: + slide[index] = next(language_markers_iterator) + line + self.update_editor_because_content_modified() + continue + return False + return True + + def validate_language_marker_psalm(self, fix: bool = False) -> bool: # noqa: C901 + """Method used for language_marker validation of psalms. + + Auto Fixing is not possible! + Psalms should have 2 or 3 langauges indicated by ##1, ##3 and ##4 but not ##2 + Arguments: + fix: if fix should be attempted - impossible for psalm! + Returns: + if language markers are as expected + """ + for block in self.content.values(): + for slide in block[1:]: + for line in slide: + if line.startswith(("##1", "##3", "##4")): + continue + if fix: + logger.warning( + "Can't fix '%s' line in (%s) because it's a Psalm", + line, + self.filename, + ) + return False + + return True diff --git a/testData/EG Psalmen & Sonstiges/709 Herr, sei nicht ferne.sng b/testData/EG Psalmen & Sonstiges/709 Herr, sei nicht ferne.sng index bfd6f3c..2946743 100644 --- a/testData/EG Psalmen & Sonstiges/709 Herr, sei nicht ferne.sng +++ b/testData/EG Psalmen & Sonstiges/709 Herr, sei nicht ferne.sng @@ -4,7 +4,7 @@ #Version=3 #FontSize=50 #BackgroundImage=Menschen\Verzweiflung-mann-r.jpg -#(c)=König David +#(c)=K�nig David #Bible=Psalm 22, 2 2-6 12.20 #Songbook=EG 709 - Psalm 22 I #ChurchSongID=EG 709 - Psalm 22 I @@ -21,9 +21,9 @@ Verse ##1 doch antwortest du nicht, ##3 und des Nachts, doch finde ich keine Ruhe. ##1 Du aber bist heilig, -##3 der du thronst über den Lobgesängen Israels. +##3 der du thronst �ber den Lobges�ngen Israels. --- -#1 Unsere Väter hofften auf dich; +1 Unsere V�ter hofften auf dich; ##3 und da sie hofften, halfst du ihnen heraus. ##1 Zu dir schrien sie und wurden errettet, ##3 sie hofften auf dich @@ -34,4 +34,4 @@ Verse ##3 denn es ist hier kein Helfer. --- ##1 Aber du, Herr, sei nicht ferne; -##3 meine Stärke, eile, mir zu helfen! +##3 meine St�rke, eile, mir zu helfen! diff --git a/testData/Test/sample_languages.sng b/testData/Test/sample_languages.sng index b39a6aa..88b3deb 100644 --- a/testData/Test/sample_languages.sng +++ b/testData/Test/sample_languages.sng @@ -34,4 +34,25 @@ Verse 2 ##1 2. Eine Zeile mit Sprachmarkierung ##2 2. One line with language marker ##1 deutscher text -##2 english text \ No newline at end of file +##2 english text +--- +Verse 3 +Lang 1.1 +Lang 2.1 +##1 Lang 1.2 +Lang 1.3 +Lang 2.2 +--- +Verse 4 +Lang 1.1 +Lang 2.1 +##2 Lang 2.2 +Lang 1.2 +--- +Verse 5 +##2 Lang 2.1 +##2 lang 2.2 +##2 lang 2.3 +Lang 1.1 +Lang 2.4 +Lang 1.2 \ No newline at end of file diff --git a/tests/test_sng_language.py b/tests/test_sng_language.py index 1296572..728d80b 100644 --- a/tests/test_sng_language.py +++ b/tests/test_sng_language.py @@ -1,3 +1,4 @@ +import logging from pathlib import Path import pytest @@ -66,7 +67,7 @@ def test_get_content(self, filename: Path, languages: list[str] | None) -> None: ( Path("testData/EG Psalmen & Sonstiges") / "709 Herr, sei nicht ferne.sng", - {"##1", "##3"}, + {"##1", "##3", None}, None, ), ], @@ -164,3 +165,104 @@ def test_validate_header_language_count( } result = sample_song.validate_header_language_count(fix=fix) assert expected_result == result + + @pytest.mark.parametrize( + ("filename", "verses", "fix", "expected_result"), + [ + (Path("testData/Test") / "sample_languages.sng", ["Verse 2"], False, True), + (Path("testData/Test") / "sample_languages.sng", ["Verse 3"], False, False), + (Path("testData/Test") / "sample_languages.sng", ["Verse 4"], True, True), + (Path("testData/Test") / "sample_languages.sng", ["Verse 5"], False, False), + ], + ) + def test_validate_language_marker( + self, filename: str, verses: list[str] | None, fix: bool, expected_result: bool + ) -> None: + """Checking different params with validate_language_marker. + + Args: + filename: the file to use + verses: the verse selection to use as content + fix: if fix attempt should be performed + expected_result: if should be valid after function run + """ + sample_song = SngFile(filename=filename) + sample_song.content = { + key: value for key, value in sample_song.content.items() if key in verses + } + result = sample_song.validate_language_marker(fix=fix) + assert expected_result == result + + # check language marker equals Lang x. text in line of verse 3-5 + if fix: + for block in sample_song.content.values(): + if block[1][2] in [3, 4, 5]: + continue + for slide in block[1:]: + self.helper_language_marker_as_expected(slide=slide) + + def helper_language_marker_as_expected(self, slide: list[str]) -> bool: + """Helper for test_validate_language_marker. + + Validates marker matches syntax for all lines + e.g. '##2 Lang 2.1' ma + + Args: + slide: one slide with all it's text lines + + Returns: + if everything is as expected + """ + for line in slide: + assert line.startswith("##") + text_items = line.split(" ") + marked_language = text_items[0][2:] + expected_language = text_items[2][:1] + assert marked_language == expected_language + + @pytest.mark.usefixtures("caplog") + def test_validate_language_marker_psalm( + self, caplog: pytest.LogCaptureFixture + ) -> None: + """Checking psalm cases of validate_language_marker. + + Psalms should not be fixable or already valid + complete sample should be invalid because of error + attempt to fix it should log an "unfixable" + looking at first 2 slides of verse only it should be valid + """ + filepath = Path("testData/EG Psalmen & Sonstiges") + filename = "709 Herr, sei nicht ferne.sng" + sample_song = SngFile(filename=filepath / filename, songbook_prefix="EG") + result = sample_song.validate_language_marker_psalm(fix=False) + assert not result + + caplog.set_level(logging.INFO) + result = sample_song.validate_language_marker_psalm(fix=True) + assert not result + assert len(caplog.records) > 0 + + # reduced scope should be valid + sample_song.content["Verse"] = sample_song.content["Verse"][0:2] + result = sample_song.validate_language_marker_psalm(fix=False) + assert result + + @pytest.mark.usefixtures("caplog") + def test_validate_langauge_marker_indirect_psalm( + self, caplog: pytest.LogCaptureFixture + ) -> None: + """Checks that running validate_langauge_marker results in psalm specific log message.""" + filepath = Path("testData/EG Psalmen & Sonstiges") + filename = "709 Herr, sei nicht ferne.sng" + sample_song = SngFile(filename=filepath / filename, songbook_prefix="EG") + + caplog.set_level(logging.INFO) + result = sample_song.validate_language_marker(fix=True) + assert not result + + problematic_line = "1 Unsere V�ter hofften auf dich;" + expected_log = "WARNING SngFileLanguagePart:SngFileLanguagePart.py:229 " + f"Can't fix '{problematic_line}' line" + f" in ({sample_song.filename}) because it's a Psalm\n" + + assert expected_log in caplog.text