From b7222248633fc97dd5e4231954830e072a088d45 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 26 Apr 2024 22:25:08 +0200 Subject: [PATCH] Improve comment processing --- novelwriter/core/index.py | 45 +++++++++++----- tests/test_core/test_core_index.py | 85 +++++++++++++++++++++++------- 2 files changed, 99 insertions(+), 31 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 98642537..a9449357 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -40,8 +40,8 @@ from novelwriter import SHARED from novelwriter.common import ( checkInt, isHandle, isItemClass, isListInstance, isTitleTag, jsonEncode ) -from novelwriter.constants import nwFiles, nwKeyWords, nwHeaders -from novelwriter.enum import nwComment, nwItemClass, nwItemType, nwItemLayout +from novelwriter.constants import nwFiles, nwHeaders, nwKeyWords +from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType from novelwriter.error import logException from novelwriter.text.counting import standardCounter @@ -1360,24 +1360,43 @@ class IndexHeading: # Text Processing Functions # =============================================================================================== # -CLASSIFIERS = { +MODIFIERS = { "synopsis": nwComment.SYNOPSIS, "short": nwComment.SHORT, "note": nwComment.NOTE, "footnote": nwComment.FOOTNOTE, } +KEY_REQ = { + "synopsis": 0, # Key not allowed + "short": 0, # Key not allowed + "note": 1, # Key optional + "footnote": 2, # Key required +} -TERMS = ["note", "footnote"] + +def _checkModKey(modifier: str, key: str) -> bool: + """Check if a modifier and key set are ok.""" + if modifier in MODIFIERS: + if key == "": + return KEY_REQ[modifier] < 2 + elif key.replace("_", "").isalnum(): + return KEY_REQ[modifier] > 0 + return False def processComment(text: str) -> tuple[nwComment, str, str, int, int]: - """Extract comment style and text. Should only be called on text - starting with a %. + """Extract comment style, key and text. Should only be called on + text starting with a %. """ - check = text[1:].lstrip() - classifier, _, content = check.partition(":") - classifier, _, term = classifier.partition(".") - if content and (clean := classifier.strip().lower()) in CLASSIFIERS: - term = "ERR" if term and clean not in TERMS else term.strip() - return CLASSIFIERS[clean], term, content.strip(), text.find(".") + 1, text.find(":") + 1 - return nwComment.IGNORE if text.startswith("%~") else nwComment.PLAIN, "", check, 0, 0 + if text[:2] == "%~": + return nwComment.IGNORE, "", text[2:].lstrip(), 0, 0 + + check = text[1:].strip() + start, _, content = check.partition(":") + modifier, _, key = start.rstrip().partition(".") + if content and (clean := modifier.lower()) and _checkModKey(clean, key): + col = text.find(":") + 1 + dot = text.find(".", 0, col) + 1 + return MODIFIERS[clean], key, content.lstrip(), dot, col + + return nwComment.PLAIN, "", check, 0, 0 diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index 4ce56138..09d7ff03 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -26,16 +26,16 @@ from shutil import copyfile import pytest -from mocked import causeException -from tools import C, buildTestProject, cmpFiles, writeFile - from novelwriter import SHARED from novelwriter.constants import nwFiles -from novelwriter.core.index import IndexItem, NWIndex, TagsIndex, processComment +from novelwriter.core.index import IndexItem, NWIndex, TagsIndex, _checkModKey, processComment from novelwriter.core.item import NWItem from novelwriter.core.project import NWProject from novelwriter.enum import nwComment, nwItemClass, nwItemLayout +from tests.mocked import causeException +from tests.tools import C, buildTestProject, cmpFiles, writeFile + @pytest.mark.core def testCoreIndex_LoadSave(qtbot, monkeypatch, prjLipsum, mockGUI, tstPaths): @@ -1319,39 +1319,88 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): # END Test testCoreIndex_ItemIndex +@pytest.mark.core +def testCoreIndex_checkModKey(): + """Test the _checkModKey function.""" + # Check Requirements + + # Synopsis + assert _checkModKey("synopsis", "") is True + assert _checkModKey("synopsis", "a") is False + + # Short + assert _checkModKey("short", "") is True + assert _checkModKey("short", "a") is False + + # Note + assert _checkModKey("note", "") is True + assert _checkModKey("note", "a") is True + + # Footnote + assert _checkModKey("footnote", "") is False + assert _checkModKey("footnote", "a") is True + + # Invalid + assert _checkModKey("stuff", "") is False + assert _checkModKey("stuff", "a") is False + + # Check Keys + assert _checkModKey("note", "a") is True + assert _checkModKey("note", "a1") is True + assert _checkModKey("note", "a1.2") is False + assert _checkModKey("note", "a1_2") is True + +# END Test testCoreIndex_checkModKey + + @pytest.mark.core def testCoreIndex_processComment(): """Test the comment processing function.""" - # Regular comment + # Plain assert processComment("%Hi") == (nwComment.PLAIN, "", "Hi", 0, 0) assert processComment("% Hi") == (nwComment.PLAIN, "", "Hi", 0, 0) assert processComment("% Hi:You") == (nwComment.PLAIN, "", "Hi:You", 0, 0) assert processComment("% Hi.You:There") == (nwComment.PLAIN, "", "Hi.You:There", 0, 0) - # Check Non-Term + # Ignore + assert processComment("%~Hi") == (nwComment.IGNORE, "", "Hi", 0, 0) + assert processComment("%~ Hi") == (nwComment.IGNORE, "", "Hi", 0, 0) + + # Invalid + assert processComment("") == (nwComment.PLAIN, "", "", 0, 0) + + # Short : Term not allowed assert processComment("%short: Hi") == (nwComment.SHORT, "", "Hi", 0, 7) - assert processComment("%short.term: Hi") == (nwComment.SHORT, "ERR", "Hi", 7, 12) + assert processComment("%short.a: Hi") == (nwComment.PLAIN, "", "short.a: Hi", 0, 0) - # Check Term + # Synopsis : Term not allowed + assert processComment("%synopsis: Hi") == (nwComment.SYNOPSIS, "", "Hi", 0, 10) + assert processComment("%synopsis.a: Hi") == (nwComment.PLAIN, "", "synopsis.a: Hi", 0, 0) + + # Note : Term optional assert processComment("%note: Hi") == (nwComment.NOTE, "", "Hi", 0, 6) - assert processComment("%note.term: Hi") == (nwComment.NOTE, "term", "Hi", 6, 11) + assert processComment("%note.a: Hi") == (nwComment.NOTE, "a", "Hi", 6, 8) - # Check Padding + # Footnote : Term required + assert processComment("%footnote: Hi") == (nwComment.PLAIN, "", "footnote: Hi", 0, 0) + assert processComment("%footnote.a: Hi") == (nwComment.FOOTNOTE, "a", "Hi", 10, 12) + + # Check Case + assert processComment("%Footnote.a: Hi") == (nwComment.FOOTNOTE, "a", "Hi", 10, 12) + assert processComment("%FOOTNOTE.A: Hi") == (nwComment.FOOTNOTE, "A", "Hi", 10, 12) + assert processComment("%FootNote.A_a: Hi") == (nwComment.FOOTNOTE, "A_a", "Hi", 10, 14) + + # Padding without term assert processComment("%short: Hi") == (nwComment.SHORT, "", "Hi", 0, 7) assert processComment("% short: Hi") == (nwComment.SHORT, "", "Hi", 0, 8) assert processComment("% short : Hi") == (nwComment.SHORT, "", "Hi", 0, 10) assert processComment("% short : Hi") == (nwComment.SHORT, "", "Hi", 0, 12) assert processComment("% \t short : Hi") == (nwComment.SHORT, "", "Hi", 0, 13) + # Padding with term assert processComment("%note.term: Hi") == (nwComment.NOTE, "term", "Hi", 6, 11) assert processComment("% note.term: Hi") == (nwComment.NOTE, "term", "Hi", 7, 12) - assert processComment("% note . term : Hi") == (nwComment.NOTE, "term", "Hi", 9, 16) - assert processComment("% note . term : Hi") == (nwComment.NOTE, "term", "Hi", 11, 20) - - # Check Classifiers - assert processComment("%short: Hi") == (nwComment.SHORT, "", "Hi", 0, 7) - assert processComment("%synopsis: Hi") == (nwComment.SYNOPSIS, "", "Hi", 0, 10) - assert processComment("%note.term: Hi") == (nwComment.NOTE, "term", "Hi", 6, 11) - assert processComment("%footnote.term: Hi") == (nwComment.FOOTNOTE, "term", "Hi", 10, 15) + assert processComment("% note. term : Hi") == (nwComment.PLAIN, "", "note. term : Hi", 0, 0) + assert processComment("% note . term : Hi") == (nwComment.PLAIN, "", "note . term : Hi", 0, 0) # END Test testCoreIndex_processComment