Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve test coverage of build tools #1466

Merged
merged 10 commits into from
Jul 18, 2023
48 changes: 27 additions & 21 deletions novelwriter/core/buildsettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
from novelwriter.error import logException
from novelwriter.common import checkUuid, isHandle, jsonEncode
from novelwriter.constants import nwFiles, nwHeadFmt
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -208,15 +207,15 @@ def getBool(self, key: str) -> bool:
def getInt(self, key: str) -> int:
"""Type safe value access for integers."""
value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None)[1]))
if isinstance(value, int):
return value
if isinstance(value, (int, float)):
return int(value)
return 0

def getFloat(self, key: str) -> float:
"""Type safe value access for floats."""
value = self._settings.get(key, SETTINGS_TEMPLATE.get(key, (None, None)[1]))
if isinstance(value, float):
return value
if isinstance(value, (int, float)):
return float(value)
return 0.0

##
Expand Down Expand Up @@ -282,8 +281,8 @@ def setExcluded(self, tHandle: str):
self._changed = True
return

def setSkipRoot(self, tHandle: str, state: bool):
"""Set a specific root folder as skipped or not."""
def setAllowRoot(self, tHandle: str, state: bool):
"""Set a specific root folder as allowed or not."""
if state is True:
self._skipRoot.discard(tHandle)
self._changed = True
Expand All @@ -299,7 +298,7 @@ def setValue(self, key: str, value: str | int | bool | float) -> bool:
definition = SETTINGS_TEMPLATE[key]
if not isinstance(value, definition[0]):
return False
if len(definition) == 4:
if len(definition) == 4 and isinstance(value, (int, float)):
value = min(max(value, definition[2]), definition[3])
self._changed = value != self._settings[key]
self._settings[key] = value
Expand Down Expand Up @@ -328,24 +327,30 @@ def buildItemFilter(
incNotes = bool(self.getBool("filter.includeNotes"))
incInactive = bool(self.getBool("filter.includeInactive"))

postponed = []

def allowRoot(rHandle):
if rHandle in postponed and rHandle in result and rHandle is not None:
result[rHandle] = (True, FilterMode.ROOT)
postponed.remove(rHandle)

for item in project.tree:
tHandle = item.itemHandle
if not tHandle:
continue
if not isinstance(item, NWItem):
result[tHandle] = (False, FilterMode.UNKNOWN)
if tHandle is None:
continue
if item.isInactiveClass() or (item.itemRoot in self._skipRoot):
result[tHandle] = (False, FilterMode.SKIPPED)
continue
if withRoots and item.isRootType():
result[tHandle] = (True, FilterMode.ROOT)
result[tHandle] = (False, FilterMode.SKIPPED)
postponed.append(tHandle)
continue
if not item.isFileType():
result[tHandle] = (False, FilterMode.SKIPPED)
continue
if tHandle in self._included:
result[tHandle] = (True, FilterMode.INCLUDED)
allowRoot(item.itemRoot)
continue
if tHandle in self._excluded:
result[tHandle] = (False, FilterMode.EXCLUDED)
Expand All @@ -361,6 +366,8 @@ def buildItemFilter(
isAllowed = byActive and byLayout

result[tHandle] = (isAllowed, FilterMode.FILTERED)
if isAllowed:
allowRoot(item.itemRoot)

return result

Expand Down Expand Up @@ -401,7 +408,7 @@ def unpack(self, data: dict):
self.setLastPath(data.get("path", None))
self.setLastBuildName(data.get("build", ""))

buildFmt = str(data.get("build", ""))
buildFmt = str(data.get("format", ""))
if buildFmt in nwBuildFmt.__members__:
self.setLastFormat(nwBuildFmt[buildFmt])

Expand Down Expand Up @@ -454,14 +461,13 @@ def getBuild(self, buildID: str) -> BuildSettings | None:
build.unpack(self._builds[buildID])
return build

def setBuild(self, build: BuildSettings) -> bool:
def setBuild(self, build: BuildSettings):
"""Set build settings data in the collection."""
if not isinstance(build, BuildSettings):
return False
buildID = build.buildID
self._builds[buildID] = build.pack()
self._saveCollection()
return True
if isinstance(build, BuildSettings):
buildID = build.buildID
self._builds[buildID] = build.pack()
self._saveCollection()
return

def removeBuild(self, buildID: str):
"""Remove the a build from the collection."""
Expand Down
58 changes: 27 additions & 31 deletions novelwriter/core/docbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from novelwriter import CONFIG
from novelwriter.enum import nwBuildFmt
from novelwriter.error import formatException, logException
from novelwriter.core.item import NWItem
from novelwriter.core.tomd import ToMarkdown
from novelwriter.core.toodt import ToOdt
from novelwriter.core.tohtml import ToHtml
Expand Down Expand Up @@ -96,15 +97,13 @@ def addDocument(self, tHandle: str):

def queueAll(self):
"""Queue all document as defined by the build settings."""
self._queue = []
filtered = self._build.buildItemFilter(self._project)
noteTitles = self._build.getBool("text.addNoteHeadings")
for item in self._project.tree:
if not item.itemHandle:
continue
if filtered.get(item.itemHandle, False):
self._queue.append(item.itemHandle)
elif item.isRootType() and noteTitles:
self._queue.append(item.itemHandle)
return

def iterBuild(self, path: Path, bFormat: nwBuildFmt) -> Iterable[tuple[int, bool]]:
Expand Down Expand Up @@ -155,16 +154,16 @@ def iterBuildHTML(self, path: Path | None, asJson: bool = False) -> Iterable[tup
makeObj = ToHtml(self._project)
filtered = self._setupBuild(makeObj)

if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs()

for i, tHandle in enumerate(self._queue):
self._error = None
if filtered.get(tHandle, (False, 0))[0]:
yield i, self._doBuild(makeObj, tHandle)
else:
yield i, False

if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs()

self._error = None
self._cache = makeObj

Expand Down Expand Up @@ -217,8 +216,6 @@ def iterBuildNWD(self, path: Path | None, asJson: bool = False) -> Iterable[tupl
filtered = self._setupBuild(makeObj)

makeObj.setKeepMarkdown(True)
if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")

for i, tHandle in enumerate(self._queue):
self._error = None
Expand All @@ -227,6 +224,9 @@ def iterBuildNWD(self, path: Path | None, asJson: bool = False) -> Iterable[tupl
else:
yield i, False

if self._build.getBool("format.replaceTabs"):
makeObj.replaceTabs(nSpaces=4, spaceChar=" ")

self._error = None
self._cache = makeObj

Expand Down Expand Up @@ -299,30 +299,26 @@ def _setupBuild(self, bldObj: Tokenizer) -> dict:
def _doBuild(self, bldObj: Tokenizer, tHandle: str, convert: bool = True) -> bool:
"""Build a single document and add it to the build object."""
tItem = self._project.tree[tHandle]
if tItem is None:
self._error = f"Build: Unknown item '{tHandle}'"
logger.error(self._error)
return False

try:
if tItem.isRootType() and not tItem.isNovelLike():
bldObj.addRootHeading(tItem.itemHandle)
if convert:
bldObj.doConvert()
elif tItem.isFileType():
bldObj.setText(tHandle)
bldObj.doPreProcessing()
bldObj.tokenizeText()
bldObj.doHeaders()
if convert:
bldObj.doConvert()
else:
logger.info(f"Build: Skipping '{tHandle}'")
if isinstance(tItem, NWItem):
try:
if tItem.isRootType() and not tItem.isNovelLike():
bldObj.addRootHeading(tHandle)
if convert:
bldObj.doConvert()
elif tItem.isFileType():
bldObj.setText(tHandle)
bldObj.doPreProcessing()
bldObj.tokenizeText()
bldObj.doHeaders()
if convert:
bldObj.doConvert()
else:
logger.info(f"Build: Skipping '{tHandle}'")

except Exception:
self._error = f"Build: Failed to build '{tHandle}'"
logger.error(self._error)
return False
except Exception:
self._error = f"Build: Failed to build '{tHandle}'"
logger.error(self._error)
return False

return True

Expand Down
2 changes: 1 addition & 1 deletion novelwriter/core/tohtml.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ def saveHtmlJson(self, path: str | Path):
},
"text": {
"css": self.getStyleSheet(),
"html": [page.rstrip("\n").split("\n") for page in self.fullHTML],
"html": [t.replace("\t", "	").rstrip().split("\n") for t in self.fullHTML],
}
}
with open(path, mode="w", encoding="utf-8") as fObj:
Expand Down
2 changes: 2 additions & 0 deletions novelwriter/core/tomd.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ def replaceTabs(self, nSpaces: int = 8, spaceChar: str = " "):
"""Replace tabs with spaces."""
spaces = spaceChar*nSpaces
self._fullMD = [p.replace("\t", spaces) for p in self._fullMD]
if self._keepMarkdown:
self._allMarkdown = [p.replace("\t", spaces) for p in self._allMarkdown]
return

##
Expand Down
20 changes: 7 additions & 13 deletions novelwriter/extensions/pagedsidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,34 +59,30 @@ def __init__(self, parent):
return

def setLabelColor(self, color):
"""Set the text color for the labels.
"""
"""Set the text color for the labels."""
if isinstance(color, list):
self._labelCol = QColor(*color)
elif isinstance(color, QColor):
self._labelCol = color
return

def addSeparator(self):
"""Add a spacer widget.
"""
"""Add a spacer widget."""
spacer = QWidget(self)
spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
spacer.setFixedHeight(self._spacerHeight)
self.insertWidget(self._stretchAction, spacer)
return

def addLabel(self, text):
"""Add a new label to the toolbar.
"""
"""Add a new label to the toolbar."""
label = NPagedToolLabel(self, self._labelCol)
label.setText(text)
self.insertWidget(self._stretchAction, label)
return

def addButton(self, text, buttonId=-1):
"""Add a new button to the toolbar.
"""
"""Add a new button to the toolbar."""
button = NPagedToolButton(self)
button.setText(text)

Expand All @@ -99,8 +95,7 @@ def addButton(self, text, buttonId=-1):
return action

def setSelected(self, buttonId):
"""Set the selected button.
"""
"""Set the selected button."""
self._group.button(buttonId).setChecked(True)
return

Expand All @@ -110,8 +105,7 @@ def setSelected(self, buttonId):

@pyqtSlot("QAbstractButton*")
def _buttonClicked(self, button):
"""A button was clicked in the group, emit its id.
"""
"""A button was clicked in the group, emit its id."""
buttonId = self._group.id(button)
if buttonId != -1:
self.buttonClicked.emit(buttonId)
Expand Down Expand Up @@ -155,7 +149,7 @@ def paintEvent(self, event):
height = self.height()
palette = self.palette()

if opt.state & QStyle.State_MouseOver:
if opt.state & QStyle.State_MouseOver == QStyle.State_MouseOver:
backCol = palette.alternateBase()
paint.setBrush(backCol)
paint.setOpacity(0.5)
Expand Down
10 changes: 8 additions & 2 deletions novelwriter/extensions/switchbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,15 @@ def __init__(self, parent: QWidget, baseSize: int):
self._hSwitch = baseSize
self._wSwitch = 2*self._hSwitch
self._sIcon = baseSize
self._widgets = []
self.clear()
return

def clear(self):
"""Rebuild the content of the core widget."""
self._index = 0
self._widgets = []

self._content = QGridLayout()
self._content.setColumnStretch(1, 1)

Expand All @@ -69,6 +73,7 @@ def addLabel(self, text: str):
font.setBold(True)
label.setFont(font)
self._content.addWidget(label, self._index, 0, 1, 3, Qt.AlignLeft)
self._widgets.append(label)
self._bumpIndex()
return

Expand All @@ -87,16 +92,17 @@ def addItem(self, qIcon: QIcon, text: str, identifier: str, default: bool = Fals
switch.toggled.connect(lambda state: self._emitSwitchSignal(identifier, state))
self._content.addWidget(switch, self._index, 2, Qt.AlignRight)

self._widgets.append(switch)
self._bumpIndex()

return

def addSeparator(self):
"""Add a blank entry in the content box.
"""
"""Add a blank entry in the content box."""
spacer = QWidget()
spacer.setFixedHeight(int(0.5*self._sIcon))
self._content.addWidget(spacer, self._index, 0, 1, 3, Qt.AlignLeft)
self._widgets.append(spacer)
self._bumpIndex()
return

Expand Down
Loading