Add story structure to build settings (#2298)

This commit is contained in:
Veronica Berglyd Olsen
2025-04-17 16:39:45 +02:00
committed by GitHub
12 changed files with 74 additions and 59 deletions
+2
View File
@@ -78,6 +78,7 @@ SETTINGS_TEMPLATE: dict[str, tuple[type, T_BuildValue]] = {
"headings.breakScene": (bool, False),
"text.includeSynopsis": (bool, False),
"text.includeComments": (bool, False),
"text.includeStory": (bool, False),
"text.includeKeywords": (bool, False),
"text.includeBodyText": (bool, True),
"text.ignoredKeywords": (str, ""),
@@ -144,6 +145,7 @@ SETTINGS_LABELS = {
"text.grpContent": QT_TRANSLATE_NOOP("Builds", "Text Content"),
"text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"),
"text.includeComments": QT_TRANSLATE_NOOP("Builds", "Include Comments"),
"text.includeStory": QT_TRANSLATE_NOOP("Builds", "Include Story Structure"),
"text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Include Keywords"),
"text.includeBodyText": QT_TRANSLATE_NOOP("Builds", "Include Body Text"),
"text.ignoredKeywords": QT_TRANSLATE_NOOP("Builds", "Ignore These Keywords"),
+5 -3
View File
@@ -32,7 +32,7 @@ from PyQt6.QtGui import QFont
from novelwriter import CONFIG
from novelwriter.constants import nwLabels
from novelwriter.core.item import NWItem
from novelwriter.enum import nwBuildFmt
from novelwriter.enum import nwBuildFmt, nwComment
from novelwriter.error import formatException, logException
from novelwriter.formats.todocx import ToDocX
from novelwriter.formats.tohtml import ToHtml
@@ -311,10 +311,12 @@ class NWBuildDocument:
)
bldObj.setBodyText(self._build.getBool("text.includeBodyText"))
bldObj.setSynopsis(self._build.getBool("text.includeSynopsis"))
bldObj.setComments(self._build.getBool("text.includeComments"))
bldObj.setKeywords(self._build.getBool("text.includeKeywords"))
bldObj.setIgnoredKeywords(self._build.getStr("text.ignoredKeywords"))
bldObj.setCommentType(nwComment.PLAIN, self._build.getBool("text.includeComments"))
bldObj.setCommentType(nwComment.SYNOPSIS, self._build.getBool("text.includeSynopsis"))
bldObj.setCommentType(nwComment.SHORT, self._build.getBool("text.includeSynopsis"))
bldObj.setCommentType(nwComment.STORY, self._build.getBool("text.includeStory"))
if isinstance(bldObj, ToHtml):
bldObj.setStyles(self._build.getBool("html.addStyles"))
+13 -14
View File
@@ -121,14 +121,16 @@ class Tokenizer(ABC):
self._indentFirst = False # Indent first paragraph
self._doJustify = False # Justify text
self._doBodyText = True # Include body text
self._doSynopsis = False # Also process synopsis comments
self._doComments = False # Also process comments
self._doComments = set() # Comment styles to allow
self._doKeywords = False # Also process keywords like tags and references
self._keepBreaks = True # Keep line breaks in paragraphs
self._defaultAlign = "left" # The default text alignment
self._skipKeywords: set[str] = set() # Keywords to ignore
# Defaults
self._doComments.add(nwComment.FOOTNOTE)
# Other Setting
self._theme = TextDocumentTheme()
self._classes: dict[str, QColor] = {}
@@ -396,14 +398,12 @@ class Tokenizer(ABC):
self._doBodyText = state
return
def setSynopsis(self, state: bool) -> None:
"""Include synopsis comments in build."""
self._doSynopsis = state
return
def setComments(self, state: bool) -> None:
"""Include comments in build."""
self._doComments = state
def setCommentType(self, comment: nwComment, state: bool) -> None:
"""Toggle the inclusion og certain comment types."""
if state:
self._doComments.add(comment)
else:
self._doComments.discard(comment)
return
def setKeywords(self, state: bool) -> None:
@@ -607,9 +607,7 @@ class Tokenizer(ABC):
continue
cStyle, cKey, cText, _, _ = processComment(aLine)
if cStyle in (nwComment.SYNOPSIS, nwComment.SHORT) and not self._doSynopsis:
continue
if cStyle == nwComment.PLAIN and not self._doComments:
if cStyle not in self._doComments:
continue
if doJustify and not tStyle & BlockFmt.ALIGNED:
@@ -1043,7 +1041,8 @@ class Tokenizer(ABC):
tTxt, tFmt = self._extractFormats(text)
tFmt.insert(0, (0, TextFmt.COL_B, style.textClass))
tFmt.append((len(tTxt), TextFmt.COL_E, ""))
if label := (self._localLookup(style.label) + (f" ({key})" if key else "")).strip():
term = f" ({key.title()})" if key else ""
if label := f"{self._localLookup(style.label)}{term}".strip():
shift = len(label) + 2
tTxt = f"{label}: {tTxt}"
rFmt = [(0, TextFmt.B_B, ""), (shift - 1, TextFmt.B_E, "")]
+4 -3
View File
@@ -43,7 +43,7 @@ from PyQt6.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import decodeMimeHandles, qtAddAction, qtLambda
from novelwriter.constants import nwConst, nwStyles, nwUnicode
from novelwriter.enum import nwChange, nwDocAction, nwDocMode, nwItemType
from novelwriter.enum import nwChange, nwComment, nwDocAction, nwDocMode, nwItemType
from novelwriter.error import logException
from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.eventfilters import WheelEventFilter
@@ -228,8 +228,9 @@ class GuiDocViewer(QTextBrowser):
qDoc.setTheme(self._docTheme)
qDoc.initDocument()
qDoc.setKeywords(True)
qDoc.setComments(CONFIG.viewComments)
qDoc.setSynopsis(CONFIG.viewSynopsis)
qDoc.setCommentType(nwComment.PLAIN, CONFIG.viewComments)
qDoc.setCommentType(nwComment.SYNOPSIS, CONFIG.viewSynopsis)
qDoc.setCommentType(nwComment.SHORT, CONFIG.viewSynopsis)
# Be extra careful here to prevent crashes when first opening a
# project as a crash here leaves no way of recovering.
+1 -1
View File
@@ -629,7 +629,7 @@ class _DetailsWidget(QWidget):
item.setText(1, "")
self.listView.addTopLevelItem(item)
for key in [
"text.includeSynopsis", "text.includeComments",
"text.includeSynopsis", "text.includeComments", "text.includeStory",
"text.includeKeywords", "text.includeBodyText",
]:
sub = QTreeWidgetItem()
+4
View File
@@ -951,11 +951,13 @@ class _FormattingTab(NScrollableForm):
self.incBodyText = NSwitch(self, height=iPx)
self.incSynopsis = NSwitch(self, height=iPx)
self.incComments = NSwitch(self, height=iPx)
self.incStory = NSwitch(self, height=iPx)
self.incKeywords = NSwitch(self, height=iPx)
self.addRow(self._build.getLabel("text.includeBodyText"), self.incBodyText)
self.addRow(self._build.getLabel("text.includeSynopsis"), self.incSynopsis)
self.addRow(self._build.getLabel("text.includeComments"), self.incComments)
self.addRow(self._build.getLabel("text.includeStory"), self.incStory)
self.addRow(self._build.getLabel("text.includeKeywords"), self.incKeywords)
# Ignored Keywords
@@ -1264,6 +1266,7 @@ class _FormattingTab(NScrollableForm):
self.incBodyText.setChecked(self._build.getBool("text.includeBodyText"))
self.incSynopsis.setChecked(self._build.getBool("text.includeSynopsis"))
self.incComments.setChecked(self._build.getBool("text.includeComments"))
self.incStory.setChecked(self._build.getBool("text.includeStory"))
self.incKeywords.setChecked(self._build.getBool("text.includeKeywords"))
self.ignoredKeywords.setText(self._build.getStr("text.ignoredKeywords"))
self.addNoteHead.setChecked(self._build.getBool("text.addNoteHeadings"))
@@ -1362,6 +1365,7 @@ class _FormattingTab(NScrollableForm):
self._build.setValue("text.includeBodyText", self.incBodyText.isChecked())
self._build.setValue("text.includeSynopsis", self.incSynopsis.isChecked())
self._build.setValue("text.includeComments", self.incComments.isChecked())
self._build.setValue("text.includeStory", self.incStory.isChecked())
self._build.setValue("text.includeKeywords", self.incKeywords.isChecked())
self._build.setValue("text.ignoredKeywords", self.ignoredKeywords.text())
self._build.setValue("text.addNoteHeadings", self.addNoteHead.isChecked())
+7 -6
View File
@@ -30,7 +30,7 @@ from novelwriter.constants import nwHeadFmt
from novelwriter.core.buildsettings import BuildSettings
from novelwriter.core.docbuild import NWBuildDocument
from novelwriter.core.project import NWProject
from novelwriter.enum import nwBuildFmt
from novelwriter.enum import nwBuildFmt, nwComment
from novelwriter.formats.shared import BlockFmt, BlockTyp
from novelwriter.formats.todocx import OOXML_SCM, ToDocX, _mkTag, _wTag
@@ -226,8 +226,9 @@ def testFmtToDocX_ParagraphStyles(mockGUI):
"""Test formatting of paragraphs."""
project = NWProject()
doc = ToDocX(project)
doc.setSynopsis(True)
doc.setComments(True)
doc.setCommentType(nwComment.PLAIN, True)
doc.setCommentType(nwComment.SYNOPSIS, True)
doc.setCommentType(nwComment.SHORT, True)
doc.setKeywords(True)
doc.initDocument()
@@ -389,8 +390,8 @@ def testFmtToDocX_ParagraphFormatting(mockGUI):
"""Test formatting of paragraphs."""
project = NWProject()
doc = ToDocX(project)
doc.setSynopsis(True)
doc.setComments(True)
doc.setCommentType(nwComment.PLAIN, True)
doc.setCommentType(nwComment.SYNOPSIS, True)
doc.setKeywords(True)
doc.initDocument()
@@ -743,7 +744,7 @@ def testFmtToDocX_SaveDocument(mockGUI, prjLipsum, fncPath, tstPaths):
def prettifyXml(inFile, outFile):
with open(outFile, mode="wb") as fStream:
xml = ET.parse(inFile)
xmlIndent(xml)
xmlIndent(xml) # type: ignore
xml.write(fStream, encoding="utf-8", xml_declaration=True)
expected = [
+5 -4
View File
@@ -27,6 +27,7 @@ import pytest
from novelwriter import CONFIG
from novelwriter.constants import nwHeadFmt
from novelwriter.core.project import NWProject
from novelwriter.enum import nwComment
from novelwriter.formats.shared import BlockFmt, BlockTyp
from novelwriter.formats.tohtml import ToHtml
@@ -185,7 +186,7 @@ def testFmtToHtml_ConvertParagraphs(mockGUI):
html.doConvert()
assert html._pages[-1] == ""
html.setSynopsis(True)
html.setCommentType(nwComment.SYNOPSIS, True)
html._text = "%synopsis: The synopsis ...\n"
html.tokenizeText()
html.doConvert()
@@ -196,7 +197,7 @@ def testFmtToHtml_ConvertParagraphs(mockGUI):
"</p>\n"
)
html.setSynopsis(True)
html.setCommentType(nwComment.SHORT, True)
html._text = "%short: A short description ...\n"
html.tokenizeText()
html.doConvert()
@@ -213,7 +214,7 @@ def testFmtToHtml_ConvertParagraphs(mockGUI):
html.doConvert()
assert html._pages[-1] == ""
html.setComments(True)
html.setCommentType(nwComment.PLAIN, True)
html._text = "% A comment ...\n"
html.tokenizeText()
html.doConvert()
@@ -575,7 +576,7 @@ def testFmtToHtml_SpecialCases(mockGUI):
# ===================
# See: https://github.com/vkbo/novelWriter/issues/950
html.setComments(True)
html.setCommentType(nwComment.PLAIN, True)
html._text = "% Test > text _<**bold**>_ and more.\n"
html.tokenizeText()
html.doConvert()
+20 -19
View File
@@ -99,8 +99,7 @@ def testFmtToken_Setters(mockGUI):
assert tokens._hideSection is False
assert tokens._linkHeadings is False
assert tokens._doBodyText is True
assert tokens._doSynopsis is False
assert tokens._doComments is False
assert tokens._doComments == {nwComment.FOOTNOTE}
assert tokens._doKeywords is False
# Set new values
@@ -124,8 +123,9 @@ def testFmtToken_Setters(mockGUI):
tokens.setSeparatorMargins(2.0, 2.0)
tokens.setLinkHeadings(True)
tokens.setBodyText(False)
tokens.setSynopsis(True)
tokens.setComments(True)
tokens.setCommentType(nwComment.PLAIN, True)
tokens.setCommentType(nwComment.SHORT, True)
tokens.setCommentType(nwComment.SYNOPSIS, True)
tokens.setKeywords(True)
# Check new values
@@ -155,8 +155,9 @@ def testFmtToken_Setters(mockGUI):
assert tokens._hideSection is True
assert tokens._linkHeadings is True
assert tokens._doBodyText is False
assert tokens._doSynopsis is True
assert tokens._doComments is True
assert tokens._doComments == {
nwComment.FOOTNOTE, nwComment.PLAIN, nwComment.SYNOPSIS, nwComment.SHORT,
}
assert tokens._doKeywords is True
# Properties
@@ -756,12 +757,12 @@ def testFmtToken_MetaFormat(mockGUI):
assert tokens._blocks == []
# Comment
tokens.setComments(False)
tokens.setCommentType(nwComment.PLAIN, False)
tokens._text = "% A comment\n"
tokens.tokenizeText()
assert tokens._blocks == []
tokens.setComments(True)
tokens.setCommentType(nwComment.PLAIN, True)
tokens._text = "% A comment\n"
tokens.tokenizeText()
assert tokens._blocks == [(
@@ -773,12 +774,12 @@ def testFmtToken_MetaFormat(mockGUI):
)]
# Synopsis
tokens.setSynopsis(False)
tokens.setCommentType(nwComment.SYNOPSIS, False)
tokens._text = "%synopsis: The synopsis\n"
tokens.tokenizeText()
assert tokens._blocks == []
tokens.setSynopsis(True)
tokens.setCommentType(nwComment.SYNOPSIS, True)
tokens._text = "% synopsis: The synopsis\n"
tokens.tokenizeText()
assert tokens._blocks == [(
@@ -790,12 +791,12 @@ def testFmtToken_MetaFormat(mockGUI):
)]
# Short
tokens.setSynopsis(False)
tokens.setCommentType(nwComment.SHORT, False)
tokens._text = "% short: A short description\n"
tokens.tokenizeText()
assert tokens._blocks == []
tokens.setSynopsis(True)
tokens.setCommentType(nwComment.SHORT, True)
tokens._text = "% short: A short description\n"
tokens.tokenizeText()
assert tokens._blocks == [(
@@ -1151,7 +1152,7 @@ def testFmtToken_LineBreak(mockGUI):
project = NWProject()
tokens = BareTokenizer(project)
tokens._handle = TMH
tokens.setComments(True)
tokens.setCommentType(nwComment.PLAIN, True)
# They are stripped in headers
tokens._text = "## Hello[br] World"
@@ -1516,7 +1517,7 @@ def testFmtToken_TextIndent(mockGUI):
"""Test the handling of text indent in the Tokenizer class."""
project = NWProject()
tokens = BareTokenizer(project)
tokens.setSynopsis(True)
tokens.setCommentType(nwComment.SYNOPSIS, True)
tokens._handle = TMH
# No First Indent
@@ -1987,7 +1988,7 @@ def testFmtToken_CountStats(mockGUI, ipsumText):
tokens._counts = {}
tokens.setChapterFormat(nwHeadFmt.TITLE)
tokens.setSceneFormat("* * *", False)
tokens.setSynopsis(True)
tokens.setCommentType(nwComment.SYNOPSIS, True)
tokens.tokenizeText()
tokens.countStats()
assert [t[2] for t in tokens._blocks] == ["Chapter", "Synopsis: Stuff", "Text"]
@@ -2004,7 +2005,7 @@ def testFmtToken_CountStats(mockGUI, ipsumText):
tokens._counts = {}
tokens.setChapterFormat(nwHeadFmt.TITLE)
tokens.setSceneFormat("* * *", False)
tokens.setSynopsis(True)
tokens.setCommentType(nwComment.SHORT, True)
tokens.tokenizeText()
tokens.countStats()
assert [t[2] for t in tokens._blocks] == ["Chapter", "Short Description: Stuff", "Text"]
@@ -2021,7 +2022,7 @@ def testFmtToken_CountStats(mockGUI, ipsumText):
tokens._counts = {}
tokens.setChapterFormat(nwHeadFmt.TITLE)
tokens.setSceneFormat("* * *", False)
tokens.setComments(True)
tokens.setCommentType(nwComment.PLAIN, True)
tokens.tokenizeText()
tokens.countStats()
assert [t[2] for t in tokens._blocks] == ["Chapter", "Comment: Stuff", "Text"]
@@ -2095,8 +2096,8 @@ def testFmtToken_CountStats(mockGUI, ipsumText):
tokens.setPartitionFormat(f"T: {nwHeadFmt.TITLE}")
tokens.setChapterFormat(f"C {nwHeadFmt.CH_NUM}: {nwHeadFmt.TITLE}")
tokens.setSceneFormat("* * *", False)
tokens.setSynopsis(True)
tokens.setComments(True)
tokens.setCommentType(nwComment.SYNOPSIS, True)
tokens.setCommentType(nwComment.PLAIN, True)
tokens.setKeywords(True)
tokens.tokenizeText()
+4 -3
View File
@@ -24,6 +24,7 @@ import pytest
from novelwriter.constants import nwHeadFmt
from novelwriter.core.project import NWProject
from novelwriter.enum import nwComment
from novelwriter.formats.shared import BlockFmt, BlockTyp
from novelwriter.formats.tomarkdown import ToMarkdown
@@ -148,13 +149,13 @@ def testFmtToMarkdown_ConvertParagraphs(mockGUI):
md.doConvert()
assert md._pages[-1] == ""
md.setSynopsis(True)
md.setCommentType(nwComment.SYNOPSIS, True)
md._text = "%synopsis: The synopsis ...\n"
md.tokenizeText()
md.doConvert()
assert md._pages[-1] == "**Synopsis:** The synopsis ...\n\n"
md.setSynopsis(True)
md.setCommentType(nwComment.SHORT, True)
md._text = "%short: A description ...\n"
md.tokenizeText()
md.doConvert()
@@ -166,7 +167,7 @@ def testFmtToMarkdown_ConvertParagraphs(mockGUI):
md.doConvert()
assert md._pages[-1] == ""
md.setComments(True)
md.setCommentType(nwComment.PLAIN, True)
md._text = "% A comment ...\n"
md.tokenizeText()
md.doConvert()
+4 -2
View File
@@ -32,6 +32,7 @@ from PyQt6.QtGui import QColor
from novelwriter.common import xmlIndent
from novelwriter.constants import nwHeadFmt
from novelwriter.core.project import NWProject
from novelwriter.enum import nwComment
from novelwriter.formats.shared import BlockFmt, BlockTyp, TextFmt
from novelwriter.formats.toodt import ODTParagraphStyle, ODTTextStyle, ToOdt, XMLParagraph, _mkTag
@@ -611,8 +612,9 @@ def testFmtToOdt_ConvertParagraphs(mockGUI):
"% short: Then what\n\n"
"% A plain comment\n\n"
)
odt.setSynopsis(True)
odt.setComments(True)
odt.setCommentType(nwComment.SYNOPSIS, True)
odt.setCommentType(nwComment.SHORT, True)
odt.setCommentType(nwComment.PLAIN, True)
odt.setKeywords(True)
odt.tokenizeText()
odt.initDocument()
+5 -4
View File
@@ -27,6 +27,7 @@ from PyQt6.QtGui import QFont, QTextBlock, QTextCharFormat, QTextCursor
from novelwriter import CONFIG
from novelwriter.constants import nwUnicode
from novelwriter.core.project import NWProject
from novelwriter.enum import nwComment
from novelwriter.formats.shared import BlockFmt, BlockTyp, TextDocumentTheme
from novelwriter.formats.toqdoc import ToQTextDocument
from novelwriter.types import (
@@ -195,8 +196,8 @@ def testFmtToQTextDocument_NovelMeta(mockGUI):
doc._isNovel = True
doc._isFirst = True
doc.setComments(True)
doc.setSynopsis(True)
doc.setCommentType(nwComment.PLAIN, True)
doc.setCommentType(nwComment.SYNOPSIS, True)
doc.setKeywords(True)
doc._text = (
"### Scene\n\n"
@@ -272,8 +273,8 @@ def testFmtToQTextDocument_NoteMeta(mockGUI):
doc._isNovel = False
doc._isFirst = True
doc.setComments(True)
doc.setSynopsis(True)
doc.setCommentType(nwComment.PLAIN, True)
doc.setCommentType(nwComment.SHORT, True)
doc.setKeywords(True)
doc._text = (
"# Jane Smith\n\n"