Move comment processing to a separate file
This commit is contained in:
@@ -43,6 +43,7 @@ from novelwriter.common import (
|
|||||||
from novelwriter.constants import nwFiles, nwKeyWords, nwStyles
|
from novelwriter.constants import nwFiles, nwKeyWords, nwStyles
|
||||||
from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType
|
from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType
|
||||||
from novelwriter.error import logException
|
from novelwriter.error import logException
|
||||||
|
from novelwriter.text.comments import processComment
|
||||||
from novelwriter.text.counting import standardCounter
|
from novelwriter.text.counting import standardCounter
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
@@ -1343,48 +1344,3 @@ class IndexHeading:
|
|||||||
else:
|
else:
|
||||||
raise ValueError("The itemIndex contains an invalid reference type")
|
raise ValueError("The itemIndex contains an invalid reference type")
|
||||||
return
|
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
|
|
||||||
|
|||||||
@@ -40,12 +40,12 @@ from novelwriter.constants import (
|
|||||||
nwHeadFmt, nwKeyWords, nwLabels, nwShortcode, nwStats, nwStyles, nwUnicode,
|
nwHeadFmt, nwKeyWords, nwLabels, nwShortcode, nwStats, nwStyles, nwUnicode,
|
||||||
trConst
|
trConst
|
||||||
)
|
)
|
||||||
from novelwriter.core.index import processComment
|
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
from novelwriter.enum import nwComment, nwItemLayout
|
from novelwriter.enum import nwComment, nwItemLayout
|
||||||
from novelwriter.formats.shared import (
|
from novelwriter.formats.shared import (
|
||||||
BlockFmt, BlockTyp, T_Block, T_Formats, T_Note, TextDocumentTheme, TextFmt
|
BlockFmt, BlockTyp, T_Block, T_Formats, T_Note, TextDocumentTheme, TextFmt
|
||||||
)
|
)
|
||||||
|
from novelwriter.text.comments import processComment
|
||||||
from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser
|
from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ from PyQt6.QtGui import (
|
|||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.common import checkInt
|
from novelwriter.common import checkInt
|
||||||
from novelwriter.constants import nwStyles, nwUnicode
|
from novelwriter.constants import nwStyles, nwUnicode
|
||||||
from novelwriter.core.index import processComment
|
|
||||||
from novelwriter.enum import nwComment
|
from novelwriter.enum import nwComment
|
||||||
|
from novelwriter.text.comments import processComment
|
||||||
from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser
|
from novelwriter.text.patterns import REGEX_PATTERNS, DialogParser
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
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
|
||||||
@@ -28,7 +28,7 @@ import pytest
|
|||||||
|
|
||||||
from novelwriter import SHARED
|
from novelwriter import SHARED
|
||||||
from novelwriter.constants import nwFiles
|
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.item import NWItem
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
from novelwriter.enum import nwComment, nwItemClass, nwItemLayout
|
from novelwriter.enum import nwComment, nwItemClass, nwItemLayout
|
||||||
@@ -1419,87 +1419,3 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd):
|
|||||||
"notes": {"footnotes": ["fkey"], "comments": ["ckey"]},
|
"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)
|
|
||||||
|
|||||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
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)
|
||||||
Reference in New Issue
Block a user