Move the tags index into a wrapper class
This commit is contained in:
+95
-26
@@ -56,7 +56,7 @@ class NWIndex():
|
|||||||
self._indexBroken = False
|
self._indexBroken = False
|
||||||
|
|
||||||
# Indices
|
# Indices
|
||||||
self._tags = {}
|
self._tagsIndex = TagsIndex()
|
||||||
self._itemIndex = ItemIndex(theProject)
|
self._itemIndex = ItemIndex(theProject)
|
||||||
|
|
||||||
# TimeStamps
|
# TimeStamps
|
||||||
@@ -81,7 +81,7 @@ class NWIndex():
|
|||||||
def clearIndex(self):
|
def clearIndex(self):
|
||||||
"""Clear the index dictionaries and time stamps.
|
"""Clear the index dictionaries and time stamps.
|
||||||
"""
|
"""
|
||||||
self._tags = {}
|
self._tagsIndex.clear()
|
||||||
self._itemIndex.clear()
|
self._itemIndex.clear()
|
||||||
self._timeNovel = 0
|
self._timeNovel = 0
|
||||||
self._timeNotes = 0
|
self._timeNotes = 0
|
||||||
@@ -93,7 +93,7 @@ class NWIndex():
|
|||||||
"""
|
"""
|
||||||
logger.debug("Removing item '%s' from the index", tHandle)
|
logger.debug("Removing item '%s' from the index", tHandle)
|
||||||
for tTag in self._itemIndex.allItemTags(tHandle):
|
for tTag in self._itemIndex.allItemTags(tHandle):
|
||||||
self._tags.pop(tTag, None)
|
del self._tagsIndex[tTag]
|
||||||
|
|
||||||
del self._itemIndex[tHandle]
|
del self._itemIndex[tHandle]
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ class NWIndex():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._validateTagsIndex(theData["tagsIndex"])
|
self._tagsIndex.unpackData(theData["tagsIndex"])
|
||||||
self._itemIndex.unpackData(theData["itemIndex"])
|
self._itemIndex.unpackData(theData["itemIndex"])
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error("The index content is invalid")
|
logger.error("The index content is invalid")
|
||||||
@@ -186,10 +186,11 @@ class NWIndex():
|
|||||||
tStart = time()
|
tStart = time()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
tagsIndex = self._tagsIndex.packData()
|
||||||
itemIndex = self._itemIndex.packData()
|
itemIndex = self._itemIndex.packData()
|
||||||
with open(indexFile, mode="w+", encoding="utf-8") as outFile:
|
with open(indexFile, mode="w+", encoding="utf-8") as outFile:
|
||||||
outFile.write("{\n")
|
outFile.write("{\n")
|
||||||
outFile.write(f' "tagsIndex": {jsonEncode(self._tags, n=1, nmax=2)},\n')
|
outFile.write(f' "tagsIndex": {jsonEncode(tagsIndex, n=1, nmax=2)},\n')
|
||||||
outFile.write(f' "itemIndex": {jsonEncode(itemIndex, n=1, nmax=4)}\n')
|
outFile.write(f' "itemIndex": {jsonEncode(itemIndex, n=1, nmax=4)}\n')
|
||||||
outFile.write("}\n")
|
outFile.write("}\n")
|
||||||
|
|
||||||
@@ -356,11 +357,7 @@ class NWIndex():
|
|||||||
|
|
||||||
sTitle = f"T{nTitle:06d}"
|
sTitle = f"T{nTitle:06d}"
|
||||||
if theBits[0] == nwKeyWords.TAG_KEY:
|
if theBits[0] == nwKeyWords.TAG_KEY:
|
||||||
self._tags[theBits[1]] = {
|
self._tagsIndex.add(theBits[1], tHandle, sTitle, itemClass)
|
||||||
"handle": tHandle,
|
|
||||||
"heading": sTitle,
|
|
||||||
"class": itemClass.name,
|
|
||||||
}
|
|
||||||
self._itemIndex.setHeadingTag(tHandle, sTitle, theBits[1])
|
self._itemIndex.setHeadingTag(tHandle, sTitle, theBits[1])
|
||||||
else:
|
else:
|
||||||
self._itemIndex.addHeadingReferences(tHandle, sTitle, theBits[1:], theBits[0])
|
self._itemIndex.addHeadingReferences(tHandle, sTitle, theBits[1:], theBits[0])
|
||||||
@@ -425,8 +422,8 @@ class NWIndex():
|
|||||||
|
|
||||||
# For a tag, only the first value is accepted, the rest are ignored
|
# For a tag, only the first value is accepted, the rest are ignored
|
||||||
if theBits[0] == nwKeyWords.TAG_KEY and nBits > 1:
|
if theBits[0] == nwKeyWords.TAG_KEY and nBits > 1:
|
||||||
if theBits[1] in self._tags:
|
if theBits[1] in self._tagsIndex:
|
||||||
isGood[1] = self._tags[theBits[1]].get("handle") == tItem.itemHandle
|
isGood[1] = self._tagsIndex.tagHandle(theBits[1]) == tItem.itemHandle
|
||||||
else:
|
else:
|
||||||
isGood[1] = True
|
isGood[1] = True
|
||||||
return isGood
|
return isGood
|
||||||
@@ -434,8 +431,8 @@ class NWIndex():
|
|||||||
# If we're still here, we check that the references exist
|
# If we're still here, we check that the references exist
|
||||||
theKey = nwKeyWords.KEY_CLASS[theBits[0]].name
|
theKey = nwKeyWords.KEY_CLASS[theBits[0]].name
|
||||||
for n in range(1, nBits):
|
for n in range(1, nBits):
|
||||||
if theBits[n] in self._tags:
|
if theBits[n] in self._tagsIndex:
|
||||||
isGood[n] = theKey == self._tags[theBits[n]].get("class")
|
isGood[n] = self._tagsIndex.tagClass(theBits[n]) == theKey
|
||||||
|
|
||||||
return isGood
|
return isGood
|
||||||
|
|
||||||
@@ -582,22 +579,98 @@ class NWIndex():
|
|||||||
def getTagSource(self, theTag):
|
def getTagSource(self, theTag):
|
||||||
"""Return the source location of a given tag.
|
"""Return the source location of a given tag.
|
||||||
"""
|
"""
|
||||||
ref = self._tags.get(theTag, {})
|
tHandle = self._tagsIndex.tagHandle(theTag)
|
||||||
return ref.get("handle"), ref.get("heading", H_NONE)
|
sTitle = self._tagsIndex.tagHeading(theTag)
|
||||||
|
return tHandle, sTitle
|
||||||
|
|
||||||
|
# END Class NWIndex
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================================== #
|
||||||
|
# Indexer Objects
|
||||||
|
# =============================================================================================== #
|
||||||
|
|
||||||
|
class TagsIndex:
|
||||||
|
"""A wrapper class that holds the reverse lookup tags index.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._tags = {}
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Internal Functions
|
# Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def _validateTagsIndex(self, tagsIndex):
|
def clear(self):
|
||||||
|
"""Clear the index.
|
||||||
|
"""
|
||||||
|
self._tags = {}
|
||||||
|
return
|
||||||
|
|
||||||
|
def __contains__(self, tagKey):
|
||||||
|
"""Check if a tag exists in the index,
|
||||||
|
"""
|
||||||
|
return tagKey in self._tags
|
||||||
|
|
||||||
|
def __delitem__(self, tagKey):
|
||||||
|
"""Delete an entry in the index.
|
||||||
|
"""
|
||||||
|
self._tags.pop(tagKey, None)
|
||||||
|
return
|
||||||
|
|
||||||
|
def __getitem__(self, tagKey):
|
||||||
|
"""Return a tag, or return None if it isn't found.
|
||||||
|
"""
|
||||||
|
return self._tags.get(tagKey, None)
|
||||||
|
|
||||||
|
def add(self, tagKey, tHandle, sTitle, itemClass):
|
||||||
|
"""Add a key to the index and set all values.
|
||||||
|
"""
|
||||||
|
self._tags[tagKey] = {
|
||||||
|
"handle": tHandle, "heading": sTitle, "class": itemClass.name
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
def tagHandle(self, tagKey):
|
||||||
|
"""Get the handle of a given tag.
|
||||||
|
"""
|
||||||
|
if tagKey in self._tags:
|
||||||
|
return self._tags.get(tagKey).get("handle")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def tagHeading(self, tagKey):
|
||||||
|
"""Get the heading of a given tag.
|
||||||
|
"""
|
||||||
|
if tagKey in self._tags:
|
||||||
|
return self._tags.get(tagKey).get("heading")
|
||||||
|
return H_NONE
|
||||||
|
|
||||||
|
def tagClass(self, tagKey):
|
||||||
|
"""Get the class of a given tag.
|
||||||
|
"""
|
||||||
|
if tagKey in self._tags:
|
||||||
|
return self._tags.get(tagKey).get("class")
|
||||||
|
return None
|
||||||
|
|
||||||
|
##
|
||||||
|
# Pack/Unpack
|
||||||
|
##
|
||||||
|
|
||||||
|
def packData(self):
|
||||||
|
"""Pack all the data of the tags into a single dictionary.
|
||||||
|
"""
|
||||||
|
return self._tags
|
||||||
|
|
||||||
|
def unpackData(self, data):
|
||||||
"""Iterate through the tagsIndex loaded from cache and check
|
"""Iterate through the tagsIndex loaded from cache and check
|
||||||
that it's valid.
|
that it's valid.
|
||||||
"""
|
"""
|
||||||
self._tags = {}
|
self._tags = {}
|
||||||
if not isinstance(tagsIndex, dict):
|
if not isinstance(data, dict):
|
||||||
raise ValueError("tagsIndex is not a dict")
|
raise ValueError("tagsIndex is not a dict")
|
||||||
|
|
||||||
for tagKey, tagData in tagsIndex.items():
|
for tagKey, tagData in data.items():
|
||||||
if not isinstance(tagKey, str):
|
if not isinstance(tagKey, str):
|
||||||
raise ValueError("tagsIndex keys must be a strings")
|
raise ValueError("tagsIndex keys must be a strings")
|
||||||
if "handle" not in tagData:
|
if "handle" not in tagData:
|
||||||
@@ -613,17 +686,13 @@ class NWIndex():
|
|||||||
if not isItemClass(tagData["class"]):
|
if not isItemClass(tagData["class"]):
|
||||||
raise ValueError("tagsIndex handle must be an nwItemClass")
|
raise ValueError("tagsIndex handle must be an nwItemClass")
|
||||||
|
|
||||||
self._tags = tagsIndex
|
self._tags = data
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# END Class NWIndex
|
# END Class TagsIndex
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================================== #
|
|
||||||
# Indexer Objects
|
|
||||||
# =============================================================================================== #
|
|
||||||
|
|
||||||
class ItemIndex:
|
class ItemIndex:
|
||||||
"""A wrapper object holding the indexed items.
|
"""A wrapper object holding the indexed items.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -68,19 +68,19 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir):
|
|||||||
assert theIndex.saveIndex() is True
|
assert theIndex.saveIndex() is True
|
||||||
|
|
||||||
# Take a copy of the index
|
# Take a copy of the index
|
||||||
tagIndex = str(theIndex._tags)
|
tagIndex = str(theIndex._tagsIndex.packData())
|
||||||
itemsIndex = str(theIndex._itemIndex.packData())
|
itemsIndex = str(theIndex._itemIndex.packData())
|
||||||
|
|
||||||
# Delete a handle
|
# Delete a handle
|
||||||
assert theIndex._tags.get("Bod", None) is not None
|
assert theIndex._tagsIndex["Bod"] is not None
|
||||||
assert theIndex._itemIndex["4c4f28287af27"] is not None
|
assert theIndex._itemIndex["4c4f28287af27"] is not None
|
||||||
theIndex.deleteHandle("4c4f28287af27")
|
theIndex.deleteHandle("4c4f28287af27")
|
||||||
assert theIndex._tags.get("Bod", None) is None
|
assert theIndex._tagsIndex["Bod"] is None
|
||||||
assert theIndex._itemIndex["4c4f28287af27"] is None
|
assert theIndex._itemIndex["4c4f28287af27"] is None
|
||||||
|
|
||||||
# Clear the index
|
# Clear the index
|
||||||
theIndex.clearIndex()
|
theIndex.clearIndex()
|
||||||
assert theIndex._tags == {}
|
assert theIndex._tagsIndex._tags == {}
|
||||||
assert theIndex._itemIndex._items == {}
|
assert theIndex._itemIndex._items == {}
|
||||||
|
|
||||||
# Make the load fail
|
# Make the load fail
|
||||||
@@ -91,7 +91,7 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, outDir, refDir):
|
|||||||
# Make the load pass
|
# Make the load pass
|
||||||
assert theIndex.loadIndex() is True
|
assert theIndex.loadIndex() is True
|
||||||
|
|
||||||
assert str(theIndex._tags) == tagIndex
|
assert str(theIndex._tagsIndex.packData()) == tagIndex
|
||||||
assert str(theIndex._itemIndex.packData()) == itemsIndex
|
assert str(theIndex._itemIndex.packData()) == itemsIndex
|
||||||
|
|
||||||
# Break the index and check that we notice
|
# Break the index and check that we notice
|
||||||
@@ -188,9 +188,9 @@ def testCoreIndex_CheckThese(nwMinimal, mockGUI):
|
|||||||
"@pov: Jane\n"
|
"@pov: Jane\n"
|
||||||
"@invalid: John\n" # Checks for issue #688
|
"@invalid: John\n" # Checks for issue #688
|
||||||
))
|
))
|
||||||
assert theIndex._tags == {
|
assert theIndex._tagsIndex.tagHandle("Jane") == cHandle
|
||||||
"Jane": {"handle": cHandle, "heading": "T000001", "class": "CHARACTER"}
|
assert theIndex._tagsIndex.tagHeading("Jane") == "T000001"
|
||||||
}
|
assert theIndex._tagsIndex.tagClass("Jane") == "CHARACTER"
|
||||||
assert theIndex.getNovelData(nHandle, "T000001").title == "Hello World!"
|
assert theIndex.getNovelData(nHandle, "T000001").title == "Hello World!"
|
||||||
assert theIndex.getReferences(nHandle, "T000001") == {
|
assert theIndex.getReferences(nHandle, "T000001") == {
|
||||||
"@char": [],
|
"@char": [],
|
||||||
@@ -301,9 +301,9 @@ def testCoreIndex_ScanText(nwMinimal, mockGUI):
|
|||||||
"This is a story about Jane Smith.\n\n"
|
"This is a story about Jane Smith.\n\n"
|
||||||
"Well, not really.\n"
|
"Well, not really.\n"
|
||||||
))
|
))
|
||||||
assert theIndex._tags == {
|
assert theIndex._tagsIndex.tagHandle("Jane") == cHandle
|
||||||
"Jane": {"handle": cHandle, "heading": "T000001", "class": "CHARACTER"}
|
assert theIndex._tagsIndex.tagHeading("Jane") == "T000001"
|
||||||
}
|
assert theIndex._tagsIndex.tagClass("Jane") == "CHARACTER"
|
||||||
assert theIndex.getNovelData(nHandle, "T000001").title == "Hello World!"
|
assert theIndex.getNovelData(nHandle, "T000001").title == "Hello World!"
|
||||||
|
|
||||||
# Title Indexing
|
# Title Indexing
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
|
|||||||
|
|
||||||
# Rebuild the index
|
# Rebuild the index
|
||||||
nwGUI.mainMenu.aRebuildIndex.activate(QAction.Trigger)
|
nwGUI.mainMenu.aRebuildIndex.activate(QAction.Trigger)
|
||||||
assert nwGUI.theProject.index._tags != {}
|
assert nwGUI.theProject.index._tagsIndex._tags != {}
|
||||||
assert nwGUI.theProject.index._itemIndex._items != {}
|
assert nwGUI.theProject.index._itemIndex._items != {}
|
||||||
|
|
||||||
# Select a document in the project tree
|
# Select a document in the project tree
|
||||||
|
|||||||
Reference in New Issue
Block a user