Add more formatting options for manuscript builds (#1723)

This commit is contained in:
Veronica Berglyd Olsen
2024-03-02 15:42:12 +01:00
committed by GitHub
11 changed files with 630 additions and 83 deletions
+14
View File
@@ -59,10 +59,17 @@ SETTINGS_TEMPLATE = {
"headings.fmtSection": (str, ""),
"headings.hideScene": (bool, False),
"headings.hideSection": (bool, True),
"headings.centerTitle": (bool, True),
"headings.centerChapter": (bool, False),
"headings.centerScene": (bool, False),
"headings.breakTitle": (bool, True),
"headings.breakChapter": (bool, True),
"headings.breakScene": (bool, False),
"text.includeSynopsis": (bool, False),
"text.includeComments": (bool, False),
"text.includeKeywords": (bool, False),
"text.includeBodyText": (bool, True),
"text.ignoredKeywords": (str, ""),
"text.addNoteHeadings": (bool, True),
"format.textFont": (str, CONFIG.textFont),
"format.textSize": (int, 12),
@@ -81,7 +88,9 @@ SETTINGS_TEMPLATE = {
"odt.addColours": (bool, True),
"odt.pageHeader": (str, nwHeadFmt.ODT_AUTO),
"odt.pageCountOffset": (int, 0),
"md.preserveBreaks": (bool, True),
"html.addStyles": (bool, True),
"html.preserveTabs": (bool, False),
}
SETTINGS_LABELS = {
@@ -104,6 +113,7 @@ SETTINGS_LABELS = {
"text.includeComments": QT_TRANSLATE_NOOP("Builds", "Include Comments"),
"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"),
"text.grpInsert": QT_TRANSLATE_NOOP("Builds", "Insert Content"),
"text.addNoteHeadings": QT_TRANSLATE_NOOP("Builds", "Add Titles for Notes"),
@@ -130,8 +140,12 @@ SETTINGS_LABELS = {
"odt.pageHeader": QT_TRANSLATE_NOOP("Builds", "Page Header"),
"odt.pageCountOffset": QT_TRANSLATE_NOOP("Builds", "Page Counter Offset"),
"md": QT_TRANSLATE_NOOP("Builds", "Markdown (.md)"),
"md.preserveBreaks": QT_TRANSLATE_NOOP("Builds", "Preserve Hard Line Breaks"),
"html": QT_TRANSLATE_NOOP("Builds", "HTML (.html)"),
"html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
"html.preserveTabs": QT_TRANSLATE_NOOP("Builds", "Preserve Tab Characters"),
}
+36 -5
View File
@@ -52,15 +52,16 @@ class NWBuildDocument:
manuscript, based on a build definition object (BuildSettings).
"""
__slots__ = ("_project", "_build", "_queue", "_error", "_cache", "_count")
__slots__ = ("_project", "_build", "_queue", "_error", "_cache", "_count", "_preview")
def __init__(self, project: NWProject, build: BuildSettings, doCount: bool = False) -> None:
def __init__(self, project: NWProject, build: BuildSettings) -> None:
self._project = project
self._build = build
self._queue = []
self._error = None
self._cache = None
self._count = doCount
self._count = False
self._preview = False
return
##
@@ -80,6 +81,21 @@ class NWBuildDocument:
"""
return self._cache
##
# Setters
##
def setCountEnabled(self, state: bool) -> None:
"""Turn on/off stats counting for builds."""
self._count = state
return
def setPreviewMode(self, state: bool) -> None:
"""Set the preview mode of the build. Implies count mode."""
self._preview = state
self._count = state
return
##
# Special Methods
##
@@ -161,7 +177,7 @@ class NWBuildDocument:
else:
yield i, False
if self._build.getBool("format.replaceTabs"):
if not (self._build.getBool("html.preserveTabs") or self._preview):
makeObj.replaceTabs()
self._error = None
@@ -192,6 +208,8 @@ class NWBuildDocument:
if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")
makeObj.setPreserveBreaks(self._build.getBool("md.preserveBreaks"))
for i, tHandle in enumerate(self._queue):
self._error = None
if filtered.get(tHandle, (False, 0))[0]:
@@ -268,15 +286,28 @@ class NWBuildDocument:
self._build.getStr("headings.fmtSection"),
self._build.getBool("headings.hideSection")
)
bldObj.setTitleStyle(
self._build.getBool("headings.centerTitle"),
self._build.getBool("headings.breakTitle")
)
bldObj.setChapterStyle(
self._build.getBool("headings.centerChapter"),
self._build.getBool("headings.breakChapter")
)
bldObj.setSceneStyle(
self._build.getBool("headings.centerScene"),
self._build.getBool("headings.breakScene")
)
bldObj.setFont(fontFamily, textSize, textFixed)
bldObj.setJustify(self._build.getBool("format.justifyText"))
bldObj.setLineHeight(self._build.getFloat("format.lineHeight"))
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.setBodyText(self._build.getBool("text.includeBodyText"))
bldObj.setIgnoredKeywords(self._build.getStr("text.ignoredKeywords"))
if isinstance(bldObj, ToHtml):
bldObj.setStyles(self._build.getBool("html.addStyles"))
+72 -44
View File
@@ -130,16 +130,17 @@ class Tokenizer(ABC):
self._tokens: list[tuple[int, int, str, list[tuple[int, int]], int]] = []
# User Settings
self._textFont = "Serif" # Output text font
self._textSize = 11 # Output text size
self._textFixed = False # Fixed width text
self._lineHeight = 1.15 # Line height in units of em
self._blockIndent = 4.00 # Block indent in units of em
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._doKeywords = False # Also process keywords like tags and references
self._textFont = "Serif" # Output text font
self._textSize = 11 # Output text size
self._textFixed = False # Fixed width text
self._lineHeight = 1.15 # Line height in units of em
self._blockIndent = 4.00 # Block indent in units of em
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._doKeywords = False # Also process keywords like tags and references
self._skipKeywords = set() # Keywords to ignore
# Margins
self._marginTitle = (1.000, 0.500)
@@ -162,6 +163,10 @@ class Tokenizer(ABC):
self._linkHeaders = False # Add an anchor before headers
self._titleStyle = self.A_CENTRE | self.A_PBB
self._chapterStyle = self.A_PBB
self._sceneStyle = self.A_NONE
# Instance Variables
self._hFormatter = HeadingFormatter(self._project)
self._skipSeparator = False # Flag to indicate that we skip the scene separator
@@ -171,6 +176,7 @@ class Tokenizer(ABC):
self._isNovel = False # Document is a novel document
self._isNote = False # Document is a project note
self._isFirst = True # Document is the first in a set
self._noBreak = False # Don't allow an initial break when formatting headers
# Error Handling
self._errData = []
@@ -255,6 +261,27 @@ class Tokenizer(ABC):
self._hideSection = hide
return
def setTitleStyle(self, center: bool, pageBreak: bool) -> None:
"""Set the title heading style."""
self._titleStyle = (
(self.A_CENTRE if center else self.A_NONE) | (self.A_PBB if pageBreak else self.A_NONE)
)
return
def setChapterStyle(self, center: bool, pageBreak: bool) -> None:
"""Set the chapter heading style."""
self._chapterStyle = (
(self.A_CENTRE if center else self.A_NONE) | (self.A_PBB if pageBreak else self.A_NONE)
)
return
def setSceneStyle(self, center: bool, pageBreak: bool) -> None:
"""Set the scene heading style."""
self._sceneStyle = (
(self.A_CENTRE if center else self.A_NONE) | (self.A_PBB if pageBreak else self.A_NONE)
)
return
def setFont(self, family: str, size: int, isFixed: bool = False) -> None:
"""Set the build font."""
self._textFont = family
@@ -337,6 +364,11 @@ class Tokenizer(ABC):
self._doKeywords = state
return
def setIgnoredKeywords(self, keywords: str) -> None:
"""Comma separated string of keywords to ignore."""
self._skipKeywords = set(x.lower().strip() for x in keywords.split(","))
return
def setKeepMarkdown(self, state: bool) -> None:
"""Keep original markdown during build."""
self._keepMarkdown = state
@@ -504,31 +536,26 @@ class Tokenizer(ABC):
tmpMarkdown.append("%s\n" % aLine)
elif aLine[0] == "@":
self._tokens.append((
self.T_KEYWORD, nHead, aLine[1:].strip(), [], sAlign
))
if self._doKeywords and self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine)
valid, bits, _ = self._project.index.scanThis(aLine)
if valid and bits and bits[0] not in self._skipKeywords:
self._tokens.append((
self.T_KEYWORD, nHead, aLine[1:].strip(), [], sAlign
))
if self._doKeywords and self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:2] == "# ":
if self._isNovel:
sAlign |= self.A_CENTRE
sAlign |= self.A_PBB
nHead += 1
self._tokens.append((
self.T_HEAD1, nHead, aLine[2:].strip(), [], sAlign
self.T_HEAD1, nHead, aLine[2:].strip(), [], self.A_NONE
))
if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:3] == "## ":
if self._isNovel:
sAlign |= self.A_PBB
nHead += 1
self._tokens.append((
self.T_HEAD2, nHead, aLine[3:].strip(), [], sAlign
self.T_HEAD2, nHead, aLine[3:].strip(), [], self.A_NONE
))
if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine)
@@ -536,7 +563,7 @@ class Tokenizer(ABC):
elif aLine[:4] == "### ":
nHead += 1
self._tokens.append((
self.T_HEAD3, nHead, aLine[4:].strip(), [], sAlign
self.T_HEAD3, nHead, aLine[4:].strip(), [], self.A_NONE
))
if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine)
@@ -544,34 +571,25 @@ class Tokenizer(ABC):
elif aLine[:5] == "#### ":
nHead += 1
self._tokens.append((
self.T_HEAD4, nHead, aLine[5:].strip(), [], sAlign
self.T_HEAD4, nHead, aLine[5:].strip(), [], self.A_NONE
))
if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:3] == "#! ":
nHead += 1
if self._isNovel:
tStyle = self.T_TITLE
else:
tStyle = self.T_HEAD1
self._tokens.append((
tStyle, nHead, aLine[3:].strip(), [], sAlign | self.A_CENTRE
self.T_TITLE, nHead, aLine[3:].strip(), [], self.A_PBB | self.A_CENTRE
))
if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine)
elif aLine[:4] == "##! ":
nHead += 1
if self._isNovel:
tStyle = self.T_UNNUM
sAlign |= self.A_PBB
else:
tStyle = self.T_HEAD2
# If we're not in a novel section, we just treat this as a regular H2
tStyle = self.T_UNNUM if self._isNovel else self.T_HEAD2
self._tokens.append((
tStyle, nHead, aLine[4:].strip(), [], sAlign
tStyle, nHead, aLine[4:].strip(), [], self.A_NONE
))
if self._keepMarkdown:
tmpMarkdown.append("%s\n" % aLine)
@@ -622,7 +640,8 @@ class Tokenizer(ABC):
# If we have content, turn off the first page flag
if self._isFirst and self._tokens:
self._isFirst = False
self._isFirst = False # First document has been processed
self._noBreak = True # Check again after headers are processed
# Make sure the token array doesn't start with a page break
# on the very first page, adding a blank first page.
@@ -686,7 +705,7 @@ class Tokenizer(ABC):
tTemp = self._hFormatter.apply(self._fmtTitle, token[2], token[1])
self._tokens[n] = (
token[0], token[1], tTemp, [], token[4]
token[0], token[1], tTemp, [], self._titleStyle
)
# Set scene variables
@@ -704,7 +723,7 @@ class Tokenizer(ABC):
# Format the chapter header
self._tokens[n] = (
token[0], token[1], tTemp, [], token[4]
token[0], token[1], tTemp, [], self._chapterStyle
)
# Set scene variables
@@ -729,11 +748,11 @@ class Tokenizer(ABC):
self._tokens[n] = (
self.T_EMPTY if self._skipSeparator else self.T_SEP, token[1],
"" if self._skipSeparator else tTemp, [],
self.A_NONE if self._skipSeparator else (token[4] | self.A_CENTRE)
self.A_NONE if self._skipSeparator else self.A_CENTRE
)
else:
self._tokens[n] = (
token[0], token[1], tTemp, [], token[4]
token[0], token[1], tTemp, [], self._sceneStyle
)
self._skipSeparator = False
@@ -758,6 +777,15 @@ class Tokenizer(ABC):
token[0], token[1], tTemp, [], token[4]
)
if n == 0 and self._noBreak:
# Make sure again that the token array doesn't start with a page break
self._noBreak = False
if self._tokens[0][4] & self.A_PBB:
token = self._tokens[0]
self._tokens[0] = (
token[0], token[1], token[2], token[3], token[4] & ~self.A_PBB
)
return True
def countStats(self) -> dict[str, int]:
+8 -1
View File
@@ -49,6 +49,7 @@ class ToMarkdown(Tokenizer):
super().__init__(project)
self._genMode = self.M_STD
self._fullMD: list[str] = []
self._preserveBreaks = True
return
##
@@ -74,6 +75,11 @@ class ToMarkdown(Tokenizer):
self._genMode = self.M_EXT
return
def setPreserveBreaks(self, state: bool) -> None:
"""Preserve line breaks in paragraphs."""
self._preserveBreaks = state
return
##
# Class Methods
##
@@ -125,12 +131,13 @@ class ToMarkdown(Tokenizer):
para = []
lines = []
lineSep = " \n" if self._preserveBreaks else " "
for tType, _, tText, tFormat, tStyle in self._tokens:
if tType == self.T_EMPTY:
if len(para) > 0:
tTemp = (" \n".join(para)).rstrip(" ")
tTemp = (lineSep.join(para)).rstrip(" ")
lines.append(f"{tTemp}\n\n")
para = []
+2 -1
View File
@@ -337,7 +337,8 @@ class GuiManuscript(QDialog):
if build is None:
return
docBuild = NWBuildDocument(SHARED.project, build, doCount=True)
docBuild = NWBuildDocument(SHARED.project, build)
docBuild.setPreviewMode(True)
docBuild.queueAll()
self.docPreview.beginNewBuild(len(docBuild))
+139 -21
View File
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwHeadFmt, nwLabels, trConst
from novelwriter.constants import nwHeadFmt, nwKeyWords, nwLabels, trConst
from novelwriter.core.buildsettings import BuildSettings, FilterMode
from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.modified import NComboBox, NDoubleSpinBox, NSpinBox
@@ -593,6 +593,7 @@ class _HeadingsTab(NScrollablePage):
self._editing = 0
iPx = SHARED.theme.baseIconSize
sSp = CONFIG.pxInt(16)
vSp = CONFIG.pxInt(12)
bSp = CONFIG.pxInt(6)
@@ -614,8 +615,8 @@ class _HeadingsTab(NScrollablePage):
wrapTitle.addWidget(self.btnTitle)
wrapTitle.setSpacing(bSp)
self.formatBox.addWidget(self.lblTitle, 0, 0, Qt.AlignLeft)
self.formatBox.addLayout(wrapTitle, 0, 1, Qt.AlignLeft)
self.formatBox.addWidget(self.lblTitle, 0, 0)
self.formatBox.addLayout(wrapTitle, 0, 1)
# Chapter Heading
self.lblChapter = QLabel(self._build.getLabel("headings.fmtChapter"))
@@ -630,8 +631,8 @@ class _HeadingsTab(NScrollablePage):
wrapChapter.addWidget(self.btnChapter)
wrapChapter.setSpacing(bSp)
self.formatBox.addWidget(self.lblChapter, 1, 0, Qt.AlignLeft)
self.formatBox.addLayout(wrapChapter, 1, 1, Qt.AlignLeft)
self.formatBox.addWidget(self.lblChapter, 1, 0)
self.formatBox.addLayout(wrapChapter, 1, 1)
# Unnumbered Chapter Heading
self.lblUnnumbered = QLabel(self._build.getLabel("headings.fmtUnnumbered"))
@@ -646,8 +647,8 @@ class _HeadingsTab(NScrollablePage):
wrapUnnumbered.addWidget(self.btnUnnumbered)
wrapUnnumbered.setSpacing(bSp)
self.formatBox.addWidget(self.lblUnnumbered, 2, 0, Qt.AlignLeft)
self.formatBox.addLayout(wrapUnnumbered, 2, 1, Qt.AlignLeft)
self.formatBox.addWidget(self.lblUnnumbered, 2, 0)
self.formatBox.addLayout(wrapUnnumbered, 2, 1)
# Scene Heading
sceneHideTip = self._build.getLabel("headings.hideScene")
@@ -672,9 +673,9 @@ class _HeadingsTab(NScrollablePage):
wrapSceneHide.addWidget(self.swtScene)
wrapSceneHide.setSpacing(bSp)
self.formatBox.addWidget(self.lblScene, 3, 0, Qt.AlignLeft)
self.formatBox.addLayout(wrapScene, 3, 1, Qt.AlignLeft)
self.formatBox.addLayout(wrapSceneHide, 3, 2, Qt.AlignLeft)
self.formatBox.addWidget(self.lblScene, 3, 0)
self.formatBox.addLayout(wrapScene, 3, 1)
self.formatBox.addLayout(wrapSceneHide, 3, 2)
# Section Heading
sectionHideTip = self._build.getLabel("headings.hideSection")
@@ -699,16 +700,16 @@ class _HeadingsTab(NScrollablePage):
wrapSectionHide.addWidget(self.swtSection)
wrapSectionHide.setSpacing(bSp)
self.formatBox.addWidget(self.lblSection, 4, 0, Qt.AlignLeft)
self.formatBox.addLayout(wrapSection, 4, 1, Qt.AlignLeft)
self.formatBox.addLayout(wrapSectionHide, 4, 2, Qt.AlignLeft)
self.formatBox.addWidget(self.lblSection, 4, 0)
self.formatBox.addLayout(wrapSection, 4, 1)
self.formatBox.addLayout(wrapSectionHide, 4, 2)
# Edit Form
# =========
self.lblEditForm = QLabel(self.tr("Editing: {0}").format(self.tr("None")))
self.editTextBox = QPlainTextEdit()
self.editTextBox = QPlainTextEdit(self)
self.editTextBox.setFixedHeight(5*iPx)
self.editTextBox.setEnabled(False)
@@ -735,10 +736,10 @@ class _HeadingsTab(NScrollablePage):
self.aInsCharPOV.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CHAR_POV))
self.aInsCharFocus.triggered.connect(lambda: self._insertIntoForm(nwHeadFmt.CHAR_FOCUS))
self.btnInsert = QPushButton(self.tr("Insert"))
self.btnInsert = QPushButton(self.tr("Insert"), self)
self.btnInsert.setMenu(self.menuInsert)
self.btnApply = QPushButton(self.tr("Apply"))
self.btnApply = QPushButton(self.tr("Apply"), self)
self.btnApply.clicked.connect(self._saveFormat)
self.formButtonBox = QHBoxLayout()
@@ -751,13 +752,72 @@ class _HeadingsTab(NScrollablePage):
self.editFormBox.addWidget(self.editTextBox)
self.editFormBox.addLayout(self.formButtonBox)
# Layout Matrix
# =============
self.layoutMatrix = QGridLayout()
self.layoutMatrix.setVerticalSpacing(vSp)
self.layoutMatrix.setHorizontalSpacing(vSp)
# Heading
self.layoutHeading = QLabel("<b>{0}</b>".format(self.tr("Additional Styling")), self)
self.layoutMatrix.addWidget(self.layoutHeading, 0, 0, 1, 5)
# Title Layout
self.mtxTitle = QLabel(self._build.getLabel("headings.fmtTitle"))
self.centerTitle = NSwitch(self, height=iPx)
self.breakTitle = NSwitch(self, height=iPx)
lblCenterT = QLabel(self.tr("Centre"))
lblCenterT.setIndent(sSp)
lblBreakT = QLabel(self.tr("Page Break"))
lblBreakT.setIndent(sSp)
self.layoutMatrix.addWidget(self.mtxTitle, 1, 0)
self.layoutMatrix.addWidget(lblCenterT, 1, 1)
self.layoutMatrix.addWidget(self.centerTitle, 1, 2)
self.layoutMatrix.addWidget(lblBreakT, 1, 3)
self.layoutMatrix.addWidget(self.breakTitle, 1, 4)
# Chapter Layout
self.mtxChapter = QLabel(self._build.getLabel("headings.fmtChapter"))
self.centerChapter = NSwitch(self, height=iPx)
self.breakChapter = NSwitch(self, height=iPx)
lblCenterC = QLabel(self.tr("Centre"))
lblCenterC.setIndent(sSp)
lblBreakC = QLabel(self.tr("Page Break"))
lblBreakC.setIndent(sSp)
self.layoutMatrix.addWidget(self.mtxChapter, 2, 0)
self.layoutMatrix.addWidget(lblCenterC, 2, 1)
self.layoutMatrix.addWidget(self.centerChapter, 2, 2)
self.layoutMatrix.addWidget(lblBreakC, 2, 3)
self.layoutMatrix.addWidget(self.breakChapter, 2, 4)
# Scene Layout
self.mtxScene = QLabel(self._build.getLabel("headings.fmtScene"))
self.centerScene = NSwitch(self, height=iPx)
self.breakScene = NSwitch(self, height=iPx)
lblCenterS = QLabel(self.tr("Centre"))
lblCenterS.setIndent(sSp)
lblBreakS = QLabel(self.tr("Page Break"))
lblBreakS.setIndent(sSp)
self.layoutMatrix.addWidget(self.mtxScene, 3, 0)
self.layoutMatrix.addWidget(lblCenterS, 3, 1)
self.layoutMatrix.addWidget(self.centerScene, 3, 2)
self.layoutMatrix.addWidget(lblBreakS, 3, 3)
self.layoutMatrix.addWidget(self.breakScene, 3, 4)
self.layoutMatrix.setColumnStretch(5, 1)
# Assemble
# ========
self.outerBox = QVBoxLayout()
self.outerBox.addLayout(self.formatBox)
self.outerBox.addSpacing(CONFIG.pxInt(16))
self.outerBox.addSpacing(sSp)
self.outerBox.addLayout(self.editFormBox)
self.outerBox.addSpacing(sSp)
self.outerBox.addLayout(self.layoutMatrix)
self.outerBox.addStretch(1)
self.setCentralLayout(self.outerBox)
@@ -773,12 +833,24 @@ class _HeadingsTab(NScrollablePage):
self.fmtSection.setText(self._build.getStr("headings.fmtSection"))
self.swtScene.setChecked(self._build.getBool("headings.hideScene"))
self.swtSection.setChecked(self._build.getBool("headings.hideSection"))
self.centerTitle.setChecked(self._build.getBool("headings.centerTitle"))
self.centerChapter.setChecked(self._build.getBool("headings.centerChapter"))
self.centerScene.setChecked(self._build.getBool("headings.centerScene"))
self.breakTitle.setChecked(self._build.getBool("headings.breakTitle"))
self.breakChapter.setChecked(self._build.getBool("headings.breakChapter"))
self.breakScene.setChecked(self._build.getBool("headings.breakScene"))
return
def saveContent(self) -> None:
"""Save choices back into build object."""
self._build.setValue("headings.hideScene", self.swtScene.isChecked())
self._build.setValue("headings.hideSection", self.swtSection.isChecked())
self._build.setValue("headings.centerTitle", self.centerTitle.isChecked())
self._build.setValue("headings.centerChapter", self.centerChapter.isChecked())
self._build.setValue("headings.centerScene", self.centerScene.isChecked())
self._build.setValue("headings.breakTitle", self.breakTitle.isChecked())
self._build.setValue("headings.breakChapter", self.breakChapter.isChecked())
self._build.setValue("headings.breakScene", self.breakScene.isChecked())
return
##
@@ -894,16 +966,32 @@ class _ContentTab(NScrollableForm):
iPx = SHARED.theme.baseIconSize
# Text Content
self.incBodyText = NSwitch(self, height=iPx)
self.incSynopsis = NSwitch(self, height=iPx)
self.incComments = NSwitch(self, height=iPx)
self.incKeywords = NSwitch(self, height=iPx)
self.incBodyText = NSwitch(self, height=iPx)
self.ignoredKeywords = QLineEdit(self)
self.mnKeywords = QMenu(self)
for keyword in nwKeyWords.VALID_KEYS:
self.mnKeywords.addAction(
trConst(nwLabels.KEY_NAME[keyword]),
lambda keyword=keyword: self._updateIgnoredKeywords(keyword)
)
self.ignoredKeywordsButton = QToolButton(self)
self.ignoredKeywordsButton.setIcon(SHARED.theme.getIcon("add"))
self.ignoredKeywordsButton.setMenu(self.mnKeywords)
self.ignoredKeywordsButton.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
self.addGroupLabel(self._build.getLabel("text.grpContent"))
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.includeKeywords"), self.incKeywords)
self.addRow(self._build.getLabel("text.includeBodyText"), self.incBodyText)
self.addRow(self._build.getLabel("text.ignoredKeywords"), self.ignoredKeywords,
button=self.ignoredKeywordsButton, stretch=(1, 1))
# Insert Content
self.addNoteHead = NSwitch(self, height=iPx)
@@ -918,22 +1006,39 @@ class _ContentTab(NScrollableForm):
def loadContent(self) -> None:
"""Populate the widgets."""
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.incKeywords.setChecked(self._build.getBool("text.includeKeywords"))
self.incBodyText.setChecked(self._build.getBool("text.includeBodyText"))
self.ignoredKeywords.setText(self._build.getStr("text.ignoredKeywords"))
self.addNoteHead.setChecked(self._build.getBool("text.addNoteHeadings"))
self._updateIgnoredKeywords()
return
def saveContent(self) -> None:
"""Save choices back into build object."""
self._updateIgnoredKeywords()
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.includeKeywords", self.incKeywords.isChecked())
self._build.setValue("text.includeBodyText", self.incBodyText.isChecked())
self._build.setValue("text.ignoredKeywords", self.ignoredKeywords.text())
self._build.setValue("text.addNoteHeadings", self.addNoteHead.isChecked())
return
##
# Internal Functions
##
def _updateIgnoredKeywords(self, keyword: str | None = None) -> None:
"""Update the ignored keywords list."""
current = [x.lower().strip() for x in self.ignoredKeywords.text().split(",")]
if keyword:
current.append(keyword)
verified = set(x for x in current if x in nwKeyWords.VALID_KEYS)
self.ignoredKeywords.setText(", ".join(verified))
return
# END Class _ContentTab
@@ -1245,6 +1350,15 @@ class _OutputTab(NScrollableForm):
self.htmlAddStyles = NSwitch(self, height=iPx)
self.addRow(self._build.getLabel("html.addStyles"), self.htmlAddStyles)
self.htmlPreserveTabs = NSwitch(self, height=iPx)
self.addRow(self._build.getLabel("html.preserveTabs"), self.htmlPreserveTabs)
# Markdown Document
self.addGroupLabel(self._build.getLabel("md"))
self.mdPreserveBreaks = NSwitch(self, height=iPx)
self.addRow(self._build.getLabel("md.preserveBreaks"), self.mdPreserveBreaks)
# Finalise
self.finalise()
@@ -1256,6 +1370,8 @@ class _OutputTab(NScrollableForm):
self.odtPageHeader.setText(self._build.getStr("odt.pageHeader"))
self.odtPageCountOffset.setValue(self._build.getInt("odt.pageCountOffset"))
self.htmlAddStyles.setChecked(self._build.getBool("html.addStyles"))
self.htmlPreserveTabs.setChecked(self._build.getBool("html.preserveTabs"))
self.mdPreserveBreaks.setChecked(self._build.getBool("md.preserveBreaks"))
return
def saveContent(self) -> None:
@@ -1264,6 +1380,8 @@ class _OutputTab(NScrollableForm):
self._build.setValue("odt.pageHeader", self.odtPageHeader.text())
self._build.setValue("odt.pageCountOffset", self.odtPageCountOffset.value())
self._build.setValue("html.addStyles", self.htmlAddStyles.isChecked())
self._build.setValue("html.preserveTabs", self.htmlPreserveTabs.isChecked())
self._build.setValue("md.preserveBreaks", self.mdPreserveBreaks.isChecked())
return
##
+2 -1
View File
@@ -83,7 +83,8 @@ def testCoreDocBuild_OpenDocument(monkeypatch, mockGUI, prjLipsum, fncPath, tstP
build = BuildSettings()
build.unpack(BUILD_CONF)
docBuild = NWBuildDocument(project, build, doCount=True)
docBuild = NWBuildDocument(project, build)
docBuild.setCountEnabled(True)
docBuild.queueAll()
assert len(docBuild) == 21
+22 -2
View File
@@ -44,6 +44,7 @@ def testCoreToHtml_ConvertHeaders(mockGUI):
# Header 1
html._text = "# Partition\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == (
"<h1 class='title' style='text-align: center;'>Partition</h1>\n"
@@ -52,6 +53,7 @@ def testCoreToHtml_ConvertHeaders(mockGUI):
# Header 2
html._text = "## Chapter Title\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == (
"<h1 style='page-break-before: always;'>Chapter Title</h1>\n"
@@ -60,26 +62,30 @@ def testCoreToHtml_ConvertHeaders(mockGUI):
# Header 3
html._text = "### Scene Title\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == "<h2>Scene Title</h2>\n"
# Header 4
html._text = "#### Section Title\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == "<h3>Section Title</h3>\n"
# Title
html._text = "#! Title\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == (
"<h1 class='title' style='text-align: center;'>Title</h1>\n"
"<h1 class='title' style='text-align: center; page-break-before: always;'>Title</h1>\n"
)
# Unnumbered
html._text = "##! Prologue\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == "<h1 style='page-break-before: always;'>Prologue</h1>\n"
@@ -94,38 +100,45 @@ def testCoreToHtml_ConvertHeaders(mockGUI):
# Header 1
html._text = "# Heading One\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == "<h1><a name='T0001'></a>Heading One</h1>\n"
# Header 2
html._text = "## Heading Two\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == "<h2><a name='T0001'></a>Heading Two</h2>\n"
# Header 3
html._text = "### Heading Three\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == "<h3><a name='T0001'></a>Heading Three</h3>\n"
# Header 4
html._text = "#### Heading Four\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == "<h4><a name='T0001'></a>Heading Four</h4>\n"
# Title
html._text = "#! Heading One\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == (
"<h1 style='text-align: center;'><a name='T0001'></a>Heading One</h1>\n"
"<h1 class='title' style='text-align: center; page-break-before: always;'>"
"<a name='T0001'></a>Heading One</h1>\n"
)
# Unnumbered
html._text = "##! Heading Two\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == "<h2><a name='T0001'></a>Heading Two</h2>\n"
@@ -227,9 +240,12 @@ def testCoreToHtml_ConvertParagraphs(mockGUI):
)
# Multiple Keywords
html._isFirst = False
html._noBreak = False
html.setKeywords(True)
html._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == (
"<h1 style='page-break-before: always;'>Chapter</h1>\n"
@@ -457,8 +473,11 @@ def testCoreToHtml_SpecialCases(mockGUI):
"</p>\n"
)
html._isFirst = False
html._noBreak = False
html._text = "## Heading <1>\n"
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == (
"<h1 style='page-break-before: always;'>Heading &lt;1&gt;</h1>\n"
@@ -532,6 +551,7 @@ def testCoreToHtml_Complex(mockGUI, fncPath):
html._text = docText[i]
html.doPreProcessing()
html.tokenizeText()
html.doHeaders()
html.doConvert()
assert html.result == resText[i]
+312 -3
View File
@@ -261,7 +261,7 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens.tokenizeText()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Note Title", [], Tokenizer.A_CENTRE),
(Tokenizer.T_TITLE, 1, "Note Title", [], Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
]
assert tokens.allMarkdown[-1] == "#! Note Title\n\n"
@@ -276,6 +276,7 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens._text = "# Novel Title\n"
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Novel Title", [], Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -289,6 +290,7 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens._text = "# Note Title\n"
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Note Title", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -301,9 +303,11 @@ def testCoreToken_HeaderFormat(mockGUI):
# Story File
tokens._isNovel = True
tokens._isNote = False
tokens._noBreak = False
tokens._text = "## Chapter One\n"
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Chapter One", [], Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -316,6 +320,7 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens._text = "## Heading 2\n"
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Heading 2", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -331,6 +336,7 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens._text = "### Scene One\n"
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD3, 1, "Scene One", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -343,6 +349,7 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens._text = "### Heading 3\n"
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD3, 1, "Heading 3", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -358,6 +365,7 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens._text = "#### A Section\n"
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD4, 1, "A Section", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -370,6 +378,7 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens._text = "#### Heading 4\n"
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD4, 1, "Heading 4", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -382,11 +391,13 @@ def testCoreToken_HeaderFormat(mockGUI):
# Story File
tokens._isNovel = True
tokens._isNote = False
tokens._isFirst = False
tokens._text = "#! Title\n"
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_TITLE, 1, "Title", [], Tokenizer.A_CENTRE),
(Tokenizer.T_TITLE, 1, "Title", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
]
assert tokens.allMarkdown[-1] == "#! Title\n\n"
@@ -394,11 +405,13 @@ def testCoreToken_HeaderFormat(mockGUI):
# Note File
tokens._isNovel = False
tokens._isNote = True
tokens._isFirst = False
tokens._text = "#! Title\n"
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title", [], Tokenizer.A_CENTRE),
(Tokenizer.T_TITLE, 1, "Title", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
]
assert tokens.allMarkdown[-1] == "#! Title\n\n"
@@ -412,6 +425,7 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens._text = "##! Prologue\n"
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_UNNUM, 1, "Prologue", [], Tokenizer.A_PBB),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -424,6 +438,7 @@ def testCoreToken_HeaderFormat(mockGUI):
tokens._text = "##! Prologue\n"
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD2, 1, "Prologue", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -433,6 +448,280 @@ def testCoreToken_HeaderFormat(mockGUI):
# END Test testCoreToken_HeaderFormat
@pytest.mark.core
def testCoreToken_HeaderStyle(mockGUI):
"""Test the styling of headers in the Tokenizer class."""
project = NWProject()
tokens = BareTokenizer(project)
def processStyle(text: str, first: bool) -> int:
tokens._text = text
tokens._isFirst = first
tokens._noBreak = first
tokens.tokenizeText()
tokens.doHeaders()
return tokens._tokens[0][4]
# No Styles
# =========
tokens.setTitleStyle(False, False)
tokens.setChapterStyle(False, False)
tokens.setSceneStyle(False, False)
assert tokens._titleStyle == Tokenizer.A_NONE
assert tokens._chapterStyle == Tokenizer.A_NONE
assert tokens._sceneStyle == Tokenizer.A_NONE
# Novel Docs
tokens._isNovel = True
tokens._isNote = False
# First Document is False
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
assert processStyle("## Chapter\n", False) == Tokenizer.A_NONE
assert processStyle("### Scene\n", False) == Tokenizer.A_NONE
assert processStyle("#### Section\n", False) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("##! Prologue\n", False) == Tokenizer.A_NONE
# First Document is True
assert processStyle("# Title\n", True) == Tokenizer.A_NONE
assert processStyle("## Chapter\n", True) == Tokenizer.A_NONE
assert processStyle("### Scene\n", True) == Tokenizer.A_NONE
assert processStyle("#### Section\n", True) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", True) == Tokenizer.A_CENTRE
assert processStyle("##! Prologue\n", True) == Tokenizer.A_NONE
# Note Docs
tokens._isNovel = False
tokens._isNote = True
# First Document is False
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
assert processStyle("## Chapter\n", False) == Tokenizer.A_NONE
assert processStyle("### Scene\n", False) == Tokenizer.A_NONE
assert processStyle("#### Section\n", False) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("##! Prologue\n", False) == Tokenizer.A_NONE
# First Document is True
assert processStyle("# Title\n", True) == Tokenizer.A_NONE
assert processStyle("## Chapter\n", True) == Tokenizer.A_NONE
assert processStyle("### Scene\n", True) == Tokenizer.A_NONE
assert processStyle("#### Section\n", True) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", True) == Tokenizer.A_CENTRE
assert processStyle("##! Prologue\n", True) == Tokenizer.A_NONE
# Center Headers
# ==============
tokens.setTitleStyle(True, False)
tokens.setChapterStyle(True, False)
tokens.setSceneStyle(True, False)
assert tokens._titleStyle == Tokenizer.A_CENTRE
assert tokens._chapterStyle == Tokenizer.A_CENTRE
assert tokens._sceneStyle == Tokenizer.A_CENTRE
# Novel Docs
tokens._isNovel = True
tokens._isNote = False
# First Document is False
assert processStyle("# Title\n", False) == Tokenizer.A_CENTRE
assert processStyle("## Chapter\n", False) == Tokenizer.A_CENTRE
assert processStyle("### Scene\n", False) == Tokenizer.A_CENTRE
assert processStyle("#### Section\n", False) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("##! Prologue\n", False) == Tokenizer.A_CENTRE
# First Document is True
assert processStyle("# Title\n", True) == Tokenizer.A_CENTRE
assert processStyle("## Chapter\n", True) == Tokenizer.A_CENTRE
assert processStyle("### Scene\n", True) == Tokenizer.A_CENTRE
assert processStyle("#### Section\n", True) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", True) == Tokenizer.A_CENTRE
assert processStyle("##! Prologue\n", True) == Tokenizer.A_CENTRE
# Note Docs
tokens._isNovel = False
tokens._isNote = True
# First Document is False
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
assert processStyle("## Chapter\n", False) == Tokenizer.A_NONE
assert processStyle("### Scene\n", False) == Tokenizer.A_NONE
assert processStyle("#### Section\n", False) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("##! Prologue\n", False) == Tokenizer.A_NONE
# First Document is True
assert processStyle("# Title\n", True) == Tokenizer.A_NONE
assert processStyle("## Chapter\n", True) == Tokenizer.A_NONE
assert processStyle("### Scene\n", True) == Tokenizer.A_NONE
assert processStyle("#### Section\n", True) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", True) == Tokenizer.A_CENTRE
assert processStyle("##! Prologue\n", True) == Tokenizer.A_NONE
# Page Break Headers
# ==================
tokens.setTitleStyle(False, True)
tokens.setChapterStyle(False, True)
tokens.setSceneStyle(False, True)
assert tokens._titleStyle == Tokenizer.A_PBB
assert tokens._chapterStyle == Tokenizer.A_PBB
assert tokens._sceneStyle == Tokenizer.A_PBB
# Novel Docs
tokens._isNovel = True
tokens._isNote = False
# First Document is False
assert processStyle("# Title\n", False) == Tokenizer.A_PBB
assert processStyle("## Chapter\n", False) == Tokenizer.A_PBB
assert processStyle("### Scene\n", False) == Tokenizer.A_PBB
assert processStyle("#### Section\n", False) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("##! Prologue\n", False) == Tokenizer.A_PBB
# First Document is True
assert processStyle("# Title\n", True) == Tokenizer.A_NONE
assert processStyle("## Chapter\n", True) == Tokenizer.A_NONE
assert processStyle("### Scene\n", True) == Tokenizer.A_NONE
assert processStyle("#### Section\n", True) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", True) == Tokenizer.A_CENTRE
assert processStyle("##! Prologue\n", True) == Tokenizer.A_NONE
# Note Docs
tokens._isNovel = False
tokens._isNote = True
# First Document is False
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
assert processStyle("## Chapter\n", False) == Tokenizer.A_NONE
assert processStyle("### Scene\n", False) == Tokenizer.A_NONE
assert processStyle("#### Section\n", False) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("##! Prologue\n", False) == Tokenizer.A_NONE
# First Document is True
assert processStyle("# Title\n", True) == Tokenizer.A_NONE
assert processStyle("## Chapter\n", True) == Tokenizer.A_NONE
assert processStyle("### Scene\n", True) == Tokenizer.A_NONE
assert processStyle("#### Section\n", True) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", True) == Tokenizer.A_CENTRE
assert processStyle("##! Prologue\n", True) == Tokenizer.A_NONE
# Page Break and Centre Headers
# =============================
tokens.setTitleStyle(True, True)
tokens.setChapterStyle(True, True)
tokens.setSceneStyle(True, True)
assert tokens._titleStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert tokens._chapterStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert tokens._sceneStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
# Novel Docs
tokens._isNovel = True
tokens._isNote = False
# First Document is False
assert processStyle("# Title\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("## Chapter\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("### Scene\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("#### Section\n", False) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("##! Prologue\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
# First Document is True
assert processStyle("# Title\n", True) == Tokenizer.A_CENTRE
assert processStyle("## Chapter\n", True) == Tokenizer.A_CENTRE
assert processStyle("### Scene\n", True) == Tokenizer.A_CENTRE
assert processStyle("#### Section\n", True) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", True) == Tokenizer.A_CENTRE
assert processStyle("##! Prologue\n", True) == Tokenizer.A_CENTRE
# Note Docs
tokens._isNovel = False
tokens._isNote = True
# First Document is False
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
assert processStyle("## Chapter\n", False) == Tokenizer.A_NONE
assert processStyle("### Scene\n", False) == Tokenizer.A_NONE
assert processStyle("#### Section\n", False) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("##! Prologue\n", False) == Tokenizer.A_NONE
# First Document is True
assert processStyle("# Title\n", True) == Tokenizer.A_NONE
assert processStyle("## Chapter\n", True) == Tokenizer.A_NONE
assert processStyle("### Scene\n", True) == Tokenizer.A_NONE
assert processStyle("#### Section\n", True) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", True) == Tokenizer.A_CENTRE
assert processStyle("##! Prologue\n", True) == Tokenizer.A_NONE
# Check Separation
# ================
tokens._isNovel = True
tokens._isNote = False
# Title Styles
tokens.setTitleStyle(True, True)
tokens.setChapterStyle(False, False)
tokens.setSceneStyle(False, False)
assert tokens._titleStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert tokens._chapterStyle == Tokenizer.A_NONE
assert tokens._sceneStyle == Tokenizer.A_NONE
assert processStyle("# Title\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("## Chapter\n", False) == Tokenizer.A_NONE
assert processStyle("### Scene\n", False) == Tokenizer.A_NONE
assert processStyle("#### Section\n", False) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("##! Prologue\n", False) == Tokenizer.A_NONE
# Chapter Styles
tokens.setTitleStyle(False, False)
tokens.setChapterStyle(True, True)
tokens.setSceneStyle(False, False)
assert tokens._titleStyle == Tokenizer.A_NONE
assert tokens._chapterStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert tokens._sceneStyle == Tokenizer.A_NONE
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
assert processStyle("## Chapter\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("### Scene\n", False) == Tokenizer.A_NONE
assert processStyle("#### Section\n", False) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("##! Prologue\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
# Scene Styles
tokens.setTitleStyle(False, False)
tokens.setChapterStyle(False, False)
tokens.setSceneStyle(True, True)
assert tokens._titleStyle == Tokenizer.A_NONE
assert tokens._chapterStyle == Tokenizer.A_NONE
assert tokens._sceneStyle == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
assert processStyle("## Chapter\n", False) == Tokenizer.A_NONE
assert processStyle("### Scene\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("#### Section\n", False) == Tokenizer.A_NONE
assert processStyle("#! My Novel\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
assert processStyle("##! Prologue\n", False) == Tokenizer.A_NONE
# END Test testCoreToken_HeaderStyle
@pytest.mark.core
def testCoreToken_MetaFormat(mockGUI):
"""Test the tokenization of meta formats in the Tokenizer class."""
@@ -520,6 +809,15 @@ def testCoreToken_MetaFormat(mockGUI):
]
assert tokens.allMarkdown[-1] == "@pov: Bod\n@plot: Main\n@location: Europe\n\n"
# Ignored keywords
tokens._text = "@pov: Bod\n@plot: Main\n@location: Europe\n"
tokens.setIgnoredKeywords("@plot, @location")
tokens.tokenizeText()
assert tokens._tokens == [
(Tokenizer.T_KEYWORD, 0, "pov: Bod", [], Tokenizer.A_NONE),
(Tokenizer.T_EMPTY, 0, "", [], Tokenizer.A_NONE),
]
# END Test testCoreToken_MetaFormat
@@ -822,6 +1120,7 @@ def testCoreToken_SpecialFormat(mockGUI):
"# Title Two\n\n"
)
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == correctResp
# Command w/Space
@@ -832,6 +1131,7 @@ def testCoreToken_SpecialFormat(mockGUI):
"# Title Two\n\n"
)
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == correctResp
# Trailing Spaces
@@ -842,6 +1142,7 @@ def testCoreToken_SpecialFormat(mockGUI):
"# Title Two\n\n"
)
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == correctResp
# Single Empty Paragraph
@@ -853,6 +1154,7 @@ def testCoreToken_SpecialFormat(mockGUI):
"Some text to go here ...\n\n"
)
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -873,6 +1175,7 @@ def testCoreToken_SpecialFormat(mockGUI):
"Some text to go here ...\n\n"
)
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -890,6 +1193,7 @@ def testCoreToken_SpecialFormat(mockGUI):
"Some text to go here ...\n\n"
)
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -909,6 +1213,7 @@ def testCoreToken_SpecialFormat(mockGUI):
"Some text to go here ...\n\n"
)
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -925,6 +1230,7 @@ def testCoreToken_SpecialFormat(mockGUI):
"Some text to go here ...\n\n"
)
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -941,6 +1247,7 @@ def testCoreToken_SpecialFormat(mockGUI):
"Some text to go here ...\n\n"
)
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -961,6 +1268,7 @@ def testCoreToken_SpecialFormat(mockGUI):
"Some text to go here ...\n\n"
)
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
@@ -980,6 +1288,7 @@ def testCoreToken_SpecialFormat(mockGUI):
"Some text to go here ...\n\n"
)
tokens.tokenizeText()
tokens.doHeaders()
assert tokens._tokens == [
(Tokenizer.T_HEAD1, 1, "Title One", [], Tokenizer.A_PBB | Tokenizer.A_CENTRE),
(Tokenizer.T_EMPTY, 1, "", [], Tokenizer.A_NONE),
+10
View File
@@ -246,6 +246,7 @@ def testCoreToOdt_ConvertHeaders(mockGUI):
# Header 1
odt._text = "# Title\n"
odt.tokenizeText()
odt.doHeaders()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
@@ -259,6 +260,7 @@ def testCoreToOdt_ConvertHeaders(mockGUI):
# Header 2
odt._text = "## Chapter\n"
odt.tokenizeText()
odt.doHeaders()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
@@ -272,6 +274,7 @@ def testCoreToOdt_ConvertHeaders(mockGUI):
# Header 3
odt._text = "### Scene\n"
odt.tokenizeText()
odt.doHeaders()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
@@ -285,6 +288,7 @@ def testCoreToOdt_ConvertHeaders(mockGUI):
# Header 4
odt._text = "#### Section\n"
odt.tokenizeText()
odt.doHeaders()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
@@ -296,8 +300,10 @@ def testCoreToOdt_ConvertHeaders(mockGUI):
)
# Title
odt._isFirst = True
odt._text = "#! Title\n"
odt.tokenizeText()
odt.doHeaders()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
@@ -311,6 +317,7 @@ def testCoreToOdt_ConvertHeaders(mockGUI):
# Unnumbered chapter
odt._text = "##! Prologue\n"
odt.tokenizeText()
odt.doHeaders()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
@@ -605,6 +612,7 @@ def testCoreToOdt_ConvertParagraphs(mockGUI):
"Text\n\n"
)
odt.tokenizeText()
odt.doHeaders()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
@@ -729,6 +737,7 @@ def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
"Text\n\n"
)
odt.tokenizeText()
odt.doHeaders()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
@@ -768,6 +777,7 @@ def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
"Text\n\n"
)
odt.tokenizeText()
odt.doHeaders()
odt.initDocument()
odt.doConvert()
odt.closeDocument()
+13 -5
View File
@@ -479,10 +479,11 @@ def testBuildSettings_Content(qtbot: QtBot, nwGUI: GuiMain):
"""Test the Content Tab of the GuiBuildSettings dialog."""
build = BuildSettings()
build.setValue("text.includeBodyText", False)
build.setValue("text.includeSynopsis", False)
build.setValue("text.includeComments", False)
build.setValue("text.includeKeywords", False)
build.setValue("text.includeBodyText", False)
build.setValue("text.ignoredKeywords", "")
build.setValue("text.addNoteHeadings", False)
@@ -496,28 +497,35 @@ def testBuildSettings_Content(qtbot: QtBot, nwGUI: GuiMain):
assert bSettings.toolStack.currentWidget() is contTab
# Check initial values
assert contTab.incBodyText.isChecked() is False
assert contTab.incSynopsis.isChecked() is False
assert contTab.incComments.isChecked() is False
assert contTab.incKeywords.isChecked() is False
assert contTab.incBodyText.isChecked() is False
assert contTab.ignoredKeywords.text() == ""
assert contTab.addNoteHead.isChecked() is False
# Toggle all
# Toggle switches
contTab.incBodyText.setChecked(True)
contTab.incSynopsis.setChecked(True)
contTab.incComments.setChecked(True)
contTab.incKeywords.setChecked(True)
contTab.incBodyText.setChecked(True)
contTab.addNoteHead.setChecked(True)
# Test cleanup of ignored keywords
contTab.ignoredKeywords.setText("@stuff, @pizza, @object") # First two are invalid
contTab._updateIgnoredKeywords("@custom") # Adding a new should trigger cleanup
assert contTab.ignoredKeywords.text() in ("@custom, @object", "@object, @custom")
# Save values
contTab.saveContent()
assert build.getBool("text.includeBodyText") is True
assert build.getBool("text.includeSynopsis") is True
assert build.getBool("text.includeComments") is True
assert build.getBool("text.includeKeywords") is True
assert build.getBool("text.includeBodyText") is True
assert build.getStr("text.ignoredKeywords") in ("@custom, @object", "@object, @custom")
assert build.getBool("text.addNoteHeadings") is True