From 05feaf7d2d9088c5658e387a2df0b4564668a054 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 9 Feb 2025 15:39:48 +0100 Subject: [PATCH] Move comment processing to a separate file --- novelwriter/core/index.py | 46 +---------- novelwriter/formats/tokenizer.py | 2 +- novelwriter/gui/dochighlight.py | 2 +- novelwriter/text/comments.py | 68 ++++++++++++++++ tests/test_core/test_core_index.py | 86 +------------------- tests/test_text/test_text_comments.py | 110 ++++++++++++++++++++++++++ 6 files changed, 182 insertions(+), 132 deletions(-) create mode 100644 novelwriter/text/comments.py create mode 100644 tests/test_text/test_text_comments.py diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index dc4914e3..d8e8bfb2 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -43,6 +43,7 @@ from novelwriter.common import ( from novelwriter.constants import nwFiles, nwKeyWords, nwStyles from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType from novelwriter.error import logException +from novelwriter.text.comments import processComment from novelwriter.text.counting import standardCounter if TYPE_CHECKING: # pragma: no cover @@ -1343,48 +1344,3 @@ class IndexHeading: else: raise ValueError("The itemIndex contains an invalid reference type") return - - -# Text Processing Functions -# ========================= - -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 -} - - -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, key and text. Should only be called on - text starting with a %. - """ - 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/novelwriter/formats/tokenizer.py b/novelwriter/formats/tokenizer.py index cd390f16..49ce6cbf 100644 --- a/novelwriter/formats/tokenizer.py +++ b/novelwriter/formats/tokenizer.py @@ -40,12 +40,12 @@ from novelwriter.constants import ( nwHeadFmt, nwKeyWords, nwLabels, nwShortcode, nwStats, nwStyles, nwUnicode, trConst ) -from novelwriter.core.index import processComment from novelwriter.core.project import NWProject from novelwriter.enum import nwComment, nwItemLayout from novelwriter.formats.shared import ( BlockFmt, BlockTyp, T_Block, T_Formats, T_Note, TextDocumentTheme, TextFmt ) +from novelwriter.text.comments import processComment from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser logger = logging.getLogger(__name__) diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py index 5ee1ea7e..0e378390 100644 --- a/novelwriter/gui/dochighlight.py +++ b/novelwriter/gui/dochighlight.py @@ -38,8 +38,8 @@ from PyQt6.QtGui import ( from novelwriter import CONFIG, SHARED from novelwriter.common import checkInt from novelwriter.constants import nwStyles, nwUnicode -from novelwriter.core.index import processComment from novelwriter.enum import nwComment +from novelwriter.text.comments import processComment from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser logger = logging.getLogger(__name__) diff --git a/novelwriter/text/comments.py b/novelwriter/text/comments.py new file mode 100644 index 00000000..d3e22454 --- /dev/null +++ b/novelwriter/text/comments.py @@ -0,0 +1,68 @@ +""" +novelWriter – Text Comments +=========================== + +File History: +Created: 2023-11-23 [2.2b1] +Moved: 2025-02-09 [2.7b1] + +This file is a part of novelWriter +Copyright (C) 2025 Veronica Berglyd Olsen and novelWriter contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +from __future__ import annotations + +from novelwriter.enum import nwComment + +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 +} + + +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, key and text. Should only be called on + text starting with a %. + """ + 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 a95fb021..baa1870e 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -28,7 +28,7 @@ import pytest from novelwriter import SHARED from novelwriter.constants import nwFiles -from novelwriter.core.index import IndexItem, NWIndex, TagsIndex, _checkModKey, processComment +from novelwriter.core.index import IndexItem, NWIndex, TagsIndex from novelwriter.core.item import NWItem from novelwriter.core.project import NWProject from novelwriter.enum import nwComment, nwItemClass, nwItemLayout @@ -1419,87 +1419,3 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): "notes": {"footnotes": ["fkey"], "comments": ["ckey"]}, } }) - - -@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 - - -@pytest.mark.core -def testCoreIndex_processComment(): - """Test the comment processing function.""" - # 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) - - # 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.a: Hi") == (nwComment.PLAIN, "", "short.a: Hi", 0, 0) - - # 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.a: Hi") == (nwComment.NOTE, "a", "Hi", 6, 8) - - # 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", 7, 13) - assert processComment("% note. term : Hi") == (nwComment.PLAIN, "", "note. term : Hi", 0, 0) - assert processComment("% note . term : Hi") == (nwComment.PLAIN, "", "note . term : Hi", 0, 0) diff --git a/tests/test_text/test_text_comments.py b/tests/test_text/test_text_comments.py new file mode 100644 index 00000000..6161fb46 --- /dev/null +++ b/tests/test_text/test_text_comments.py @@ -0,0 +1,110 @@ +""" +novelWriter – NWIndex Class Tester +================================== + +This file is a part of novelWriter +Copyright (C) 2020 Veronica Berglyd Olsen and novelWriter contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +""" +from __future__ import annotations + +import pytest + +from novelwriter.enum import nwComment +from novelwriter.text.comments import _checkModKey, processComment + + +@pytest.mark.core +def testTextComments_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 + + +@pytest.mark.core +def testTextComments_processComment(): + """Test the comment processing function.""" + # 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) + + # 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.a: Hi") == (nwComment.PLAIN, "", "short.a: Hi", 0, 0) + + # 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.a: Hi") == (nwComment.NOTE, "a", "Hi", 6, 8) + + # 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", 7, 13) + assert processComment("% note. term : Hi") == (nwComment.PLAIN, "", "note. term : Hi", 0, 0) + assert processComment("% note . term : Hi") == (nwComment.PLAIN, "", "note . term : Hi", 0, 0)