From cba1a2c0626adf48fffb923020915d48e18adc46 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 15 Sep 2023 22:38:39 +0200 Subject: [PATCH 1/4] Make tags in the index case insensitive --- novelwriter/core/index.py | 56 ++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 45b914871..5e02cb831 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -151,11 +151,13 @@ def indexChangedSince(self, checkTime: int | float) -> bool: """Check if the index has changed since a given time.""" return self._indexChange > float(checkTime) - def rootChangedSince(self, rootHandle: str, checkTime: int | float) -> bool: + def rootChangedSince(self, rootHandle: str | None, checkTime: int | float) -> bool: """Check if the index has changed since a given time for a given root item. """ - return self._rootChange.get(rootHandle, self._indexChange) > float(checkTime) + if isinstance(rootHandle, str): + return self._rootChange.get(rootHandle, self._indexChange) > float(checkTime) + return False ## # Load and Save Index to/from File @@ -475,10 +477,11 @@ def checkThese(self, tBits: list[str], nwItem: NWItem) -> list[bool]: return isGood # If we're still here, we check that the references exist - theKey = nwKeyWords.KEY_CLASS[tBits[0]].name + refKey = nwKeyWords.KEY_CLASS[tBits[0]].name for n in range(1, nBits): - if tBits[n] in self._tagsIndex: - isGood[n] = self._tagsIndex.tagClass(tBits[n]) == theKey + tagKey = tBits[n].lower() + if tagKey in self._tagsIndex: + isGood[n] = self._tagsIndex.tagClass(tagKey) == refKey return isGood @@ -586,15 +589,15 @@ def getReferences(self, tHandle: str, sTitle: str | None = None) -> dict[str, li """Extract all references made in a file, and optionally title section. """ - theRefs = {x: [] for x in nwKeyWords.KEY_CLASS} + tRefs = {x: [] for x in nwKeyWords.KEY_CLASS} for rTitle, hItem in self._itemIndex.iterItemHeaders(tHandle): if sTitle is None or sTitle == rTitle: for aTag, refTypes in hItem.references.items(): for refType in refTypes: - if refType in theRefs: - theRefs[refType].append(aTag) + if refType in tRefs: + tRefs[refType].append(self._tagsIndex.tagName(aTag)) - return theRefs + return tRefs def getBackReferenceList(self, tHandle: str) -> dict[str, str]: """Build a list of files referring back to our file, specified @@ -603,17 +606,17 @@ def getBackReferenceList(self, tHandle: str) -> dict[str, str]: if tHandle is None or tHandle not in self._itemIndex: return {} - theRefs = {} - theTags = self._itemIndex.allItemTags(tHandle) - if not theTags: - return theRefs + tRefs = {} + tTags = self._itemIndex.allItemTags(tHandle) + if not tTags: + return tRefs for aHandle, sTitle, hItem in self._itemIndex.iterAllHeaders(): for aTag in hItem.references: - if aTag in theTags and aHandle not in theRefs: - theRefs[aHandle] = sTitle + if aTag in tTags and aHandle not in tRefs: + tRefs[aHandle] = sTitle - return theRefs + return tRefs def getTagSource(self, tagKey: str) -> tuple[str, str]: """Return the source location of a given tag.""" @@ -663,22 +666,26 @@ def clear(self): def add(self, tagKey: str, tHandle: str, sTitle: str, itemClass: nwItemClass): """Add a key to the index and set all values.""" - self._tags[tagKey] = { - "handle": tHandle, "heading": sTitle, "class": itemClass.name + self._tags[tagKey.lower()] = { + "name": tagKey, "handle": tHandle, "heading": sTitle, "class": itemClass.name } return + def tagName(self, tagKey: str) -> str: + """Get the display name of a given tag.""" + return self._tags.get(tagKey.lower(), {}).get("name", None) + def tagHandle(self, tagKey: str) -> str: """Get the handle of a given tag.""" - return self._tags.get(tagKey, {}).get("handle", None) + return self._tags.get(tagKey.lower(), {}).get("handle", None) def tagHeading(self, tagKey: str) -> str: """Get the heading of a given tag.""" - return self._tags.get(tagKey, {}).get("heading", TT_NONE) + return self._tags.get(tagKey.lower(), {}).get("heading", TT_NONE) def tagClass(self, tagKey: str) -> str | None: """Get the class of a given tag.""" - return self._tags.get(tagKey, {}).get("class", None) + return self._tags.get(tagKey.lower(), {}).get("class", None) ## # Pack/Unpack @@ -699,12 +706,16 @@ def unpackData(self, data: dict): for tagKey, tagData in data.items(): if not isinstance(tagKey, str): raise ValueError("tagsIndex keys must be a strings") + if "name" not in tagData: + raise KeyError("A tagIndex item is missing a name entry") if "handle" not in tagData: raise KeyError("A tagIndex item is missing a handle entry") if "heading" not in tagData: raise KeyError("A tagIndex item is missing a heading entry") if "class" not in tagData: raise KeyError("A tagIndex item is missing a class entry") + if tagData["name"].lower() != tagKey.lower(): + raise ValueError("tagsIndex name must match key") if not isHandle(tagData["handle"]): raise ValueError("tagsIndex handle must be a handle") if not isTitleTag(tagData["heading"]): @@ -1127,7 +1138,7 @@ def setSynopsis(self, text: str): def setTag(self, tagKey: str): """Set the tag for references, and make sure it is a string.""" - self._tag = str(tagKey) + self._tag = str(tagKey).lower() return def addReference(self, tagKey: str, refType: str): @@ -1135,6 +1146,7 @@ def addReference(self, tagKey: str, refType: str): associated with. """ if refType in nwKeyWords.VALID_KEYS: + tagKey = tagKey.lower() if tagKey not in self._refs: self._refs[tagKey] = set() self._refs[tagKey].add(refType) From bf592fa706be66cd7ecdf6ad772f3eb29fb27d90 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 15 Sep 2023 22:38:56 +0200 Subject: [PATCH 2/4] Clean up outline view code --- novelwriter/extensions/novelselector.py | 2 +- novelwriter/gui/outline.py | 308 +++++++++++------------- 2 files changed, 144 insertions(+), 166 deletions(-) diff --git a/novelwriter/extensions/novelselector.py b/novelwriter/extensions/novelselector.py index c22012fc0..d903b29cf 100644 --- a/novelwriter/extensions/novelselector.py +++ b/novelwriter/extensions/novelselector.py @@ -62,7 +62,7 @@ def firstHandle(self) -> str | None: # Methods ## - def setHandle(self, tHandle: str, blockSignal: bool = True) -> None: + def setHandle(self, tHandle: str | None, blockSignal: bool = True) -> None: """Set the currently selected handle.""" self._blockSignal = blockSignal if tHandle is None: diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index 4fd271763..84234ccfe 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -59,8 +59,8 @@ class GuiOutlineView(QWidget): loadDocumentTagRequest = pyqtSignal(str, Enum) openDocumentRequest = pyqtSignal(str, Enum, str, bool) - def __init__(self, mainGui): - super().__init__(parent=mainGui) + def __init__(self, parent: QWidget) -> None: + super().__init__(parent=parent) # Build GUI self.outlineTree = GuiOutlineTree(self) @@ -98,38 +98,33 @@ def __init__(self, mainGui): # Methods ## - def updateTheme(self): - """Update theme elements. - """ + def updateTheme(self) -> None: + """Update theme elements.""" self.outlineBar.updateTheme() self.refreshTree() return - def initSettings(self): - """Initialise GUI elements that depend on specific settings. - """ + def initSettings(self) -> None: + """Initialise GUI elements that depend on specific settings.""" self.outlineTree.initSettings() self.outlineData.initSettings() return - def refreshTree(self): - """Refresh the current tree. - """ + def refreshTree(self) -> None: + """Refresh the current tree.""" self.outlineTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("outline")) return - def clearOutline(self): - """Clear project-related GUI content. - """ + def clearOutline(self) -> None: + """Clear project-related GUI content.""" self.outlineData.clearDetails() self.outlineBar.setEnabled(False) return - def openProjectTasks(self): - """Run open project tasks. - """ + def openProjectTasks(self) -> None: + """Run open project tasks.""" lastOutline = SHARED.project.data.getLastHandle("outline") - if not (lastOutline in SHARED.project.tree or lastOutline is None): + if not (lastOutline is None or lastOutline in SHARED.project.tree): lastOutline = SHARED.project.tree.findRoot(nwItemClass.NOVEL) logger.debug("Setting outline tree to root item '%s'", lastOutline) @@ -141,23 +136,23 @@ def openProjectTasks(self): return - def closeProjectTasks(self): + def closeProjectTasks(self) -> None: + """Run closing project tasks.""" self.outlineTree.closeProjectTasks() self.outlineData.updateClasses() self.clearOutline() return - def splitSizes(self): + def splitSizes(self) -> list[int]: + """Get the sizes of the splitter widget.""" return self.splitOutline.sizes() - def setTreeFocus(self): - """Set the focus to the tree widget. - """ + def setTreeFocus(self) -> None: + """Set the focus to the tree widget.""" return self.outlineTree.setFocus() - def treeHasFocus(self): - """Check if the outline tree has focus. - """ + def treeHasFocus(self) -> bool: + """Check if the outline tree has focus.""" return self.outlineTree.hasFocus() ## @@ -165,9 +160,8 @@ def treeHasFocus(self): ## @pyqtSlot(str) - def updateRootItem(self, tHandle): - """Should be called whenever a root folders changes. - """ + def updateRootItem(self, tHandle: str) -> None: + """Handle tasks whenever a root folders changes.""" self.outlineBar.populateNovelList() self.outlineData.updateClasses() return @@ -177,7 +171,7 @@ def updateRootItem(self, tHandle): ## @pyqtSlot() - def _updateMenuColumns(self): + def _updateMenuColumns(self) -> None: """Trigger an update of the toggled state of the column menu checkboxes whenever a signal is received that the hidden state of columns has changed. @@ -186,18 +180,16 @@ def _updateMenuColumns(self): return @pyqtSlot(str) - def _tagClicked(self, link): - """Capture the click of a tag in the details panel. - """ + def _tagClicked(self, link: str) -> None: + """Capture the click of a tag in the details panel.""" if link: self.loadDocumentTagRequest.emit(link, nwDocMode.VIEW) return @pyqtSlot(str) - def _rootItemChanged(self, handle): - """The root novel handle has changed or needs to be refreshed. - """ - self.outlineTree.refreshTree(rootHandle=(handle or None), overRide=True) + def _rootItemChanged(self, tHandle) -> None: + """Handle root novel changed or needs to be refreshed.""" + self.outlineTree.refreshTree(rootHandle=(tHandle or None), overRide=True) return # END Class GuiOutlineView @@ -208,8 +200,8 @@ class GuiOutlineToolBar(QToolBar): loadNovelRootRequest = pyqtSignal(str) viewColumnToggled = pyqtSignal(bool, Enum) - def __init__(self, theOutline): - super().__init__(parent=theOutline) + def __init__(self, outlineView: GuiOutlineView) -> None: + super().__init__(parent=outlineView) logger.debug("Create: GuiOutlineToolBar") @@ -221,7 +213,7 @@ def __init__(self, theOutline): self.setContentsMargins(0, 0, 0, 0) stretch = QWidget(self) - stretch.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) # Novel Selector self.novelLabel = QLabel(self.tr("Outline of")) @@ -243,7 +235,7 @@ def __init__(self, theOutline): self.tbColumns = QToolButton(self) self.tbColumns.setMenu(self.mColumns) - self.tbColumns.setPopupMode(QToolButton.InstantPopup) + self.tbColumns.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) # Assemble self.addWidget(self.novelLabel) @@ -263,32 +255,26 @@ def __init__(self, theOutline): # Methods ## - def updateTheme(self): - """Update theme elements. - """ + def updateTheme(self) -> None: + """Update theme elements.""" self.setStyleSheet("QToolBar {border: 0px;}") - self.novelValue.updateList(includeAll=True) self.aRefresh.setIcon(SHARED.theme.getIcon("refresh")) self.tbColumns.setIcon(SHARED.theme.getIcon("menu")) - return - def populateNovelList(self): - """Reload the content of the novel list. - """ + def populateNovelList(self) -> None: + """Reload the content of the novel list.""" self.novelValue.updateList(includeAll=True) return - def setCurrentRoot(self, rootHandle): - """Set the current active root handle. - """ + def setCurrentRoot(self, rootHandle: str | None) -> None: + """Set the current active root handle.""" self.novelValue.setHandle(rootHandle) return - def setColumnHiddenState(self, hiddenState): - """Forward the change of column hidden states to the menu. - """ + def setColumnHiddenState(self, hiddenState: dict[nwOutline, bool]) -> None: + """Forward the change of column hidden states to the menu.""" self.mColumns.setHiddenState(hiddenState) return @@ -297,16 +283,14 @@ def setColumnHiddenState(self, hiddenState): ## @pyqtSlot(str) - def _novelValueChanged(self, tHandle): - """Emit a signal containing the handle of the selected item. - """ + def _novelValueChanged(self, tHandle: str) -> None: + """Emit a signal containing the handle of the selected item.""" self.loadNovelRootRequest.emit(tHandle) return @pyqtSlot() - def _refreshRequested(self): - """Emit a signal containing the handle of the selected item. - """ + def _refreshRequested(self) -> None: + """Emit a signal containing the handle of the selected item.""" self.loadNovelRootRequest.emit(self.novelValue.handle) return @@ -361,7 +345,7 @@ class GuiOutlineTree(QTreeWidget): hiddenStateChanged = pyqtSignal() activeItemChanged = pyqtSignal(str, str) - def __init__(self, outlineView): + def __init__(self, outlineView: GuiOutlineView) -> None: super().__init__(parent=outlineView) logger.debug("Create: GuiOutlineTree") @@ -369,9 +353,9 @@ def __init__(self, outlineView): self.outlineView = outlineView self.setUniformRowHeights(True) - self.setFrameStyle(QFrame.NoFrame) - self.setSelectionBehavior(QAbstractItemView.SelectRows) - self.setSelectionMode(QAbstractItemView.SingleSelection) + self.setFrameStyle(QFrame.Shape.NoFrame) + self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) self.setExpandsOnDoubleClick(False) self.setDragEnabled(False) self.itemDoubleClicked.connect(self._treeDoubleClick) @@ -431,23 +415,19 @@ def hiddenColumns(self): # Methods ## - def initSettings(self): - """Set or update outline settings. - """ - # Scroll bars + def initSettings(self) -> None: + """Set or update outline settings.""" if CONFIG.hideVScroll: - self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) else: - self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) - + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) if CONFIG.hideHScroll: - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) else: - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) - + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) return - def clearContent(self): + def clearContent(self) -> None: """Clear the tree and header and set the default values for the columns arrays. """ @@ -455,10 +435,10 @@ def clearContent(self): self.setColumnCount(1) self.setHeaderLabel(trConst(nwLabels.OUTLINE_COLS[nwOutline.TITLE])) - self._treeOrder = [] - self._colWidth = {} - self._colHidden = {} - self._colIdx = {} + self._treeOrder: list[nwOutline] = [] + self._colWidth: dict[nwOutline, int] = {} + self._colHidden: dict[nwOutline, bool] = {} + self._colIdx: dict[nwOutline, int] = {} self._treeNCols = 0 for hItem in nwOutline: @@ -470,7 +450,8 @@ def clearContent(self): return - def refreshTree(self, rootHandle=None, overRide=False, novelChanged=False): + def refreshTree(self, rootHandle: str | None = None, + overRide: bool = False, novelChanged: bool = False) -> None: """Called whenever the Outline tab is activated and controls what data to load, and if necessary, force a rebuild of the tree. @@ -494,15 +475,14 @@ def refreshTree(self, rootHandle=None, overRide=False, novelChanged=False): return - def closeProjectTasks(self): - """Called before a project is closed. - """ + def closeProjectTasks(self) -> None: + """Called before a project is closed.""" self._saveHeaderState() self.clearContent() self._firstView = True return - def getSelectedHandle(self): + def getSelectedHandle(self) -> tuple[str | None, str | None]: """Get the currently selected handle. If multiple items are selected, return the first. """ @@ -518,7 +498,7 @@ def getSelectedHandle(self): ## @pyqtSlot("QTreeWidgetItem*", int) - def _treeDoubleClick(self, tItem, tCol): + def _treeDoubleClick(self, tItem: QTreeWidgetItem, tCol: int) -> None: """Extract the handle and line number of the title double- clicked, and send it to the main gui class for opening in the document editor. @@ -530,7 +510,7 @@ def _treeDoubleClick(self, tItem, tCol): return @pyqtSlot() - def _itemSelected(self): + def _itemSelected(self) -> None: """Extract the handle and line number of the currently selected title, and send it to the details panel. """ @@ -542,7 +522,7 @@ def _itemSelected(self): return @pyqtSlot(int, int, int) - def _columnMoved(self, logIdx, oldVisualIdx, newVisualIdx): + def _columnMoved(self, logIdx: int, oldVisualIdx: int, newVisualIdx: int) -> None: """Make sure the order array is up to date with the actual order of the columns. """ @@ -551,12 +531,12 @@ def _columnMoved(self, logIdx, oldVisualIdx, newVisualIdx): return @pyqtSlot(bool, Enum) - def menuColumnToggled(self, isChecked, theItem): + def menuColumnToggled(self, isChecked: bool, hItem: nwOutline) -> None: """Receive the changes to column visibility forwarded by the column selection menu. """ - if theItem in self._colIdx: - self.setColumnHidden(self._colIdx[theItem], not isChecked) + if hItem in self._colIdx: + self.setColumnHidden(self._colIdx[hItem], not isChecked) self._saveHeaderState() return @@ -600,7 +580,7 @@ def _loadHeaderState(self): return - def _saveHeaderState(self): + def _saveHeaderState(self) -> None: """Save the state of the main tree header, that is, column order, column width and column hidden state. We don't want to save the current width of hidden columns though. This preserves @@ -627,7 +607,7 @@ def _saveHeaderState(self): return - def _populateTree(self, rootHandle): + def _populateTree(self, rootHandle: str | None) -> None: """Build the tree based on the project index, and the header based on the defined constants, default values and user selected width, order and hidden state. All columns are populated, even @@ -653,22 +633,25 @@ def _populateTree(self, rootHandle): headItem = self.headerItem() if isinstance(headItem, QTreeWidgetItem): - headItem.setTextAlignment(self._colIdx[nwOutline.CCOUNT], Qt.AlignRight) - headItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], Qt.AlignRight) - headItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], Qt.AlignRight) + headItem.setTextAlignment( + self._colIdx[nwOutline.CCOUNT], Qt.AlignmentFlag.AlignRight) + headItem.setTextAlignment( + self._colIdx[nwOutline.WCOUNT], Qt.AlignmentFlag.AlignRight) + headItem.setTextAlignment( + self._colIdx[nwOutline.PCOUNT], Qt.AlignmentFlag.AlignRight) novStruct = SHARED.project.index.novelStructure(rootHandle=rootHandle, skipExcl=True) for _, tHandle, sTitle, novIdx in novStruct: iLevel = nwHeaders.H_LEVEL.get(novIdx.level, 0) - if iLevel == 0: + nwItem = SHARED.project.tree[tHandle] + if iLevel == 0 or nwItem is None: continue trItem = QTreeWidgetItem() - nwItem = SHARED.project.tree[tHandle] hDec = SHARED.theme.getHeaderDecoration(iLevel) - trItem.setData(self._colIdx[nwOutline.TITLE], Qt.DecorationRole, hDec) + trItem.setData(self._colIdx[nwOutline.TITLE], Qt.ItemDataRole.DecorationRole, hDec) trItem.setText(self._colIdx[nwOutline.TITLE], novIdx.title) trItem.setData(self._colIdx[nwOutline.TITLE], self.D_HANDLE, tHandle) trItem.setData(self._colIdx[nwOutline.TITLE], self.D_TITLE, sTitle) @@ -681,9 +664,9 @@ def _populateTree(self, rootHandle): trItem.setText(self._colIdx[nwOutline.CCOUNT], f"{novIdx.charCount:n}") trItem.setText(self._colIdx[nwOutline.WCOUNT], f"{novIdx.wordCount:n}") trItem.setText(self._colIdx[nwOutline.PCOUNT], f"{novIdx.paraCount:n}") - trItem.setTextAlignment(self._colIdx[nwOutline.CCOUNT], Qt.AlignRight) - trItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], Qt.AlignRight) - trItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], Qt.AlignRight) + trItem.setTextAlignment(self._colIdx[nwOutline.CCOUNT], Qt.AlignmentFlag.AlignRight) + trItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], Qt.AlignmentFlag.AlignRight) + trItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], Qt.AlignmentFlag.AlignRight) refs = SHARED.project.index.getReferences(tHandle, sTitle) trItem.setText(self._colIdx[nwOutline.POV], ", ".join(refs[nwKeyWords.POV_KEY])) @@ -709,8 +692,8 @@ class GuiOutlineHeaderMenu(QMenu): columnToggled = pyqtSignal(bool, Enum) - def __init__(self, theOutline): - super().__init__(parent=theOutline) + def __init__(self, outlineToolBar: GuiOutlineToolBar) -> None: + super().__init__(parent=outlineToolBar) self.acceptToggle = True @@ -731,7 +714,7 @@ def __init__(self, theOutline): return - def setHiddenState(self, hiddenState): + def setHiddenState(self, hiddenState: dict[nwOutline, bool]) -> None: """Overwrite the checked state of the columns as the inverse of the hidden state. Skip the TITLE column as it cannot be hidden. """ @@ -760,12 +743,12 @@ class GuiOutlineDetails(QScrollArea): itemTagClicked = pyqtSignal(str) - def __init__(self, theOutline): - super().__init__(parent=theOutline) + def __init__(self, outlineView: GuiOutlineView) -> None: + super().__init__(parent=outlineView) logger.debug("Create: GuiOutlineDetails") - self.theOutline = theOutline + self.outlineView = outlineView # Sizes minTitle = 30*SHARED.theme.textNWidth @@ -878,23 +861,26 @@ def tagClicked(link): # Selected Item Details self.mainGroup = QGroupBox(self.tr("Title Details"), self) - self.mainForm = QGridLayout() + self.mainForm = QGridLayout() self.mainGroup.setLayout(self.mainForm) - self.mainForm.addWidget(self.titleLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.mainForm.addWidget(self.titleValue, 0, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.mainForm.addWidget(self.cCLabel, 0, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.mainForm.addWidget(self.cCValue, 0, 3, 1, 1, Qt.AlignTop | Qt.AlignRight) - self.mainForm.addWidget(self.fileLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.mainForm.addWidget(self.fileValue, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.mainForm.addWidget(self.wCLabel, 1, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.mainForm.addWidget(self.wCValue, 1, 3, 1, 1, Qt.AlignTop | Qt.AlignRight) - self.mainForm.addWidget(self.itemLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.mainForm.addWidget(self.itemValue, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.mainForm.addWidget(self.pCLabel, 2, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.mainForm.addWidget(self.pCValue, 2, 3, 1, 1, Qt.AlignTop | Qt.AlignRight) - self.mainForm.addWidget(self.synopLabel, 3, 0, 1, 4, Qt.AlignTop | Qt.AlignLeft) - self.mainForm.addLayout(self.synopLWrap, 4, 0, 1, 4, Qt.AlignTop | Qt.AlignLeft) + topLeft = Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft + topRight = Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight + + self.mainForm.addWidget(self.titleLabel, 0, 0, 1, 1, topLeft) + self.mainForm.addWidget(self.titleValue, 0, 1, 1, 1, topLeft) + self.mainForm.addWidget(self.cCLabel, 0, 2, 1, 1, topLeft) + self.mainForm.addWidget(self.cCValue, 0, 3, 1, 1, topRight) + self.mainForm.addWidget(self.fileLabel, 1, 0, 1, 1, topLeft) + self.mainForm.addWidget(self.fileValue, 1, 1, 1, 1, topLeft) + self.mainForm.addWidget(self.wCLabel, 1, 2, 1, 1, topLeft) + self.mainForm.addWidget(self.wCValue, 1, 3, 1, 1, topRight) + self.mainForm.addWidget(self.itemLabel, 2, 0, 1, 1, topLeft) + self.mainForm.addWidget(self.itemValue, 2, 1, 1, 1, topLeft) + self.mainForm.addWidget(self.pCLabel, 2, 2, 1, 1, topLeft) + self.mainForm.addWidget(self.pCValue, 2, 3, 1, 1, topRight) + self.mainForm.addWidget(self.synopLabel, 3, 0, 1, 4, topLeft) + self.mainForm.addLayout(self.synopLWrap, 4, 0, 1, 4, topLeft) self.mainForm.setColumnStretch(1, 1) self.mainForm.setRowStretch(4, 1) @@ -906,24 +892,24 @@ def tagClicked(link): self.tagsForm = QGridLayout() self.tagsGroup.setLayout(self.tagsForm) - self.tagsForm.addWidget(self.povKeyLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addLayout(self.povKeyLWrap, 0, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addWidget(self.focKeyLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addLayout(self.focKeyLWrap, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addWidget(self.chrKeyLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addLayout(self.chrKeyLWrap, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addWidget(self.pltKeyLabel, 3, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addLayout(self.pltKeyLWrap, 3, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addWidget(self.timKeyLabel, 4, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addLayout(self.timKeyLWrap, 4, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addWidget(self.wldKeyLabel, 5, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addLayout(self.wldKeyLWrap, 5, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addWidget(self.objKeyLabel, 6, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addLayout(self.objKeyLWrap, 6, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addWidget(self.entKeyLabel, 7, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addLayout(self.entKeyLWrap, 7, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addWidget(self.cstKeyLabel, 8, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) - self.tagsForm.addLayout(self.cstKeyLWrap, 8, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addWidget(self.povKeyLabel, 0, 0, 1, 1, topLeft) + self.tagsForm.addLayout(self.povKeyLWrap, 0, 1, 1, 1, topLeft) + self.tagsForm.addWidget(self.focKeyLabel, 1, 0, 1, 1, topLeft) + self.tagsForm.addLayout(self.focKeyLWrap, 1, 1, 1, 1, topLeft) + self.tagsForm.addWidget(self.chrKeyLabel, 2, 0, 1, 1, topLeft) + self.tagsForm.addLayout(self.chrKeyLWrap, 2, 1, 1, 1, topLeft) + self.tagsForm.addWidget(self.pltKeyLabel, 3, 0, 1, 1, topLeft) + self.tagsForm.addLayout(self.pltKeyLWrap, 3, 1, 1, 1, topLeft) + self.tagsForm.addWidget(self.timKeyLabel, 4, 0, 1, 1, topLeft) + self.tagsForm.addLayout(self.timKeyLWrap, 4, 1, 1, 1, topLeft) + self.tagsForm.addWidget(self.wldKeyLabel, 5, 0, 1, 1, topLeft) + self.tagsForm.addLayout(self.wldKeyLWrap, 5, 1, 1, 1, topLeft) + self.tagsForm.addWidget(self.objKeyLabel, 6, 0, 1, 1, topLeft) + self.tagsForm.addLayout(self.objKeyLWrap, 6, 1, 1, 1, topLeft) + self.tagsForm.addWidget(self.entKeyLabel, 7, 0, 1, 1, topLeft) + self.tagsForm.addLayout(self.entKeyLWrap, 7, 1, 1, 1, topLeft) + self.tagsForm.addWidget(self.cstKeyLabel, 8, 0, 1, 1, topLeft) + self.tagsForm.addLayout(self.cstKeyLWrap, 8, 1, 1, 1, topLeft) self.tagsForm.setColumnStretch(1, 1) self.tagsForm.setRowStretch(8, 1) @@ -939,10 +925,10 @@ def tagClicked(link): self.outerWidget.setLayout(self.outerBox) self.setWidget(self.outerWidget) - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) - self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.setWidgetResizable(True) - self.setFrameStyle(QFrame.NoFrame) + self.setFrameStyle(QFrame.Shape.NoFrame) self.initSettings() @@ -950,27 +936,21 @@ def tagClicked(link): return - def initSettings(self): - """Set or update outline settings. - """ - # Scroll bars + def initSettings(self) -> None: + """Set or update outline settings.""" if CONFIG.hideVScroll: - self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) else: - self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) - + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) if CONFIG.hideHScroll: - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) else: - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) - + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.updateClasses() - return - def clearDetails(self): - """Clear all the data labels. - """ + def clearDetails(self) -> None: + """Clear all the data labels.""" self.titleLabel.setText("%s" % self.tr("Title")) self.titleValue.setText("") self.fileValue.setText("") @@ -996,7 +976,7 @@ def clearDetails(self): ## @pyqtSlot(str, str) - def showItem(self, tHandle, sTitle): + def showItem(self, tHandle: str, sTitle: str) -> bool: """Update the content of the tree with the given handle and line number pointing to a header. """ @@ -1041,9 +1021,8 @@ def showItem(self, tHandle, sTitle): return True @pyqtSlot() - def updateClasses(self): - """Update the visibility status of class details. - """ + def updateClasses(self) -> None: + """Update the visibility status of class details.""" usedClasses = SHARED.project.tree.rootClasses() pltVisible = nwItemClass.PLOT in usedClasses @@ -1069,9 +1048,8 @@ def updateClasses(self): return @staticmethod - def _formatTags(refs, key): - """Convert a list of tags into a list of clickable tag links. - """ + def _formatTags(refs: dict[str, list[str]], key: str) -> str: + """Convert a list of tags into a list of clickable tag links.""" return ", ".join( [f"{tag}" for tag in refs.get(key, [])] ) From 29518a33f29d591f4827ac20e657767d26f5c41c Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 16 Sep 2023 10:10:55 +0200 Subject: [PATCH 3/4] Fix index tests --- novelwriter/core/index.py | 59 +- .../coreIndex_LoadSave_tagsIndex.json | 28 +- tests/test_core/test_core_index.py | 753 ++++++++++-------- 3 files changed, 443 insertions(+), 397 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 5e02cb831..a999e65cc 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -240,50 +240,50 @@ def saveIndex(self) -> bool: # Index Building ## - def scanText(self, tHandle: str, theText: str) -> bool: + def scanText(self, tHandle: str, text: str) -> bool: """Scan a piece of text associated with a handle. This will update the indices accordingly. This function takes the handle and text as separate inputs as we want to primarily scan the files before we save them, in which case we already have the text. """ - theItem = self._project.tree[tHandle] - if theItem is None: + tItem = self._project.tree[tHandle] + if tItem is None: logger.info("Not indexing unknown item '%s'", tHandle) return False - if not theItem.isFileType(): + if not tItem.isFileType(): logger.info("Not indexing non-file item '%s'", tHandle) return False # Keep a record of existing tags, and create a new item entry itemTags = dict.fromkeys(self._itemIndex.allItemTags(tHandle), False) - self._itemIndex.add(tHandle, theItem) + self._itemIndex.add(tHandle, tItem) # Run word counter for the whole text - cC, wC, pC = countWords(theText) - theItem.setCharCount(cC) - theItem.setWordCount(wC) - theItem.setParaCount(pC) + cC, wC, pC = countWords(text) + tItem.setCharCount(cC) + tItem.setWordCount(wC) + tItem.setParaCount(pC) # If the file's meta data is missing, or the file is out of the # main project, we don't index the content - if theItem.itemLayout == nwItemLayout.NO_LAYOUT: + if tItem.itemLayout == nwItemLayout.NO_LAYOUT: logger.info("Not indexing no-layout item '%s'", tHandle) return False - if theItem.itemParent is None: + if tItem.itemParent is None: logger.info("Not indexing orphaned item '%s'", tHandle) return False logger.debug("Indexing item with handle '%s'", tHandle) - if theItem.isInactiveClass(): - self._scanInactive(theItem, theText) + if tItem.isInactiveClass(): + self._scanInactive(tItem, text) else: - self._scanActive(tHandle, theItem, theText, itemTags) + self._scanActive(tHandle, tItem, text, itemTags) # Update timestamps for index changes nowTime = time() self._indexChange = nowTime - self._rootChange[theItem.itemRoot] = nowTime + self._rootChange[tItem.itemRoot] = nowTime return True @@ -479,9 +479,8 @@ def checkThese(self, tBits: list[str], nwItem: NWItem) -> list[bool]: # If we're still here, we check that the references exist refKey = nwKeyWords.KEY_CLASS[tBits[0]].name for n in range(1, nBits): - tagKey = tBits[n].lower() - if tagKey in self._tagsIndex: - isGood[n] = self._tagsIndex.tagClass(tagKey) == refKey + if tBits[n] in self._tagsIndex: + isGood[n] = self._tagsIndex.tagClass(tBits[n]) == refKey return isGood @@ -641,30 +640,30 @@ class TagsIndex: __slots__ = ("_tags") - def __init__(self): + def __init__(self) -> None: self._tags: dict[str, dict] = {} return - def __contains__(self, tagKey): - return tagKey in self._tags + def __contains__(self, tagKey: str) -> bool: + return tagKey.lower() in self._tags - def __delitem__(self, tagKey): - self._tags.pop(tagKey, None) + def __delitem__(self, tagKey: str) -> None: + self._tags.pop(tagKey.lower(), None) return - def __getitem__(self, tagKey): - return self._tags.get(tagKey, None) + def __getitem__(self, tagKey: str) -> dict | None: + return self._tags.get(tagKey.lower(), None) ## # Methods ## - def clear(self): + def clear(self) -> None: """Clear the index.""" self._tags = {} return - def add(self, tagKey: str, tHandle: str, sTitle: str, itemClass: nwItemClass): + def add(self, tagKey: str, tHandle: str, sTitle: str, itemClass: nwItemClass) -> None: """Add a key to the index and set all values.""" self._tags[tagKey.lower()] = { "name": tagKey, "handle": tHandle, "heading": sTitle, "class": itemClass.name @@ -673,7 +672,7 @@ def add(self, tagKey: str, tHandle: str, sTitle: str, itemClass: nwItemClass): def tagName(self, tagKey: str) -> str: """Get the display name of a given tag.""" - return self._tags.get(tagKey.lower(), {}).get("name", None) + return self._tags.get(tagKey.lower(), {}).get("name", "") def tagHandle(self, tagKey: str) -> str: """Get the handle of a given tag.""" @@ -695,7 +694,7 @@ def packData(self) -> dict: """Pack all the data of the tags into a single dictionary.""" return self._tags - def unpackData(self, data: dict): + def unpackData(self, data: dict) -> None: """Iterate through the tagsIndex loaded from cache and check that it's valid. """ @@ -714,7 +713,7 @@ def unpackData(self, data: dict): raise KeyError("A tagIndex item is missing a heading entry") if "class" not in tagData: raise KeyError("A tagIndex item is missing a class entry") - if tagData["name"].lower() != tagKey.lower(): + if tagData["name"].lower() != tagKey: raise ValueError("tagsIndex name must match key") if not isHandle(tagData["handle"]): raise ValueError("tagsIndex handle must be a handle") diff --git a/tests/reference/coreIndex_LoadSave_tagsIndex.json b/tests/reference/coreIndex_LoadSave_tagsIndex.json index 63be86585..014346aff 100644 --- a/tests/reference/coreIndex_LoadSave_tagsIndex.json +++ b/tests/reference/coreIndex_LoadSave_tagsIndex.json @@ -1,8 +1,8 @@ { "novelWriter.tagsIndex": { - "Bod": {"handle": "4c4f28287af27", "heading": "T0001", "class": "CHARACTER"}, - "Main": {"handle": "2426c6f0ca922", "heading": "T0001", "class": "PLOT"}, - "Europe": {"handle": "04468803b92e1", "heading": "T0001", "class": "WORLD"} + "bod": {"name": "Bod", "handle": "4c4f28287af27", "heading": "T0001", "class": "CHARACTER"}, + "main": {"name": "Main", "handle": "2426c6f0ca922", "heading": "T0001", "class": "PLOT"}, + "europe": {"name": "Europe", "handle": "04468803b92e1", "heading": "T0001", "class": "WORLD"} }, "novelWriter.itemIndex": { "7a992350f3eb6": { @@ -30,7 +30,7 @@ "T0001": {"level": "H2", "title": "Chapter One", "line": 1, "tag": "", "cCount": 419, "wCount": 67, "pCount": 1, "synopsis": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam."} }, "references": { - "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"} } }, "88243afbe5ed8": { @@ -39,7 +39,7 @@ "T0002": {"level": "H4", "title": "Scene One, Section Two", "line": 13, "tag": "", "cCount": 1561, "wCount": 230, "pCount": 2, "synopsis": ""} }, "references": { - "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"} } }, "f96ec11c6a3da": { @@ -48,7 +48,7 @@ "T0002": {"level": "H4", "title": "Scene Two, Section Two", "line": 15, "tag": "", "cCount": 2009, "wCount": 301, "pCount": 3, "synopsis": ""} }, "references": { - "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"} } }, "846352075de7d": { @@ -61,7 +61,7 @@ "T0001": {"level": "H2", "title": "Chapter Two", "line": 1, "tag": "", "cCount": 477, "wCount": 70, "pCount": 1, "synopsis": "Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue."} }, "references": { - "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"} } }, "eb103bc70c90c": { @@ -69,7 +69,7 @@ "T0001": {"level": "H3", "title": "Scene Three", "line": 1, "tag": "", "cCount": 3006, "wCount": 439, "pCount": 4, "synopsis": "Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos."} }, "references": { - "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"} } }, "f8c0562e50f1b": { @@ -77,7 +77,7 @@ "T0001": {"level": "H3", "title": "Scene Four", "line": 1, "tag": "", "cCount": 3839, "wCount": 563, "pCount": 6, "synopsis": "Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo."} }, "references": { - "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"} } }, "47666c91c7ccf": { @@ -85,25 +85,25 @@ "T0001": {"level": "H3", "title": "Scene Five", "line": 1, "tag": "", "cCount": 3644, "wCount": 543, "pCount": 5, "synopsis": "Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus."} }, "references": { - "T0001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"} + "T0001": {"bod": "@pov", "main": "@plot", "europe": "@location"} } }, "4c4f28287af27": { "headings": { - "T0001": {"level": "H1", "title": "Nobody Owens", "line": 1, "tag": "Bod", "cCount": 1864, "wCount": 284, "pCount": 3, "synopsis": ""} + "T0001": {"level": "H1", "title": "Nobody Owens", "line": 1, "tag": "bod", "cCount": 1864, "wCount": 284, "pCount": 3, "synopsis": ""} }, "references": { - "T0001": {"Main": "@plot"} + "T0001": {"main": "@plot"} } }, "2426c6f0ca922": { "headings": { - "T0001": {"level": "H1", "title": "Main Plot", "line": 1, "tag": "Main", "cCount": 1369, "wCount": 195, "pCount": 2, "synopsis": ""} + "T0001": {"level": "H1", "title": "Main Plot", "line": 1, "tag": "main", "cCount": 1369, "wCount": 195, "pCount": 2, "synopsis": ""} } }, "04468803b92e1": { "headings": { - "T0001": {"level": "H1", "title": "Ancient Europe", "line": 1, "tag": "Europe", "cCount": 1770, "wCount": 259, "pCount": 3, "synopsis": ""} + "T0001": {"level": "H1", "title": "Ancient Europe", "line": 1, "tag": "europe", "cCount": 1770, "wCount": 259, "pCount": 3, "synopsis": ""} } } } diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index b9e107e58..40557584c 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -25,6 +25,7 @@ from shutil import copyfile from mocked import causeException +from novelwriter.core.item import NWItem from tools import C, buildTestProject, cmpFiles, writeFile from novelwriter.enum import nwItemClass, nwItemLayout @@ -42,11 +43,11 @@ def testCoreIndex_LoadSave(monkeypatch, prjLipsum, mockGUI, tstPaths): testFile = tstPaths.outDir / "coreIndex_LoadSave_tagsIndex.json" compFile = tstPaths.refDir / "coreIndex_LoadSave_tagsIndex.json" - theProject = NWProject() - assert theProject.openProject(prjLipsum) + project = NWProject() + assert project.openProject(prjLipsum) - theIndex = NWIndex(theProject) - assert repr(theIndex) == "" + index = NWIndex(project) + assert repr(index) == "" notIndexable = { "b3643d0f92e32": False, # Novel ROOT @@ -56,98 +57,98 @@ def testCoreIndex_LoadSave(monkeypatch, prjLipsum, mockGUI, tstPaths): "6c6afb1247750": False, # Plot ROOT "60bdf227455cc": False, # World ROOT } - for tItem in theProject.tree: - assert theIndex.reIndexHandle(tItem.itemHandle) is notIndexable.get(tItem.itemHandle, True) + for tItem in project.tree: + assert index.reIndexHandle(tItem.itemHandle) is notIndexable.get(tItem.itemHandle, True) - assert theIndex.reIndexHandle(None) is False + assert index.reIndexHandle(None) is False # No folder for saving with monkeypatch.context() as mp: mp.setattr("novelwriter.core.storage.NWStorage.getMetaFile", lambda *a: None) - assert theIndex.saveIndex() is False + assert index.saveIndex() is False # Make the save fail with monkeypatch.context() as mp: mp.setattr("builtins.open", causeException) - assert theIndex.saveIndex() is False + assert index.saveIndex() is False # Make the save pass - assert theIndex.saveIndex() is True + assert index.saveIndex() is True # Take a copy of the index - tagIndex = str(theIndex._tagsIndex.packData()) - itemsIndex = str(theIndex._itemIndex.packData()) + tagIndex = str(index._tagsIndex.packData()) + itemsIndex = str(index._itemIndex.packData()) # Delete a handle - assert theIndex._tagsIndex["Bod"] is not None - assert theIndex._itemIndex["4c4f28287af27"] is not None - theIndex.deleteHandle("4c4f28287af27") - assert theIndex._tagsIndex["Bod"] is None - assert theIndex._itemIndex["4c4f28287af27"] is None + assert index._tagsIndex["Bod"] is not None + assert index._itemIndex["4c4f28287af27"] is not None + index.deleteHandle("4c4f28287af27") + assert index._tagsIndex["Bod"] is None + assert index._itemIndex["4c4f28287af27"] is None # Clear the index - theIndex.clearIndex() - assert theIndex._tagsIndex._tags == {} - assert theIndex._itemIndex._items == {} + index.clearIndex() + assert index._tagsIndex._tags == {} + assert index._itemIndex._items == {} # No folder for loading with monkeypatch.context() as mp: mp.setattr("novelwriter.core.storage.NWStorage.getMetaFile", lambda *a: None) - assert theIndex.loadIndex() is False + assert index.loadIndex() is False # Make the load fail with monkeypatch.context() as mp: mp.setattr(json, "load", causeException) - assert theIndex.loadIndex() is False - assert theIndex.indexBroken is True + assert index.loadIndex() is False + assert index.indexBroken is True # Make the load pass - assert theIndex.loadIndex() is True - assert theIndex.indexBroken is False + assert index.loadIndex() is True + assert index.indexBroken is False - assert str(theIndex._tagsIndex.packData()) == tagIndex - assert str(theIndex._itemIndex.packData()) == itemsIndex + assert str(index._tagsIndex.packData()) == tagIndex + assert str(index._itemIndex.packData()) == itemsIndex # Rebuild index - theIndex.clearIndex() - theIndex.rebuildIndex() + index.clearIndex() + index.rebuildIndex() - assert str(theIndex._tagsIndex.packData()) == tagIndex - assert str(theIndex._itemIndex.packData()) == itemsIndex + assert str(index._tagsIndex.packData()) == tagIndex + assert str(index._itemIndex.packData()) == itemsIndex # Check File copyfile(projFile, testFile) assert cmpFiles(testFile, compFile) - # Write an emtpy index file and load it + # Write an empty index file and load it writeFile(projFile, "{}") - assert theIndex.loadIndex() is False - assert theIndex.indexBroken is True + assert index.loadIndex() is False + assert index.indexBroken is True # Write an index file that passes loading, but is still empty writeFile(projFile, '{"novelWriter.tagsIndex": {}, "novelWriter.itemIndex": {}}') - assert theIndex.loadIndex() is True - assert theIndex.indexBroken is False + assert index.loadIndex() is True + assert index.indexBroken is False # Check that the index is re-populated - assert "04468803b92e1" in theIndex._itemIndex - assert "2426c6f0ca922" in theIndex._itemIndex - assert "441420a886d82" in theIndex._itemIndex - assert "47666c91c7ccf" in theIndex._itemIndex - assert "4c4f28287af27" in theIndex._itemIndex - assert "846352075de7d" in theIndex._itemIndex - assert "88243afbe5ed8" in theIndex._itemIndex - assert "88d59a277361b" in theIndex._itemIndex - assert "8c58a65414c23" in theIndex._itemIndex - assert "db7e733775d4d" in theIndex._itemIndex - assert "eb103bc70c90c" in theIndex._itemIndex - assert "f8c0562e50f1b" in theIndex._itemIndex - assert "f96ec11c6a3da" in theIndex._itemIndex - assert "fb609cd8319dc" in theIndex._itemIndex - assert "7a992350f3eb6" in theIndex._itemIndex + assert "04468803b92e1" in index._itemIndex + assert "2426c6f0ca922" in index._itemIndex + assert "441420a886d82" in index._itemIndex + assert "47666c91c7ccf" in index._itemIndex + assert "4c4f28287af27" in index._itemIndex + assert "846352075de7d" in index._itemIndex + assert "88243afbe5ed8" in index._itemIndex + assert "88d59a277361b" in index._itemIndex + assert "8c58a65414c23" in index._itemIndex + assert "db7e733775d4d" in index._itemIndex + assert "eb103bc70c90c" in index._itemIndex + assert "f8c0562e50f1b" in index._itemIndex + assert "f96ec11c6a3da" in index._itemIndex + assert "fb609cd8319dc" in index._itemIndex + assert "7a992350f3eb6" in index._itemIndex # Finalise - theProject.closeProject() + project.closeProject() # END Test testCoreIndex_LoadSave @@ -155,85 +156,89 @@ def testCoreIndex_LoadSave(monkeypatch, prjLipsum, mockGUI, tstPaths): @pytest.mark.core def testCoreIndex_ScanThis(mockGUI): """Test the tag scanner function scanThis.""" - theProject = NWProject() - theIndex = theProject.index + project = NWProject() + index = project.index - isValid, theBits, thePos = theIndex.scanThis("tag: this, and this") + isValid, theBits, thePos = index.scanThis("tag: this, and this") assert isValid is False - isValid, theBits, thePos = theIndex.scanThis("@") + isValid, theBits, thePos = index.scanThis("@") assert isValid is False - isValid, theBits, thePos = theIndex.scanThis("@:") + isValid, theBits, thePos = index.scanThis("@:") assert isValid is False - isValid, theBits, thePos = theIndex.scanThis(" @a: b") + isValid, theBits, thePos = index.scanThis(" @a: b") assert isValid is False - isValid, theBits, thePos = theIndex.scanThis("@a:") + isValid, theBits, thePos = index.scanThis("@a:") assert isValid is True assert theBits == ["@a"] assert thePos == [0] - isValid, theBits, thePos = theIndex.scanThis("@a:b") + isValid, theBits, thePos = index.scanThis("@a:b") assert isValid is True assert theBits == ["@a", "b"] assert thePos == [0, 3] - isValid, theBits, thePos = theIndex.scanThis("@a:b,c,d") + isValid, theBits, thePos = index.scanThis("@a:b,c,d") assert isValid is True assert theBits == ["@a", "b", "c", "d"] assert thePos == [0, 3, 5, 7] - isValid, theBits, thePos = theIndex.scanThis("@a : b , c , d") + isValid, theBits, thePos = index.scanThis("@a : b , c , d") assert isValid is True assert theBits == ["@a", "b", "c", "d"] assert thePos == [0, 5, 9, 13] - isValid, theBits, thePos = theIndex.scanThis("@tag: this, and this") + isValid, theBits, thePos = index.scanThis("@tag: this, and this") assert isValid is True assert theBits == ["@tag", "this", "and this"] assert thePos == [0, 6, 12] - theProject.closeProject() + project.closeProject() # END Test testCoreIndex_ScanThis @pytest.mark.core def testCoreIndex_CheckThese(mockGUI, fncPath, mockRnd): - """Test the tag checker function checkThese. - """ - theProject = NWProject() + """Test the tag checker function checkThese.""" + project = NWProject() mockRnd.reset() - buildTestProject(theProject, fncPath) - theIndex = theProject.index - theIndex.clearIndex() + buildTestProject(project, fncPath) + index = project.index + index.clearIndex() + + nHandle = project.newFile("Hello", C.hNovelRoot) + cHandle = project.newFile("Jane", C.hCharRoot) + assert isinstance(nHandle, str) + assert isinstance(cHandle, str) - nHandle = theProject.newFile("Hello", C.hNovelRoot) - cHandle = theProject.newFile("Jane", C.hCharRoot) - nItem = theProject.tree[nHandle] - cItem = theProject.tree[cHandle] + nItem = project.tree[nHandle] + cItem = project.tree[cHandle] + assert isinstance(nItem, NWItem) + assert isinstance(cItem, NWItem) - assert theIndex.rootChangedSince(C.hNovelRoot, 0) is False - assert theIndex.indexChangedSince(0) is False + assert index.rootChangedSince(C.hNovelRoot, 0) is False + assert index.indexChangedSince(0) is False - assert theIndex.scanText(cHandle, ( + assert index.scanText(cHandle, ( "# Jane Smith\n" "@tag: Jane\n" "@tag:\n" "@:\n" )) - assert theIndex.scanText(nHandle, ( + assert index.scanText(nHandle, ( "# Hello World!\n" "@pov: Jane\n" "@invalid: John\n" # Checks for issue #688 )) - assert theIndex._tagsIndex.tagHandle("Jane") == cHandle - assert theIndex._tagsIndex.tagHeading("Jane") == "T0001" - assert theIndex._tagsIndex.tagClass("Jane") == "CHARACTER" - assert theIndex.getItemHeader(nHandle, "T0001").title == "Hello World!" - assert theIndex.getReferences(nHandle, "T0001") == { + assert index._tagsIndex.tagHandle("Jane") == cHandle + assert index._tagsIndex.tagHeading("Jane") == "T0001" + assert index._tagsIndex.tagClass("Jane") == "CHARACTER" + assert index.getItemHeader(nHandle, "T0001").title == "Hello World!" # type: ignore + assert index.getReferences(nHandle, "T0001") == { "@char": [], "@custom": [], "@entity": [], @@ -245,35 +250,35 @@ def testCoreIndex_CheckThese(mockGUI, fncPath, mockRnd): "@time": [] } - assert theIndex.rootChangedSince(C.hNovelRoot, 0) is True - assert theIndex.indexChangedSince(0) is True + assert index.rootChangedSince(C.hNovelRoot, 0) is True + assert index.indexChangedSince(0) is True assert cItem.mainHeading == "H1" assert nItem.mainHeading == "H1" # Zero Items - assert theIndex.checkThese([], cItem) == [] + assert index.checkThese([], cItem) == [] # One Item - assert theIndex.checkThese(["@tag"], cItem) == [True] - assert theIndex.checkThese(["@who"], cItem) == [False] + assert index.checkThese(["@tag"], cItem) == [True] + assert index.checkThese(["@who"], cItem) == [False] # Two Items - assert theIndex.checkThese(["@tag", "Jane"], cItem) == [True, True] - assert theIndex.checkThese(["@tag", "John"], cItem) == [True, True] - assert theIndex.checkThese(["@tag", "Jane"], nItem) == [True, False] - assert theIndex.checkThese(["@tag", "John"], nItem) == [True, True] - assert theIndex.checkThese(["@pov", "John"], nItem) == [True, False] - assert theIndex.checkThese(["@pov", "Jane"], nItem) == [True, True] - assert theIndex.checkThese(["@ pov", "Jane"], nItem) == [False, False] - assert theIndex.checkThese(["@what", "Jane"], nItem) == [False, False] + assert index.checkThese(["@tag", "Jane"], cItem) == [True, True] + assert index.checkThese(["@tag", "John"], cItem) == [True, True] + assert index.checkThese(["@tag", "Jane"], nItem) == [True, False] + assert index.checkThese(["@tag", "John"], nItem) == [True, True] + assert index.checkThese(["@pov", "John"], nItem) == [True, False] + assert index.checkThese(["@pov", "Jane"], nItem) == [True, True] + assert index.checkThese(["@ pov", "Jane"], nItem) == [False, False] + assert index.checkThese(["@what", "Jane"], nItem) == [False, False] # Three Items - assert theIndex.checkThese(["@tag", "Jane", "John"], cItem) == [True, True, False] - assert theIndex.checkThese(["@who", "Jane", "John"], cItem) == [False, False, False] - assert theIndex.checkThese(["@pov", "Jane", "John"], nItem) == [True, True, False] + assert index.checkThese(["@tag", "Jane", "John"], cItem) == [True, True, False] + assert index.checkThese(["@who", "Jane", "John"], cItem) == [False, False, False] + assert index.checkThese(["@pov", "Jane", "John"], nItem) == [True, True, False] - theProject.closeProject() + project.closeProject() # END Test testCoreIndex_CheckThese @@ -281,60 +286,69 @@ def testCoreIndex_CheckThese(mockGUI, fncPath, mockRnd): @pytest.mark.core def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd): """Check the index text scanner.""" - theProject = NWProject() + project = NWProject() mockRnd.reset() - buildTestProject(theProject, fncPath) - theIndex = theProject.index + buildTestProject(project, fncPath) + index = project.index # Some items for fail to scan tests - dHandle = theProject.newFolder("Folder", C.hNovelRoot) - xHandle = theProject.newFile("No Layout", C.hNovelRoot) - xItem = theProject.tree[xHandle] + dHandle = project.newFolder("Folder", C.hNovelRoot) + xHandle = project.newFile("No Layout", C.hNovelRoot) + assert isinstance(dHandle, str) + assert isinstance(xHandle, str) + + xItem = project.tree[xHandle] + assert isinstance(xItem, NWItem) xItem.setLayout(nwItemLayout.NO_LAYOUT) # Check invalid data - assert theIndex.scanText(None, "Hello World!") is False - assert theIndex.scanText(dHandle, "Hello World!") is False - assert theIndex.scanText(xHandle, "Hello World!") is False + assert index.scanText(None, "Hello World!") is False # type: ignore + assert index.scanText(dHandle, "Hello World!") is False + assert index.scanText(xHandle, "Hello World!") is False xItem.setLayout(nwItemLayout.DOCUMENT) xItem.setParent(None) - assert theIndex.scanText(xHandle, "Hello World!") is False + assert index.scanText(xHandle, "Hello World!") is False # Create the trash folder - tHandle = theProject.trashFolder() - assert theProject.tree[tHandle] is not None + tHandle = project.trashFolder() + assert project.tree[tHandle] is not None xItem.setParent(tHandle) - theProject.tree.updateItemData(xItem.itemHandle) + project.tree.updateItemData(xItem.itemHandle) assert xItem.itemRoot == tHandle assert xItem.itemClass == nwItemClass.TRASH - assert theIndex.scanText(xHandle, "## Hello World!") is True + assert index.scanText(xHandle, "## Hello World!") is True assert xItem.mainHeading == "H2" # Create the archive root - aHandle = theProject.newRoot(nwItemClass.ARCHIVE) - assert theProject.tree[aHandle] is not None + aHandle = project.newRoot(nwItemClass.ARCHIVE) + assert project.tree[aHandle] is not None xItem.setParent(aHandle) - theProject.tree.updateItemData(xItem.itemHandle) - assert theIndex.scanText(xHandle, "### Hello World!") is True + project.tree.updateItemData(xItem.itemHandle) + assert index.scanText(xHandle, "### Hello World!") is True assert xItem.mainHeading == "H3" # Make some usable items - tHandle = theProject.newFile("Title", C.hNovelRoot) - pHandle = theProject.newFile("Page", C.hNovelRoot) - nHandle = theProject.newFile("Hello", C.hNovelRoot) - cHandle = theProject.newFile("Jane", C.hCharRoot) - sHandle = theProject.newFile("Scene", C.hNovelRoot) + tHandle = project.newFile("Title", C.hNovelRoot) + pHandle = project.newFile("Page", C.hNovelRoot) + nHandle = project.newFile("Hello", C.hNovelRoot) + cHandle = project.newFile("Jane", C.hCharRoot) + sHandle = project.newFile("Scene", C.hNovelRoot) + assert isinstance(tHandle, str) + assert isinstance(pHandle, str) + assert isinstance(nHandle, str) + assert isinstance(cHandle, str) + assert isinstance(sHandle, str) # Text Indexing # ============= # Index correct text - assert theIndex.scanText(cHandle, ( + assert index.scanText(cHandle, ( "# Jane Smith\n" "@tag: Jane\n" )) - assert theIndex.scanText(nHandle, ( + assert index.scanText(nHandle, ( "# Hello World!\n" "@pov: Jane\n" "@char: Jane\n\n" @@ -342,16 +356,16 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd): "This is a story about Jane Smith.\n\n" "Well, not really.\n" )) - assert theIndex._tagsIndex.tagHandle("Jane") == cHandle - assert theIndex._tagsIndex.tagHeading("Jane") == "T0001" - assert theIndex._tagsIndex.tagClass("Jane") == "CHARACTER" - assert theIndex.getItemHeader(nHandle, "T0001").title == "Hello World!" + assert index._tagsIndex.tagHandle("Jane") == cHandle + assert index._tagsIndex.tagHeading("Jane") == "T0001" + assert index._tagsIndex.tagClass("Jane") == "CHARACTER" + assert index.getItemHeader(nHandle, "T0001").title == "Hello World!" # type: ignore # Title Indexing # ============== # Document File - assert theIndex.scanText(nHandle, ( + assert index.scanText(nHandle, ( "# Title One\n\n" "% synopsis: Synopsis One.\n\n" "Paragraph One.\n\n" @@ -367,64 +381,64 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd): "##### Title Five\n\n" # Not interpreted as a title, the hashes are counted as a word "Paragraph Five.\n\n" )) - assert theIndex._itemIndex[nHandle]["T0001"].references == {} - assert theIndex._itemIndex[nHandle]["T0002"].references == {} - assert theIndex._itemIndex[nHandle]["T0003"].references == {} - assert theIndex._itemIndex[nHandle]["T0004"].references == {} - - assert theIndex._itemIndex[nHandle]["T0001"].level == "H1" - assert theIndex._itemIndex[nHandle]["T0002"].level == "H2" - assert theIndex._itemIndex[nHandle]["T0003"].level == "H3" - assert theIndex._itemIndex[nHandle]["T0004"].level == "H4" - - assert theIndex._itemIndex[nHandle]["T0001"].line == 1 - assert theIndex._itemIndex[nHandle]["T0002"].line == 7 - assert theIndex._itemIndex[nHandle]["T0003"].line == 13 - assert theIndex._itemIndex[nHandle]["T0004"].line == 19 - - assert theIndex._itemIndex[nHandle]["T0001"].title == "Title One" - assert theIndex._itemIndex[nHandle]["T0002"].title == "Title Two" - assert theIndex._itemIndex[nHandle]["T0003"].title == "Title Three" - assert theIndex._itemIndex[nHandle]["T0004"].title == "Title Four" - - assert theIndex._itemIndex[nHandle]["T0001"].charCount == 23 - assert theIndex._itemIndex[nHandle]["T0002"].charCount == 23 - assert theIndex._itemIndex[nHandle]["T0003"].charCount == 27 - assert theIndex._itemIndex[nHandle]["T0004"].charCount == 56 - - assert theIndex._itemIndex[nHandle]["T0001"].wordCount == 4 - assert theIndex._itemIndex[nHandle]["T0002"].wordCount == 4 - assert theIndex._itemIndex[nHandle]["T0003"].wordCount == 4 - assert theIndex._itemIndex[nHandle]["T0004"].wordCount == 9 - - assert theIndex._itemIndex[nHandle]["T0001"].paraCount == 1 - assert theIndex._itemIndex[nHandle]["T0002"].paraCount == 1 - assert theIndex._itemIndex[nHandle]["T0003"].paraCount == 1 - assert theIndex._itemIndex[nHandle]["T0004"].paraCount == 3 - - assert theIndex._itemIndex[nHandle]["T0001"].synopsis == "Synopsis One." - assert theIndex._itemIndex[nHandle]["T0002"].synopsis == "Synopsis Two." - assert theIndex._itemIndex[nHandle]["T0003"].synopsis == "Synopsis Three." - assert theIndex._itemIndex[nHandle]["T0004"].synopsis == "Synopsis Four." + assert index._itemIndex[nHandle]["T0001"].references == {} # type: ignore + assert index._itemIndex[nHandle]["T0002"].references == {} # type: ignore + assert index._itemIndex[nHandle]["T0003"].references == {} # type: ignore + assert index._itemIndex[nHandle]["T0004"].references == {} # type: ignore + + assert index._itemIndex[nHandle]["T0001"].level == "H1" # type: ignore + assert index._itemIndex[nHandle]["T0002"].level == "H2" # type: ignore + assert index._itemIndex[nHandle]["T0003"].level == "H3" # type: ignore + assert index._itemIndex[nHandle]["T0004"].level == "H4" # type: ignore + + assert index._itemIndex[nHandle]["T0001"].line == 1 # type: ignore + assert index._itemIndex[nHandle]["T0002"].line == 7 # type: ignore + assert index._itemIndex[nHandle]["T0003"].line == 13 # type: ignore + assert index._itemIndex[nHandle]["T0004"].line == 19 # type: ignore + + assert index._itemIndex[nHandle]["T0001"].title == "Title One" # type: ignore + assert index._itemIndex[nHandle]["T0002"].title == "Title Two" # type: ignore + assert index._itemIndex[nHandle]["T0003"].title == "Title Three" # type: ignore + assert index._itemIndex[nHandle]["T0004"].title == "Title Four" # type: ignore + + assert index._itemIndex[nHandle]["T0001"].charCount == 23 # type: ignore + assert index._itemIndex[nHandle]["T0002"].charCount == 23 # type: ignore + assert index._itemIndex[nHandle]["T0003"].charCount == 27 # type: ignore + assert index._itemIndex[nHandle]["T0004"].charCount == 56 # type: ignore + + assert index._itemIndex[nHandle]["T0001"].wordCount == 4 # type: ignore + assert index._itemIndex[nHandle]["T0002"].wordCount == 4 # type: ignore + assert index._itemIndex[nHandle]["T0003"].wordCount == 4 # type: ignore + assert index._itemIndex[nHandle]["T0004"].wordCount == 9 # type: ignore + + assert index._itemIndex[nHandle]["T0001"].paraCount == 1 # type: ignore + assert index._itemIndex[nHandle]["T0002"].paraCount == 1 # type: ignore + assert index._itemIndex[nHandle]["T0003"].paraCount == 1 # type: ignore + assert index._itemIndex[nHandle]["T0004"].paraCount == 3 # type: ignore + + assert index._itemIndex[nHandle]["T0001"].synopsis == "Synopsis One." # type: ignore + assert index._itemIndex[nHandle]["T0002"].synopsis == "Synopsis Two." # type: ignore + assert index._itemIndex[nHandle]["T0003"].synopsis == "Synopsis Three." # type: ignore + assert index._itemIndex[nHandle]["T0004"].synopsis == "Synopsis Four." # type: ignore # Note File - assert theIndex.scanText(cHandle, ( + assert index.scanText(cHandle, ( "# Title One\n\n" "@tag: One\n\n" "% synopsis: Synopsis One.\n\n" "Paragraph One.\n\n" )) - assert theIndex._itemIndex[cHandle]["T0001"].references == {} - assert theIndex._itemIndex[cHandle]["T0001"].level == "H1" - assert theIndex._itemIndex[cHandle]["T0001"].line == 1 - assert theIndex._itemIndex[cHandle]["T0001"].title == "Title One" - assert theIndex._itemIndex[cHandle]["T0001"].charCount == 23 - assert theIndex._itemIndex[cHandle]["T0001"].wordCount == 4 - assert theIndex._itemIndex[cHandle]["T0001"].paraCount == 1 - assert theIndex._itemIndex[cHandle]["T0001"].synopsis == "Synopsis One." + assert index._itemIndex[cHandle]["T0001"].references == {} # type: ignore + assert index._itemIndex[cHandle]["T0001"].level == "H1" # type: ignore + assert index._itemIndex[cHandle]["T0001"].line == 1 # type: ignore + assert index._itemIndex[cHandle]["T0001"].title == "Title One" # type: ignore + assert index._itemIndex[cHandle]["T0001"].charCount == 23 # type: ignore + assert index._itemIndex[cHandle]["T0001"].wordCount == 4 # type: ignore + assert index._itemIndex[cHandle]["T0001"].paraCount == 1 # type: ignore + assert index._itemIndex[cHandle]["T0001"].synopsis == "Synopsis One." # type: ignore # Valid and Invalid References - assert theIndex.scanText(sHandle, ( + assert index.scanText(sHandle, ( "# Title One\n\n" "@pov: One\n\n" # Valid "@char: Two\n\n" # Invalid tag @@ -432,69 +446,69 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd): "% synopsis: Synopsis One.\n\n" "Paragraph One.\n\n" )) - assert theIndex._itemIndex[sHandle]["T0001"].references == { - "One": {"@pov"}, "Two": {"@char"} + assert index._itemIndex[sHandle]["T0001"].references == { # type: ignore + "one": {"@pov"}, "two": {"@char"} } # Special Titles # ============== - assert theIndex.scanText(tHandle, ( + assert index.scanText(tHandle, ( "#! My Project\n\n" ">> By Jane Doe <<\n\n" )) - assert theIndex._itemIndex[cHandle]["T0001"].references == {} - assert theIndex._itemIndex[tHandle]["T0001"].level == "H1" - assert theIndex._itemIndex[tHandle]["T0001"].line == 1 - assert theIndex._itemIndex[tHandle]["T0001"].title == "My Project" - assert theIndex._itemIndex[tHandle]["T0001"].charCount == 21 - assert theIndex._itemIndex[tHandle]["T0001"].wordCount == 5 - assert theIndex._itemIndex[tHandle]["T0001"].paraCount == 1 - assert theIndex._itemIndex[tHandle]["T0001"].synopsis == "" - - assert theIndex.scanText(tHandle, ( + assert index._itemIndex[cHandle]["T0001"].references == {} # type: ignore + assert index._itemIndex[tHandle]["T0001"].level == "H1" # type: ignore + assert index._itemIndex[tHandle]["T0001"].line == 1 # type: ignore + assert index._itemIndex[tHandle]["T0001"].title == "My Project" # type: ignore + assert index._itemIndex[tHandle]["T0001"].charCount == 21 # type: ignore + assert index._itemIndex[tHandle]["T0001"].wordCount == 5 # type: ignore + assert index._itemIndex[tHandle]["T0001"].paraCount == 1 # type: ignore + assert index._itemIndex[tHandle]["T0001"].synopsis == "" # type: ignore + + assert index.scanText(tHandle, ( "##! Prologue\n\n" "In the beginning there was time ...\n\n" )) - assert theIndex._itemIndex[cHandle]["T0001"].references == {} - assert theIndex._itemIndex[tHandle]["T0001"].level == "H2" - assert theIndex._itemIndex[tHandle]["T0001"].line == 1 - assert theIndex._itemIndex[tHandle]["T0001"].title == "Prologue" - assert theIndex._itemIndex[tHandle]["T0001"].charCount == 43 - assert theIndex._itemIndex[tHandle]["T0001"].wordCount == 8 - assert theIndex._itemIndex[tHandle]["T0001"].paraCount == 1 - assert theIndex._itemIndex[tHandle]["T0001"].synopsis == "" + assert index._itemIndex[cHandle]["T0001"].references == {} # type: ignore + assert index._itemIndex[tHandle]["T0001"].level == "H2" # type: ignore + assert index._itemIndex[tHandle]["T0001"].line == 1 # type: ignore + assert index._itemIndex[tHandle]["T0001"].title == "Prologue" # type: ignore + assert index._itemIndex[tHandle]["T0001"].charCount == 43 # type: ignore + assert index._itemIndex[tHandle]["T0001"].wordCount == 8 # type: ignore + assert index._itemIndex[tHandle]["T0001"].paraCount == 1 # type: ignore + assert index._itemIndex[tHandle]["T0001"].synopsis == "" # type: ignore # Page wo/Title # ============= - theProject.tree[pHandle]._layout = nwItemLayout.DOCUMENT - assert theIndex.scanText(pHandle, ( + project.tree[pHandle]._layout = nwItemLayout.DOCUMENT # type: ignore + assert index.scanText(pHandle, ( "This is a page with some text on it.\n\n" )) - assert theIndex._itemIndex[pHandle]["T0000"].references == {} - assert theIndex._itemIndex[pHandle]["T0000"].level == "H0" - assert theIndex._itemIndex[pHandle]["T0000"].line == 0 - assert theIndex._itemIndex[pHandle]["T0000"].title == "" - assert theIndex._itemIndex[pHandle]["T0000"].charCount == 36 - assert theIndex._itemIndex[pHandle]["T0000"].wordCount == 9 - assert theIndex._itemIndex[pHandle]["T0000"].paraCount == 1 - assert theIndex._itemIndex[pHandle]["T0000"].synopsis == "" - - theProject.tree[pHandle]._layout = nwItemLayout.NOTE - assert theIndex.scanText(pHandle, ( + assert index._itemIndex[pHandle]["T0000"].references == {} # type: ignore + assert index._itemIndex[pHandle]["T0000"].level == "H0" # type: ignore + assert index._itemIndex[pHandle]["T0000"].line == 0 # type: ignore + assert index._itemIndex[pHandle]["T0000"].title == "" # type: ignore + assert index._itemIndex[pHandle]["T0000"].charCount == 36 # type: ignore + assert index._itemIndex[pHandle]["T0000"].wordCount == 9 # type: ignore + assert index._itemIndex[pHandle]["T0000"].paraCount == 1 # type: ignore + assert index._itemIndex[pHandle]["T0000"].synopsis == "" # type: ignore + + project.tree[pHandle]._layout = nwItemLayout.NOTE # type: ignore + assert index.scanText(pHandle, ( "This is a page with some text on it.\n\n" )) - assert theIndex._itemIndex[pHandle]["T0000"].references == {} - assert theIndex._itemIndex[pHandle]["T0000"].level == "H0" - assert theIndex._itemIndex[pHandle]["T0000"].line == 0 - assert theIndex._itemIndex[pHandle]["T0000"].title == "" - assert theIndex._itemIndex[pHandle]["T0000"].charCount == 36 - assert theIndex._itemIndex[pHandle]["T0000"].wordCount == 9 - assert theIndex._itemIndex[pHandle]["T0000"].paraCount == 1 - assert theIndex._itemIndex[pHandle]["T0000"].synopsis == "" + assert index._itemIndex[pHandle]["T0000"].references == {} # type: ignore + assert index._itemIndex[pHandle]["T0000"].level == "H0" # type: ignore + assert index._itemIndex[pHandle]["T0000"].line == 0 # type: ignore + assert index._itemIndex[pHandle]["T0000"].title == "" # type: ignore + assert index._itemIndex[pHandle]["T0000"].charCount == 36 # type: ignore + assert index._itemIndex[pHandle]["T0000"].wordCount == 9 # type: ignore + assert index._itemIndex[pHandle]["T0000"].paraCount == 1 # type: ignore + assert index._itemIndex[pHandle]["T0000"].synopsis == "" # type: ignore - theProject.closeProject() + project.closeProject() # END Test testCoreIndex_ScanText @@ -502,31 +516,33 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd): @pytest.mark.core def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): """Check the index data extraction functions.""" - theProject = NWProject() + project = NWProject() mockRnd.reset() - buildTestProject(theProject, fncPath) - - theIndex = theProject.index - theIndex.reIndexHandle(C.hNovelRoot) - theIndex.reIndexHandle(C.hPlotRoot) - theIndex.reIndexHandle(C.hCharRoot) - theIndex.reIndexHandle(C.hWorldRoot) - theIndex.reIndexHandle(C.hTitlePage) - theIndex.reIndexHandle(C.hChapterDir) - theIndex.reIndexHandle(C.hChapterDoc) - theIndex.reIndexHandle(C.hSceneDoc) - - nHandle = theProject.newFile("Hello", C.hNovelRoot) - cHandle = theProject.newFile("Jane", C.hCharRoot) - - assert theIndex.getItemHeader("", "") is None - assert theIndex.getItemHeader(C.hNovelRoot, "") is None - - assert theIndex.scanText(cHandle, ( + buildTestProject(project, fncPath) + + index = project.index + index.reIndexHandle(C.hNovelRoot) + index.reIndexHandle(C.hPlotRoot) + index.reIndexHandle(C.hCharRoot) + index.reIndexHandle(C.hWorldRoot) + index.reIndexHandle(C.hTitlePage) + index.reIndexHandle(C.hChapterDir) + index.reIndexHandle(C.hChapterDoc) + index.reIndexHandle(C.hSceneDoc) + + nHandle = project.newFile("Hello", C.hNovelRoot) + cHandle = project.newFile("Jane", C.hCharRoot) + assert isinstance(nHandle, str) + assert isinstance(cHandle, str) + + assert index.getItemHeader("", "") is None + assert index.getItemHeader(C.hNovelRoot, "") is None + + assert index.scanText(cHandle, ( "# Jane Smith\n" "@tag: Jane\n" )) - assert theIndex.scanText(nHandle, ( + assert index.scanText(nHandle, ( "# Hello World!\n" "@pov: Jane\n" "@char: Jane\n\n" @@ -537,7 +553,7 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): # The novel structure should contain the pointer to the novel file header theKeys = [] - for aKey, _, _, _ in theIndex.novelStructure(): + for aKey, _, _, _ in index.novelStructure(): theKeys.append(aKey) assert theKeys == [ @@ -548,10 +564,10 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): ] # Check that excluded files can be skipped - theProject.tree[nHandle].setActive(False) + project.tree[nHandle].setActive(False) # type: ignore theKeys = [] - for aKey, _, _, _ in theIndex.novelStructure(skipExcl=False): + for aKey, _, _, _ in index.novelStructure(skipExcl=False): theKeys.append(aKey) assert theKeys == [ @@ -562,7 +578,7 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): ] theKeys = [] - for aKey, _, _, _ in theIndex.novelStructure(skipExcl=True): + for aKey, _, _, _ in index.novelStructure(skipExcl=True): theKeys.append(aKey) assert theKeys == [ @@ -572,7 +588,7 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): ] # The novel file should have the correct counts - cC, wC, pC = theIndex.getCounts(nHandle) + cC, wC, pC = index.getCounts(nHandle) assert cC == 62 # Characters in text and title only assert wC == 12 # Words in text and title only assert pC == 2 # Paragraphs in text only @@ -580,21 +596,21 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): # getItemData + getHandleHeaderCount # ================================== - theItem = theIndex.getItemData(nHandle) + theItem = index.getItemData(nHandle) assert isinstance(theItem, IndexItem) assert theItem.headings() == ["T0001"] - assert theIndex.getHandleHeaderCount(nHandle) == 1 + assert index.getHandleHeaderCount(nHandle) == 1 # getReferences # ============= # Look up an invalid handle - theRefs = theIndex.getReferences("Not a handle") + theRefs = index.getReferences("Not a handle") assert theRefs["@pov"] == [] assert theRefs["@char"] == [] # The novel file should now refer to Jane as @pov and @char - theRefs = theIndex.getReferences(nHandle) + theRefs = index.getReferences(nHandle) assert theRefs["@pov"] == ["Jane"] assert theRefs["@char"] == ["Jane"] @@ -602,31 +618,31 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): # ==================== # None handle should return an empty dict - assert theIndex.getBackReferenceList(None) == {} + assert index.getBackReferenceList(None) == {} # type: ignore # The Title Page file should have no references as it has no tag - assert theIndex.getBackReferenceList(C.hTitlePage) == {} + assert index.getBackReferenceList(C.hTitlePage) == {} # The character file should have a record of the reference from the novel file - theRefs = theIndex.getBackReferenceList(cHandle) + theRefs = index.getBackReferenceList(cHandle) assert theRefs == {nHandle: "T0001"} # getTagSource # ============ - assert theIndex.getTagSource("Jane") == (cHandle, "T0001") - assert theIndex.getTagSource("John") == (None, "T0000") + assert index.getTagSource("Jane") == (cHandle, "T0001") + assert index.getTagSource("John") == (None, "T0000") # getCounts # ========= # For whole text and sections # Invalid handle or title should return 0s - assert theIndex.getCounts("stuff") == (0, 0, 0) - assert theIndex.getCounts(nHandle, "stuff") == (0, 0, 0) + assert index.getCounts("stuff") == (0, 0, 0) + assert index.getCounts(nHandle, "stuff") == (0, 0, 0) # Get section counts for a novel file - assert theIndex.scanText(nHandle, ( + assert index.scanText(nHandle, ( "# Hello World!\n" "@pov: Jane\n" "@char: Jane\n\n" @@ -641,25 +657,25 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): "Well, not really. She's still awesome though.\n" )) # Whole document - cC, wC, pC = theIndex.getCounts(nHandle) + cC, wC, pC = index.getCounts(nHandle) assert cC == 152 assert wC == 28 assert pC == 4 # First part - cC, wC, pC = theIndex.getCounts(nHandle, "T0001") + cC, wC, pC = index.getCounts(nHandle, "T0001") assert cC == 62 assert wC == 12 assert pC == 2 # Second part - cC, wC, pC = theIndex.getCounts(nHandle, "T0002") + cC, wC, pC = index.getCounts(nHandle, "T0002") assert cC == 90 assert wC == 16 assert pC == 2 # Get section counts for a note file - assert theIndex.scanText(cHandle, ( + assert index.scanText(cHandle, ( "# Hello World!\n" "@pov: Jane\n" "@char: Jane\n\n" @@ -674,19 +690,19 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): "Well, not really. She's still awesome though.\n" )) # Whole document - cC, wC, pC = theIndex.getCounts(cHandle) + cC, wC, pC = index.getCounts(cHandle) assert cC == 152 assert wC == 28 assert pC == 4 # First part - cC, wC, pC = theIndex.getCounts(cHandle, "T0001") + cC, wC, pC = index.getCounts(cHandle, "T0001") assert cC == 62 assert wC == 12 assert pC == 2 # Second part - cC, wC, pC = theIndex.getCounts(cHandle, "T0002") + cC, wC, pC = index.getCounts(cHandle, "T0002") assert cC == 90 assert wC == 16 assert pC == 2 @@ -694,19 +710,19 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): # Novel Stats # =========== - hHandle = theProject.newFile("Chapter", C.hNovelRoot) - sHandle = theProject.newFile("Scene One", C.hNovelRoot) - tHandle = theProject.newFile("Scene Two", C.hNovelRoot) + hHandle = project.newFile("Chapter", C.hNovelRoot) + sHandle = project.newFile("Scene One", C.hNovelRoot) + tHandle = project.newFile("Scene Two", C.hNovelRoot) - theProject.tree[hHandle].itemLayout == nwItemLayout.DOCUMENT - theProject.tree[sHandle].itemLayout == nwItemLayout.DOCUMENT - theProject.tree[tHandle].itemLayout == nwItemLayout.DOCUMENT + project.tree[hHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore + project.tree[sHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore + project.tree[tHandle].itemLayout == nwItemLayout.DOCUMENT # type: ignore - assert theIndex.scanText(hHandle, "## Chapter One\n\n") - assert theIndex.scanText(sHandle, "### Scene One\n\n") - assert theIndex.scanText(tHandle, "### Scene Two\n\n") + assert index.scanText(hHandle, "## Chapter One\n\n") # type: ignore + assert index.scanText(sHandle, "### Scene One\n\n") # type: ignore + assert index.scanText(tHandle, "### Scene Two\n\n") # type: ignore - assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=False)] == [ + assert [(h, t) for h, t, _ in index._itemIndex.iterNovelStructure(skipExcl=False)] == [ (C.hTitlePage, "T0001"), (C.hChapterDoc, "T0001"), (C.hSceneDoc, "T0001"), @@ -717,7 +733,7 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): (tHandle, "T0001"), ] - assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=True)] == [ + assert [(h, t) for h, t, _ in index._itemIndex.iterNovelStructure(skipExcl=True)] == [ (C.hTitlePage, "T0001"), (C.hChapterDoc, "T0001"), (C.hSceneDoc, "T0001"), @@ -727,8 +743,8 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): ] # Add a fake handle to the tree and check that it's ignored - theProject.tree._order.append("0000000000000") - assert [(h, t) for h, t, _ in theIndex._itemIndex.iterNovelStructure(skipExcl=False)] == [ + project.tree._order.append("0000000000000") + assert [(h, t) for h, t, _ in index._itemIndex.iterNovelStructure(skipExcl=False)] == [ (C.hTitlePage, "T0001"), (C.hChapterDoc, "T0001"), (C.hSceneDoc, "T0001"), @@ -738,25 +754,25 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): (sHandle, "T0001"), (tHandle, "T0001"), ] - theProject.tree._order.remove("0000000000000") + project.tree._order.remove("0000000000000") # Extract stats - assert theIndex.getNovelWordCount(skipExcl=False) == 43 - assert theIndex.getNovelWordCount(skipExcl=True) == 15 - assert theIndex.getNovelTitleCounts(skipExcl=False) == [0, 3, 2, 3, 0] - assert theIndex.getNovelTitleCounts(skipExcl=True) == [0, 1, 2, 3, 0] + assert index.getNovelWordCount(skipExcl=False) == 43 + assert index.getNovelWordCount(skipExcl=True) == 15 + assert index.getNovelTitleCounts(skipExcl=False) == [0, 3, 2, 3, 0] + assert index.getNovelTitleCounts(skipExcl=True) == [0, 1, 2, 3, 0] # Table of Contents - assert theIndex.getTableOfContents(C.hNovelRoot, 0, skipExcl=True) == [] - assert theIndex.getTableOfContents(C.hNovelRoot, 1, skipExcl=True) == [ + assert index.getTableOfContents(C.hNovelRoot, 0, skipExcl=True) == [] + assert index.getTableOfContents(C.hNovelRoot, 1, skipExcl=True) == [ (f"{C.hTitlePage}:T0001", 1, "New Novel", 15), ] - assert theIndex.getTableOfContents(C.hNovelRoot, 2, skipExcl=True) == [ + assert index.getTableOfContents(C.hNovelRoot, 2, skipExcl=True) == [ (f"{C.hTitlePage}:T0001", 1, "New Novel", 5), (f"{C.hChapterDoc}:T0001", 2, "New Chapter", 4), (f"{hHandle}:T0001", 2, "Chapter One", 6), ] - assert theIndex.getTableOfContents(C.hNovelRoot, 3, skipExcl=True) == [ + assert index.getTableOfContents(C.hNovelRoot, 3, skipExcl=True) == [ (f"{C.hTitlePage}:T0001", 1, "New Novel", 5), (f"{C.hChapterDoc}:T0001", 2, "New Chapter", 2), (f"{C.hSceneDoc}:T0001", 3, "New Scene", 2), @@ -765,16 +781,16 @@ def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): (f"{tHandle}:T0001", 3, "Scene Two", 2), ] - assert theIndex.getTableOfContents(C.hNovelRoot, 0, skipExcl=False) == [] - assert theIndex.getTableOfContents(C.hNovelRoot, 1, skipExcl=False) == [ + assert index.getTableOfContents(C.hNovelRoot, 0, skipExcl=False) == [] + assert index.getTableOfContents(C.hNovelRoot, 1, skipExcl=False) == [ (f"{C.hTitlePage}:T0001", 1, "New Novel", 9), (f"{nHandle}:T0001", 1, "Hello World!", 12), (f"{nHandle}:T0002", 1, "Hello World!", 22), ] - assert theIndex.saveIndex() is True - assert theProject.saveProject() is True - theProject.closeProject() + assert index.saveIndex() is True + assert project.saveProject() is True + project.closeProject() # END Test testCoreIndex_ExtractData @@ -787,17 +803,20 @@ def testCoreIndex_TagsIndex(): # Expected data content = { - "Tag1": { + "tag1": { + "name": "Tag1", "handle": "0000000000001", "heading": "T0001", "class": nwItemClass.NOVEL.name, }, - "Tag2": { + "tag2": { + "name": "Tag2", "handle": "0000000000002", "heading": "T0002", "class": nwItemClass.CHARACTER.name, }, - "Tag3": { + "tag3": { + "name": "Tag3", "handle": "0000000000003", "heading": "T0003", "class": nwItemClass.PLOT.name, @@ -811,9 +830,9 @@ def testCoreIndex_TagsIndex(): assert tagsIndex._tags == content # Get items - assert tagsIndex["Tag1"] == content["Tag1"] - assert tagsIndex["Tag2"] == content["Tag2"] - assert tagsIndex["Tag3"] == content["Tag3"] + assert tagsIndex["Tag1"] == content["tag1"] + assert tagsIndex["Tag2"] == content["tag2"] + assert tagsIndex["Tag3"] == content["tag3"] assert tagsIndex["Tag4"] is None # Contains @@ -866,12 +885,23 @@ def testCoreIndex_TagsIndex(): # Invalid data type with pytest.raises(ValueError): - tagsIndex.unpackData([]) + tagsIndex.unpackData([]) # type: ignore # Invalid key with pytest.raises(ValueError): tagsIndex.unpackData({ 1234: { + "name": "1234", + "handle": "0000000000001", + "heading": "T0001", + "class": "NOVEL", + } + }) + + # Missing name + with pytest.raises(KeyError): + tagsIndex.unpackData({ + "tag1": { "handle": "0000000000001", "heading": "T0001", "class": "NOVEL", @@ -881,7 +911,8 @@ def testCoreIndex_TagsIndex(): # Missing handle with pytest.raises(KeyError): tagsIndex.unpackData({ - "Tag1": { + "tag1": { + "name": "Tag1", "heading": "T0001", "class": "NOVEL", } @@ -890,7 +921,8 @@ def testCoreIndex_TagsIndex(): # Missing heading with pytest.raises(KeyError): tagsIndex.unpackData({ - "Tag1": { + "tag1": { + "name": "Tag1", "handle": "0000000000001", "class": "NOVEL", } @@ -899,16 +931,29 @@ def testCoreIndex_TagsIndex(): # Missing class with pytest.raises(KeyError): tagsIndex.unpackData({ - "Tag1": { + "tag1": { + "name": "Tag1", "handle": "0000000000001", "heading": "T0001", } }) - # Invalid handle + # Invalid key case with pytest.raises(ValueError): tagsIndex.unpackData({ "Tag1": { + "name": "Tag1", + "handle": "blablabla", + "heading": "T0001", + "class": "NOVEL", + } + }) + + # Invalid handle + with pytest.raises(ValueError): + tagsIndex.unpackData({ + "tag1": { + "name": "Tag1", "handle": "blablabla", "heading": "T0001", "class": "NOVEL", @@ -918,7 +963,8 @@ def testCoreIndex_TagsIndex(): # Invalid heading with pytest.raises(ValueError): tagsIndex.unpackData({ - "Tag1": { + "tag1": { + "name": "Tag1", "handle": "0000000000001", "heading": "blabla", "class": "NOVEL", @@ -928,7 +974,8 @@ def testCoreIndex_TagsIndex(): # Invalid class with pytest.raises(ValueError): tagsIndex.unpackData({ - "Tag1": { + "tag1": { + "name": "Tag1", "handle": "0000000000001", "heading": "T0001", "class": "blabla", @@ -941,17 +988,17 @@ def testCoreIndex_TagsIndex(): @pytest.mark.core def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): """Check the ItemIndex class.""" - theProject = NWProject() + project = NWProject() mockRnd.reset() - buildTestProject(theProject, fncPath) - theProject.index.clearIndex() + buildTestProject(project, fncPath) + project.index.clearIndex() nHandle = C.hTitlePage cHandle = C.hChapterDoc sHandle = C.hSceneDoc - assert theProject.index.saveIndex() is True - itemIndex = theProject.index._itemIndex + assert project.index.saveIndex() is True + itemIndex = project.index._itemIndex # The index should be empty assert nHandle not in itemIndex @@ -963,9 +1010,9 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): assert cHandle not in itemIndex # Add the novel chapter file - itemIndex.add(cHandle, theProject.tree[cHandle]) + itemIndex.add(cHandle, project.tree[cHandle]) # type: ignore assert cHandle in itemIndex - assert itemIndex[cHandle].item == theProject.tree[cHandle] + assert itemIndex[cHandle].item == project.tree[cHandle] # type: ignore assert itemIndex.allItemTags(cHandle) == [] assert list(itemIndex.iterItemHeaders(cHandle))[0][0] == "T0000" @@ -986,17 +1033,17 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): idxData = itemIndex.packData() assert idxData[cHandle]["headings"]["T0001"] == { - "level": "H2", "line": 1, "title": "Chapter One", "tag": "One", + "level": "H2", "line": 1, "title": "Chapter One", "tag": "one", "cCount": 60, "wCount": 10, "pCount": 2, "synopsis": "In the beginning ...", } - assert "@pov" in idxData[cHandle]["references"]["T0001"]["Jane"] - assert "@focus" in idxData[cHandle]["references"]["T0001"]["Jane"] - assert "@char" in idxData[cHandle]["references"]["T0001"]["Jane"] - assert "@char" in idxData[cHandle]["references"]["T0001"]["John"] + assert "@pov" in idxData[cHandle]["references"]["T0001"]["jane"] + assert "@focus" in idxData[cHandle]["references"]["T0001"]["jane"] + assert "@char" in idxData[cHandle]["references"]["T0001"]["jane"] + assert "@char" in idxData[cHandle]["references"]["T0001"]["john"] # Add the other two files - itemIndex.add(nHandle, theProject.tree[nHandle]) - itemIndex.add(sHandle, theProject.tree[sHandle]) + itemIndex.add(nHandle, project.tree[nHandle]) # type: ignore + itemIndex.add(sHandle, project.tree[sHandle]) # type: ignore itemIndex.addItemHeading(nHandle, 1, "H1", "Novel") itemIndex.addItemHeading(sHandle, 1, "H3", "Scene One") @@ -1005,32 +1052,32 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): # Check repr strings assert repr(itemIndex[nHandle]) == f"" - assert repr(itemIndex[nHandle]["T0001"]) == "" + assert repr(itemIndex[nHandle]["T0001"]) == "" # type: ignore # Check content of a single item - assert "T0001" in itemIndex[nHandle] - assert itemIndex[cHandle].allTags() == ["One"] + assert "T0001" in itemIndex[nHandle] # type: ignore + assert itemIndex[cHandle].allTags() == ["one"] # type: ignore # Check the content of a single heading - assert itemIndex[cHandle]["T0001"].key == "T0001" - assert itemIndex[cHandle]["T0001"].level == "H2" - assert itemIndex[cHandle]["T0001"].line == 1 - assert itemIndex[cHandle]["T0001"].title == "Chapter One" - assert itemIndex[cHandle]["T0001"].tag == "One" - assert itemIndex[cHandle]["T0001"].charCount == 60 - assert itemIndex[cHandle]["T0001"].wordCount == 10 - assert itemIndex[cHandle]["T0001"].paraCount == 2 - assert itemIndex[cHandle]["T0001"].synopsis == "In the beginning ..." - assert "Jane" in itemIndex[cHandle]["T0001"].references - assert "John" in itemIndex[cHandle]["T0001"].references + assert itemIndex[cHandle]["T0001"].key == "T0001" # type: ignore + assert itemIndex[cHandle]["T0001"].level == "H2" # type: ignore + assert itemIndex[cHandle]["T0001"].line == 1 # type: ignore + assert itemIndex[cHandle]["T0001"].title == "Chapter One" # type: ignore + assert itemIndex[cHandle]["T0001"].tag == "one" # type: ignore + assert itemIndex[cHandle]["T0001"].charCount == 60 # type: ignore + assert itemIndex[cHandle]["T0001"].wordCount == 10 # type: ignore + assert itemIndex[cHandle]["T0001"].paraCount == 2 # type: ignore + assert itemIndex[cHandle]["T0001"].synopsis == "In the beginning ..." # type: ignore + assert "jane" in itemIndex[cHandle]["T0001"].references # type: ignore + assert "john" in itemIndex[cHandle]["T0001"].references # type: ignore # Check heading level setter - itemIndex[cHandle]["T0001"].setLevel("H3") # Change it - assert itemIndex[cHandle]["T0001"].level == "H3" - itemIndex[cHandle]["T0001"].setLevel("H2") # Set it back - assert itemIndex[cHandle]["T0001"].level == "H2" - itemIndex[cHandle]["T0001"].setLevel("H5") # Invalid level - assert itemIndex[cHandle]["T0001"].level == "H2" + itemIndex[cHandle]["T0001"].setLevel("H3") # Change it # type: ignore + assert itemIndex[cHandle]["T0001"].level == "H3" # type: ignore + itemIndex[cHandle]["T0001"].setLevel("H2") # Set it back # type: ignore + assert itemIndex[cHandle]["T0001"].level == "H2" # type: ignore + itemIndex[cHandle]["T0001"].setLevel("H5") # Invalid level # type: ignore + assert itemIndex[cHandle]["T0001"].level == "H2" # type: ignore # Data Extraction # =============== @@ -1051,10 +1098,10 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): # =============== # Add a second novel - mHandle = theProject.newRoot(nwItemClass.NOVEL) - uHandle = theProject.newFile("Title Page", mHandle) - itemIndex.add(uHandle, theProject.tree[uHandle]) - itemIndex.addItemHeading(uHandle, "T0001", "H1", "Novel 2") + mHandle = project.newRoot(nwItemClass.NOVEL) + uHandle = project.newFile("Title Page", mHandle) + itemIndex.add(uHandle, project.tree[uHandle]) # type: ignore + itemIndex.addItemHeading(uHandle, "T0001", "H1", "Novel 2") # type: ignore assert uHandle in itemIndex # Structure of all novels @@ -1077,7 +1124,7 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): assert nStruct[0][0] == uHandle # Inject garbage into tree - theProject.tree._order.append("stuff") + project.tree._order.append("stuff") nStruct = list(itemIndex.iterNovelStructure()) assert len(nStruct) == 4 assert nStruct[0][0] == nHandle @@ -1086,7 +1133,7 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): assert nStruct[3][0] == uHandle # Skip excluded - theProject.tree[sHandle].setActive(False) + project.tree[sHandle].setActive(False) # type: ignore nStruct = list(itemIndex.iterNovelStructure(skipExcl=True)) assert len(nStruct) == 3 assert nStruct[0][0] == nHandle @@ -1094,7 +1141,7 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): assert nStruct[2][0] == uHandle # Delete new item - del itemIndex[uHandle] + del itemIndex[uHandle] # type: ignore assert uHandle not in itemIndex # Unpack Error Handling @@ -1109,7 +1156,7 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): # Data must be dictionary with pytest.raises(ValueError): - itemIndex.unpackData("stuff") + itemIndex.unpackData("stuff") # type: ignore # Keys must be valid handles with pytest.raises(ValueError): @@ -1134,8 +1181,8 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): "references": {"T0001": {}, "T0002": {}}, } }) - assert "T0001" in itemIndex[cHandle] - assert "T0002" not in itemIndex[cHandle] + assert "T0001" in itemIndex[cHandle] # type: ignore + assert "T0002" not in itemIndex[cHandle] # type: ignore itemIndex.clear() # Tag keys must be strings @@ -1180,8 +1227,8 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): def testCoreIndex_CountWords(): """Test the word counter and the exclusion filers.""" # Non-Text - assert countWords(None) == (0, 0, 0) - assert countWords(1234) == (0, 0, 0) + assert countWords(None) == (0, 0, 0) # type: ignore + assert countWords(1234) == (0, 0, 0) # type: ignore # General Text cC, wC, pC = countWords(( From 2e0204d5770ff8ab6f9c4880709dfb5cc2b7c228 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 17 Sep 2023 20:29:33 +0200 Subject: [PATCH 4/4] Clean up variables and annotations in index classes --- novelwriter/core/index.py | 195 ++++++++++++++--------------- tests/test_core/test_core_index.py | 6 +- 2 files changed, 99 insertions(+), 102 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index a999e65cc..068bb0363 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -169,15 +169,13 @@ def loadIndex(self) -> bool: if not isinstance(indexFile, Path): return False - theData = {} tStart = time() - self._indexBroken = False if indexFile.exists(): logger.debug("Loading index file") try: with open(indexFile, mode="r", encoding="utf-8") as inFile: - theData = json.load(inFile) + data = json.load(inFile) except Exception: logger.error("Failed to load index file") logException() @@ -185,8 +183,8 @@ def loadIndex(self) -> bool: return False try: - self._tagsIndex.unpackData(theData["novelWriter.tagsIndex"]) - self._itemIndex.unpackData(theData["novelWriter.itemIndex"]) + self._tagsIndex.unpackData(data["novelWriter.tagsIndex"]) + self._itemIndex.unpackData(data["novelWriter.itemIndex"]) except Exception: logger.error("The index content is invalid") logException() @@ -298,14 +296,14 @@ def _scanActive(self, tHandle: str, nwItem: NWItem, text: str, tags: dict) -> No pTitle = TT_NONE # Tag of the previous title canSetHeader = True # First header has not yet been set - theLines = text.splitlines() - for nLine, aLine in enumerate(theLines, start=1): + lines = text.splitlines() + for n, line in enumerate(lines, start=1): - if aLine.strip() == "": + if line.strip() == "": continue - if aLine.startswith("#"): - hDepth, hText = self._splitHeading(aLine) + if line.startswith("#"): + hDepth, hText = self._splitHeading(line) if hDepth == "H0": continue @@ -313,33 +311,33 @@ def _scanActive(self, tHandle: str, nwItem: NWItem, text: str, tags: dict) -> No nwItem.setMainHeading(hDepth) canSetHeader = False - cTitle = self._itemIndex.addItemHeading(tHandle, nLine, hDepth, hText) + cTitle = self._itemIndex.addItemHeading(tHandle, n, hDepth, hText) if cTitle != TT_NONE: if nTitle > 0: # We have a new title, so we need to count the words of the previous one - lastText = "\n".join(theLines[nTitle-1:nLine-1]) + lastText = "\n".join(lines[nTitle-1:n-1]) self._indexWordCounts(tHandle, lastText, pTitle) - nTitle = nLine + nTitle = n pTitle = cTitle - elif aLine.startswith("@"): + elif line.startswith("@"): if cTitle != TT_NONE: - self._indexKeyword(tHandle, aLine, cTitle, nwItem.itemClass, tags) + self._indexKeyword(tHandle, line, cTitle, nwItem.itemClass, tags) - elif aLine.startswith("%"): + elif line.startswith("%"): if cTitle != TT_NONE: - toCheck = aLine[1:].lstrip() + toCheck = line[1:].lstrip() synTag = toCheck[:9].lower() - tLen = len(aLine) + tLen = len(line) cLen = len(toCheck) cOff = tLen - cLen if synTag == "synopsis:": - sText = aLine[cOff+9:].strip() + sText = line[cOff+9:].strip() self._itemIndex.setHeadingSynopsis(tHandle, cTitle, sText) # Count words for remaining text after last heading if pTitle != TT_NONE: - lastText = "\n".join(theLines[nTitle-1:]) + lastText = "\n".join(lines[nTitle-1:]) self._indexWordCounts(tHandle, lastText, pTitle) # Also count words on a page with no titles @@ -356,9 +354,9 @@ def _scanActive(self, tHandle: str, nwItem: NWItem, text: str, tags: dict) -> No def _scanInactive(self, nwItem: NWItem, text: str) -> None: """Scan an inactive document for meta data.""" - for aLine in text.splitlines(): - if aLine.startswith("#"): - hDepth, _ = self._splitHeading(aLine) + for line in text.splitlines(): + if line.startswith("#"): + hDepth, _ = self._splitHeading(line) if hDepth != "H0": nwItem.setMainHeading(hDepth) break @@ -380,7 +378,7 @@ def _splitHeading(self, line: str) -> tuple[str, str]: return "H2", line[4:].strip() return "H0", "" - def _indexWordCounts(self, tHandle: str, text: str, sTitle: str): + def _indexWordCounts(self, tHandle: str, text: str, sTitle: str) -> None: """Count text stats and save the counts to the index.""" cC, wC, pC = countWords(text) self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC) @@ -393,22 +391,22 @@ def _indexKeyword(self, tHandle: str, line: str, sTitle: str, of active tags is updated so that no longer used tags can be pruned later. """ - isValid, theBits, _ = self.scanThis(line) - if not isValid or len(theBits) < 2: - logger.warning("Skipping keyword with %d value(s) in '%s'", len(theBits), tHandle) + isValid, tBits, _ = self.scanThis(line) + if not isValid or len(tBits) < 2: + logger.warning("Skipping keyword with %d value(s) in '%s'", len(tBits), tHandle) return - if theBits[0] not in nwKeyWords.VALID_KEYS: - logger.warning("Skipping invalid keyword '%s' in '%s'", theBits[0], tHandle) + if tBits[0] not in nwKeyWords.VALID_KEYS: + logger.warning("Skipping invalid keyword '%s' in '%s'", tBits[0], tHandle) return - if theBits[0] == nwKeyWords.TAG_KEY: - tagName = theBits[1] + if tBits[0] == nwKeyWords.TAG_KEY: + tagName = tBits[1] self._tagsIndex.add(tagName, tHandle, sTitle, itemClass) self._itemIndex.setHeadingTag(tHandle, sTitle, tagName) tags[tagName] = True else: - self._itemIndex.addHeadingReferences(tHandle, sTitle, theBits[1:], theBits[0]) + self._itemIndex.addHeadingRef(tHandle, sTitle, tBits[1:], tBits[0]) return @@ -506,8 +504,8 @@ def novelStructure( they appear in the tree view and in the respective document files, but skipping all note files. """ - novStruct = self._itemIndex.iterNovelStructure(rHandle=rootHandle, skipExcl=skipExcl) - for tHandle, sTitle, hItem in novStruct: + structure = self._itemIndex.iterNovelStructure(rHandle=rootHandle, skipExcl=skipExcl) + for tHandle, sTitle, hItem in structure: yield f"{tHandle}:{sTitle}", tHandle, sTitle, hItem return @@ -557,14 +555,14 @@ def getTableOfContents( "words": hItem.wordCount, } - theToC = [( + result = [( tKey, tData[tKey]["level"], tData[tKey]["title"], tData[tKey]["words"] ) for tKey in tOrder] - return theToC + return result def getCounts(self, tHandle: str, sTitle: str | None = None) -> tuple[int, int, int]: """Return the counts for a file, or a section of a file, @@ -745,7 +743,7 @@ class around a single storage dictionary with a set of utility __slots__ = ("_project", "_items") - def __init__(self, project: NWProject): + def __init__(self, project: NWProject) -> None: self._project = project self._items: dict[str, IndexItem] = {} return @@ -753,7 +751,7 @@ def __init__(self, project: NWProject): def __contains__(self, tHandle: str) -> bool: return tHandle in self._items - def __delitem__(self, tHandle: str): + def __delitem__(self, tHandle: str) -> None: self._items.pop(tHandle, None) return @@ -764,12 +762,12 @@ def __getitem__(self, tHandle: str) -> IndexItem | None: # Methods ## - def clear(self): + def clear(self) -> None: """Clear the index.""" self._items = {} return - def add(self, tHandle: str, nwItem: NWItem): + def add(self, tHandle: str, nwItem: NWItem) -> None: """Add a new item to the index. This will overwrite the item if it already exists. """ @@ -837,7 +835,7 @@ def addItemHeading(self, tHandle: str, lineNo: int, level: str, text: str) -> st return sTitle return TT_NONE - def setHeadingCounts(self, tHandle: str, sTitle: str, cC: int, wC: int, pC: int): + def setHeadingCounts(self, tHandle: str, sTitle: str, cC: int, wC: int, pC: int) -> None: """Set the character, word and paragraph counts of a heading on a given item. """ @@ -845,22 +843,22 @@ def setHeadingCounts(self, tHandle: str, sTitle: str, cC: int, wC: int, pC: int) self._items[tHandle].setHeadingCounts(sTitle, cC, wC, pC) return - def setHeadingSynopsis(self, tHandle: str, sTitle: str, text: str): + def setHeadingSynopsis(self, tHandle: str, sTitle: str, text: str) -> None: """Set the synopsis text for a heading on a given item.""" if tHandle in self._items: self._items[tHandle].setHeadingSynopsis(sTitle, text) return - def setHeadingTag(self, tHandle: str, sTitle: str, tagKey: str): + def setHeadingTag(self, tHandle: str, sTitle: str, tagKey: str) -> None: """Set the main tag for a heading on a given item.""" if tHandle in self._items: self._items[tHandle].setHeadingTag(sTitle, tagKey) return - def addHeadingReferences(self, tHandle: str, sTitle: str, tagKeys: list[str], refType: str): + def addHeadingRef(self, tHandle: str, sTitle: str, tagKeys: list[str], refType: str) -> None: """Set the reference tags for a heading on a given item.""" if tHandle in self._items: - self._items[tHandle].addHeadingReferences(sTitle, tagKeys, refType) + self._items[tHandle].addHeadingRef(sTitle, tagKeys, refType) return ## @@ -871,7 +869,7 @@ def packData(self) -> dict: """Pack all the data of the index into a single dictionary.""" return {handle: item.packData() for handle, item in self._items.items()} - def unpackData(self, data: dict): + def unpackData(self, data: dict) -> None: """Iterate through the itemIndex loaded from cache and check that it's valid. This will raise errors if there is a problem. """ @@ -904,17 +902,13 @@ class IndexItem: must be reset each time the item is re-indexed. """ - __slots__ = ("_handle", "_item", "_headings", "_headings", "_count") + __slots__ = ("_handle", "_item", "_headings", "_count") - def __init__(self, tHandle: str, nwItem: NWItem): + def __init__(self, tHandle: str, nwItem: NWItem) -> None: self._handle = tHandle self._item = nwItem - self._headings: dict[str, IndexHeading] = {} + self._headings: dict[str, IndexHeading] = {TT_NONE: IndexHeading(TT_NONE)} self._count = 0 - - # Add a placeholder heading - self._headings[TT_NONE] = IndexHeading(TT_NONE) - return def __repr__(self) -> str: @@ -935,13 +929,14 @@ def __contains__(self, sTitle: str) -> bool: @property def item(self) -> NWItem: + """Return the project item of the index item.""" return self._item ## # Setters ## - def addHeading(self, tHeading: IndexHeading): + def addHeading(self, tHeading: IndexHeading) -> None: """Add a heading to the item. Also remove the placeholder entry if it exists. """ @@ -950,25 +945,25 @@ def addHeading(self, tHeading: IndexHeading): self._headings[tHeading.key] = tHeading return - def setHeadingCounts(self, sTitle: str, cCount: int, wCount: int, pCount: int): + def setHeadingCounts(self, sTitle: str, cCount: int, wCount: int, pCount: int) -> None: """Set the character, word and paragraph count of a heading.""" if sTitle in self._headings: self._headings[sTitle].setCounts(cCount, wCount, pCount) return - def setHeadingSynopsis(self, sTitle: str, text: str): + def setHeadingSynopsis(self, sTitle: str, text: str) -> None: """Set the synopsis text of a heading.""" if sTitle in self._headings: self._headings[sTitle].setSynopsis(text) return - def setHeadingTag(self, sTitle: str, tagKey: str): + def setHeadingTag(self, sTitle: str, tagKey: str) -> None: """Set the tag of a heading.""" if sTitle in self._headings: self._headings[sTitle].setTag(tagKey) return - def addHeadingReferences(self, sTitle: str, tagKeys: list[str], refType: str): + def addHeadingRef(self, sTitle: str, tagKeys: list[str], refType: str) -> None: """Add a reference key and all its types to a heading.""" if sTitle in self._headings: for tagKey in tagKeys: @@ -980,9 +975,11 @@ def addHeadingReferences(self, sTitle: str, tagKeys: list[str], refType: str): ## def items(self) -> ItemsView[str, IndexHeading]: + """Return IndexHeading items.""" return self._headings.items() def headings(self) -> list[str]: + """Return heading keys in sorted order.""" return sorted(self._headings.keys()) def allTags(self) -> list[str]: @@ -1015,7 +1012,7 @@ def packData(self) -> dict: return data - def unpackData(self, data: dict): + def unpackData(self, data: dict) -> None: """Unpack an item entry from the data.""" references = data.get("references", {}) for sTitle, hData in data.get("headings", {}).items(): @@ -1043,7 +1040,7 @@ class IndexHeading: "_paraCount", "_synopsis", "_tag", "_refs", ) - def __init__(self, key: str, line: int = 0, level: str = "H0", title: str = ""): + def __init__(self, key: str, line: int = 0, level: str = "H0", title: str = "") -> None: self._key = key self._line = line self._level = level @@ -1110,18 +1107,18 @@ def references(self) -> dict: # Setters ## - def setLevel(self, level: str): + def setLevel(self, level: str) -> None: """Set the level of the header if it's a valid value.""" if level in nwHeaders.H_VALID: self._level = level return - def setLine(self, line: int): + def setLine(self, line: int) -> None: """Set the line number of a heading.""" self._line = max(0, checkInt(line, 0)) return - def setCounts(self, charCount: int, wordCount: int, paraCount: int): + def setCounts(self, charCount: int, wordCount: int, paraCount: int) -> None: """Set the character, word and paragraph count. Make sure the value is an integer and is not smaller than 0. """ @@ -1130,17 +1127,17 @@ def setCounts(self, charCount: int, wordCount: int, paraCount: int): self._paraCount = max(0, checkInt(paraCount, 0)) return - def setSynopsis(self, text: str): + def setSynopsis(self, text: str) -> None: """Set the synopsis text and make sure it is a string.""" self._synopsis = str(text) return - def setTag(self, tagKey: str): + def setTag(self, tagKey: str) -> None: """Set the tag for references, and make sure it is a string.""" self._tag = str(tagKey).lower() return - def addReference(self, tagKey: str, refType: str): + def addReference(self, tagKey: str, refType: str) -> None: """Add a record of a reference tag, and what keyword types it is associated with. """ @@ -1176,7 +1173,7 @@ def packReferences(self) -> dict[str, str]: """ return {key: ",".join(sorted(list(value))) for key, value in self._refs.items()} - def unpackData(self, data: dict): + def unpackData(self, data: dict) -> None: """Unpack a heading entry from a dictionary.""" self.setLevel(data.get("level", "H0")) self._title = str(data.get("title", "")) @@ -1190,7 +1187,7 @@ def unpackData(self, data: dict): self._synopsis = str(data.get("synopsis", "")) return - def unpackReferences(self, data: dict): + def unpackReferences(self, data: dict) -> None: """Unpack a set of references from a dictionary.""" for tagKey, refTypes in data.items(): if not isinstance(tagKey, str): @@ -1232,55 +1229,55 @@ def countWords(text: str) -> tuple[int, int, int]: if nwUnicode.U_EMDASH in text: text = text.replace(nwUnicode.U_EMDASH, " ") - for aLine in text.splitlines(): + for line in text.splitlines(): countPara = True - if not aLine: + if not line: prevEmpty = True continue - if aLine[0] == "@" or aLine[0] == "%": + if line[0] == "@" or line[0] == "%": continue - if aLine[0] == "[": - if aLine.startswith(("[NEWPAGE]", "[NEW PAGE]", "[VSPACE]")): + if line[0] == "[": + if line.startswith(("[NEWPAGE]", "[NEW PAGE]", "[VSPACE]")): continue - elif aLine.startswith("[VSPACE:") and aLine.endswith("]"): + elif line.startswith("[VSPACE:") and line.endswith("]"): continue - elif aLine[0] == "#": - if aLine[:5] == "#### ": - aLine = aLine[5:] + elif line[0] == "#": + if line[:5] == "#### ": + line = line[5:] countPara = False - elif aLine[:4] == "### ": - aLine = aLine[4:] + elif line[:4] == "### ": + line = line[4:] countPara = False - elif aLine[:3] == "## ": - aLine = aLine[3:] + elif line[:3] == "## ": + line = line[3:] countPara = False - elif aLine[:2] == "# ": - aLine = aLine[2:] + elif line[:2] == "# ": + line = line[2:] countPara = False - elif aLine[:3] == "#! ": - aLine = aLine[3:] + elif line[:3] == "#! ": + line = line[3:] countPara = False - elif aLine[:4] == "##! ": - aLine = aLine[4:] + elif line[:4] == "##! ": + line = line[4:] countPara = False - elif aLine[0] == ">" or aLine[-1] == "<": - if aLine[:2] == ">>": - aLine = aLine[2:].lstrip(" ") - elif aLine[:1] == ">": - aLine = aLine[1:].lstrip(" ") - if aLine[-2:] == "<<": - aLine = aLine[:-2].rstrip(" ") - elif aLine[-1:] == "<": - aLine = aLine[:-1].rstrip(" ") - - wordCount += len(aLine.split()) - charCount += len(aLine) + elif line[0] == ">" or line[-1] == "<": + if line[:2] == ">>": + line = line[2:].lstrip(" ") + elif line[:1] == ">": + line = line[1:].lstrip(" ") + if line[-2:] == "<<": + line = line[:-2].rstrip(" ") + elif line[-1:] == "<": + line = line[:-1].rstrip(" ") + + wordCount += len(line.split()) + charCount += len(line) if countPara and prevEmpty: paraCount += 1 diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py index 40557584c..d8b7917eb 100644 --- a/tests/test_core/test_core_index.py +++ b/tests/test_core/test_core_index.py @@ -1027,9 +1027,9 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): itemIndex.setHeadingCounts(cHandle, "T0001", 60, 10, 2) itemIndex.setHeadingSynopsis(cHandle, "T0001", "In the beginning ...") itemIndex.setHeadingTag(cHandle, "T0001", "One") - itemIndex.addHeadingReferences(cHandle, "T0001", ["Jane"], "@pov") - itemIndex.addHeadingReferences(cHandle, "T0001", ["Jane"], "@focus") - itemIndex.addHeadingReferences(cHandle, "T0001", ["Jane", "John"], "@char") + itemIndex.addHeadingRef(cHandle, "T0001", ["Jane"], "@pov") + itemIndex.addHeadingRef(cHandle, "T0001", ["Jane"], "@focus") + itemIndex.addHeadingRef(cHandle, "T0001", ["Jane", "John"], "@char") idxData = itemIndex.packData() assert idxData[cHandle]["headings"]["T0001"] == {