Got a working version of index builder that seems to do what is needed.

This commit is contained in:
Veronica K. B. Olsen
2019-05-30 18:45:17 +02:00
parent 2962ab4070
commit b526b0f5d9
8 changed files with 292 additions and 67 deletions
+1
View File
@@ -385,6 +385,7 @@ class GuiMain(QMainWindow):
logger.debug("Rebuilding indices ...") logger.debug("Rebuilding indices ...")
self.treeView.saveTreeOrder() self.treeView.saveTreeOrder()
self.tagIndex.clearIndex()
nItems = len(self.theProject.treeOrder) nItems = len(self.theProject.treeOrder)
dlgProg = QProgressDialog("Scanning files ...", "Cancel", 0, nItems, self) dlgProg = QProgressDialog("Scanning files ...", "Cancel", 0, nItems, self)
+147 -47
View File
@@ -29,18 +29,32 @@ import nw
from os import path from os import path
from nw.project.document import NWDoc from nw.project.document import NWDoc
from nw.enum import nwItemType from nw.enum import nwItemType, nwItemClass
from nw.constants import nwFiles from nw.constants import nwFiles
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class NWIndex(): class NWIndex():
TAG_KEY = "tag" TAG_KEY = "@tag"
NOTE_KEYS = ["pov","char","plot","time","location","object","custom"] POV_KEY = "@pov"
NOVEL_KEYS = ["scene","chapter","part"] CHAR_KEY = "@char"
PLOT_KEY = "@plot"
TIME_KEY = "@time"
WORLD_KEY = "@location"
OBJECT_KEY = "@object"
CUSTOM_KEY = "@custom"
VALID_KEYS = [TAG_KEY] + NOTE_KEYS + NOVEL_KEYS NOTE_KEYS = [PLOT_KEY, POV_KEY, CHAR_KEY, WORLD_KEY, TIME_KEY, OBJECT_KEY, CUSTOM_KEY]
VALID_CLASS = {
nwItemClass.NOVEL : [],
nwItemClass.PLOT : [PLOT_KEY],
nwItemClass.CHARACTER : [POV_KEY, CHAR_KEY],
nwItemClass.WORLD : [WORLD_KEY],
nwItemClass.TIMELINE : [TIME_KEY],
nwItemClass.OBJECT : [OBJECT_KEY],
nwItemClass.CUSTOM : [CUSTOM_KEY],
}
def __init__(self, theProject, theParent): def __init__(self, theProject, theParent):
@@ -50,28 +64,44 @@ class NWIndex():
self.mainConf = self.theParent.mainConf self.mainConf = self.theParent.mainConf
# Indices # Indices
self.itemIndex = {} self.tagIndex = {}
self.noteIndex = {}
self.novelIndex = {}
return return
def clearIndex(self): def clearIndex(self):
self.itemIndex = {} self.tagIndex = {}
self.noteIndex = {}
self.novelIndex = {}
return return
##
# Load and Save Index to/from File
##
def loadIndex(self): def loadIndex(self):
theData = {}
indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE) indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
if path.isfile(indexFile): if path.isfile(indexFile):
logger.debug("Loading index file") logger.debug("Loading index file")
try: try:
with open(indexFile,mode="r") as inFile: with open(indexFile,mode="r") as inFile:
theJson = inFile.read() theJson = inFile.read()
self.itemIndex = json.loads(theJson) theData = json.loads(theJson)
except Exception as e: except Exception as e:
logger.error("Failed to load index file") logger.error("Failed to load index file")
logger.error(str(e)) logger.error(str(e))
return False return False
if "tagIndex" in theData.keys():
self.tagIndex = theData["tagIndex"]
if "noteIndex" in theData.keys():
self.noteIndex = theData["noteIndex"]
if "novelIndex" in theData.keys():
self.novelIndex = theData["novelIndex"]
return True return True
return False return False
@@ -86,7 +116,11 @@ class NWIndex():
nIndent = None nIndent = None
try: try:
with open(indexFile,mode="w+") as outFile: with open(indexFile,mode="w+") as outFile:
outFile.write(json.dumps(self.itemIndex, indent=nIndent)) outFile.write(json.dumps({
"tagIndex" : self.tagIndex,
"noteIndex" : self.noteIndex,
"novelIndex" : self.novelIndex,
}, indent=nIndent))
except Exception as e: except Exception as e:
logger.error("Failed to save index file") logger.error("Failed to save index file")
logger.error(str(e)) logger.error(str(e))
@@ -94,64 +128,130 @@ class NWIndex():
return True return True
##
# Index Building
##
def scanText(self, tHandle, theText): def scanText(self, tHandle, theText):
theItem = self.theProject.getItem(tHandle) theItem = self.theProject.getItem(tHandle)
if theItem is None: if theItem is None: return False
return False if theItem.itemType != nwItemType.FILE: return False
if theItem.itemType != nwItemType.FILE: itemClass = theItem.itemClass
return False
logger.debug("Indexing item with handle %s" % tHandle) logger.debug("Indexing item with handle %s" % tHandle)
self.itemIndex[tHandle] = {} # Check file type, and reset its old index
if itemClass == nwItemClass.NOVEL:
self.novelIndex[tHandle] = []
self.noteIndex[tHandle] = []
isNovel = True
else:
isNovel = False
# Also clear references to file in tag index
for aTag in self.tagIndex:
if self.tagIndex[aTag][1] == tHandle:
self.tagIndex.pop(aTag)
nLine = 0 nLine = 0
for aLine in theText.splitlines(): for aLine in theText.splitlines():
aLine = aLine.strip() aLine = aLine.strip()
nLine += 1 nLine += 1
nChar = len(aLine) nChar = len(aLine)
if nChar > 0 and aLine[0] == "@": if nChar == 0: continue
self.indexThis(tHandle, aLine, nLine, theItem) if aLine[0] == "#":
if isNovel:
self.indexTitle(tHandle, aLine, nLine)
elif aLine[0] == "@":
if isNovel:
self.indexNoteRef(tHandle, aLine, nLine)
else:
self.indexTag(tHandle, aLine, nLine, itemClass)
return True return True
def indexThis(self, tHandle, aLine, nLine, theItem): def indexTitle(self, tHandle, aLine, nLine):
nChar = len(aLine) if aLine.startswith("# "):
nPos = aLine.find(":") hDepth = 1
if nPos < 2 or nChar < nPos+2: hText = aLine[2:].strip()
return False elif aLine.startswith("## "):
hDepth = 2
aKey = aLine[1:nPos].strip().lower() hText = aLine[3:].strip()
tVal = aLine[nPos+1:].strip().lower() elif aLine.startswith("### "):
if aKey not in self.VALID_KEYS: hDepth = 3
logger.verbose("Not a valid key '%s'" % aKey) hText = aLine[4:].strip()
return False elif aLine.startswith("#### "):
hDepth = 4
logger.verbose("Found valid key '%s'" % aKey) hText = aLine[5:].strip()
if aKey == self.TAG_KEY:
if tVal.find(",") >- 0:
return False
self._addItem(tHandle, aKey, nLine, tVal)
else: else:
kVal = tVal.split(",") return False
cVal = [aVal.strip() for aVal in kVal]
if len(cVal) > 0: if hText != "":
self._addItem(tHandle, aKey, nLine, cVal) self.novelIndex[tHandle].append([nLine, hDepth, hText])
else:
return False
return True return True
## def indexNoteRef(self, tHandle, aLine, nLine):
# Internal Functions
##
def _addItem(self, tHandle, tKey, tLine, tVal): isValid, theBits, thePos = self.scanThis(aLine)
if tKey not in self.itemIndex[tHandle].keys(): if not isValid or len(theBits) == 0:
self.itemIndex[tHandle][tKey] = [] return False
self.itemIndex[tHandle][tKey].append([tLine, tVal])
return theKey = theBits[0]
if theKey in self.NOTE_KEYS:
for aVal in theBits[1:]:
self.noteIndex[tHandle].append([nLine, theKey, aVal])
return True
def indexTag(self, tHandle, aLine, nLine, itemClass):
isValid, theBits, thePos = self.scanThis(aLine)
if not isValid or len(theBits) != 2:
return False
if theBits[0] == self.TAG_KEY:
self.tagIndex[theBits[1]] = [nLine, tHandle, itemClass.name]
return True
def scanThis(self, aLine):
theBits = []
thePos = []
aLine = aLine.strip()
nChar = len(aLine)
if nChar < 2:
return False, theBits, thePos
if aLine[0] != "@":
return False, theBits, thePos
cPos = 0
cKey, cSep, cVals = aLine.partition(":")
sKey = cKey.strip()
if sKey == "@":
return False, theBits, thePos
theBits.append(sKey.lower())
thePos.append(cPos)
cPos += len(sKey) + 1
if cVals == "":
# No values, so we're done
return True, theBits, thePos
aVals = cVals.split(",")
for cVal in aVals:
sVal = cVal.strip()
rLen = len(cVal.lstrip())
tLen = len(cVal)
theBits.append(sVal.lower())
thePos.append(cPos+tLen-rLen)
cPos += tLen + 1
return True, theBits, thePos
# END Class NWIndex # END Class NWIndex
@@ -1,3 +1,5 @@
# John Smith # John Smith
@tag: John
Hes pretty cool. Not Brad Pitt though. Hes pretty cool. Not Brad Pitt though.
@@ -3,8 +3,8 @@
## This is the Subtitle ## This is the Subtitle
% Begin Meta % Begin Meta
@POV: Sam @POV: Jane
@Chars: Sam, Adam, Scott @Char: Jane, John
% End Meta % End Meta
Some text here would look good as well, and maybe some "dialogue"? Some text here would look good as well, and maybe some "dialogue"?
@@ -17,8 +17,3 @@ This paragraph is also meaningless. At least a bit. Its also very short. But
This one is a bit longer. “It also has some dialogue in it” she said, before she moved on to check if the spellchecker worked. It did. “Cool,” she concluded. This one is a bit longer. “It also has some dialogue in it” she said, before she moved on to check if the spellchecker worked. It did. “Cool,” she concluded.
@ToDo: Stuff that will be done at some point
@@ -1,3 +1,5 @@
# Jane Smith # Jane Smith
@tag: Jane
Shes pretty cool. Not Angelina Jolie though. Shes pretty cool. Not Angelina Jolie though.
+88
View File
@@ -13,3 +13,91 @@ Start: 2019-05-26 15:37:47 End: 2019-05-26 15:38:07 Words: 538
Start: 2019-05-26 15:38:11 End: 2019-05-26 15:43:07 Words: 2 Start: 2019-05-26 15:38:11 End: 2019-05-26 15:43:07 Words: 2
Start: 2019-05-26 15:49:18 End: 2019-05-26 15:49:47 Words: 0 Start: 2019-05-26 15:49:18 End: 2019-05-26 15:49:47 Words: 0
Start: 2019-05-26 15:52:39 End: 2019-05-26 15:52:56 Words: 0 Start: 2019-05-26 15:52:39 End: 2019-05-26 15:52:56 Words: 0
Start: 2019-05-27 21:23:07 End: 2019-05-27 21:26:41 Words: 0
Start: 2019-05-27 21:26:56 End: 2019-05-27 21:27:48 Words: 0
Start: 2019-05-27 21:29:11 End: 2019-05-27 21:30:30 Words: 0
Start: 2019-05-27 21:33:30 End: 2019-05-27 21:35:14 Words: 0
Start: 2019-05-27 21:35:20 End: 2019-05-27 21:36:03 Words: 0
Start: 2019-05-27 21:36:07 End: 2019-05-27 21:36:23 Words: 0
Start: 2019-05-27 21:36:27 End: 2019-05-27 21:37:46 Words: 0
Start: 2019-05-27 21:37:50 End: 2019-05-27 21:38:05 Words: 0
Start: 2019-05-27 21:38:14 End: 2019-05-27 21:38:24 Words: 0
Start: 2019-05-27 21:39:08 End: 2019-05-27 21:39:23 Words: 0
Start: 2019-05-27 21:42:21 End: 2019-05-27 21:42:39 Words: 0
Start: 2019-05-27 21:45:07 End: 2019-05-27 21:45:18 Words: 0
Start: 2019-05-27 21:58:30 End: 2019-05-27 21:58:43 Words: 0
Start: 2019-05-27 22:02:01 End: 2019-05-27 22:02:09 Words: 0
Start: 2019-05-27 22:05:47 End: 2019-05-27 22:06:02 Words: 0
Start: 2019-05-27 22:08:25 End: 2019-05-27 22:09:35 Words: 0
Start: 2019-05-27 22:11:40 End: 2019-05-27 22:14:18 Words: 3
Start: 2019-05-27 22:20:55 End: 2019-05-27 22:21:40 Words: -2
Start: 2019-05-27 22:22:38 End: 2019-05-27 22:23:12 Words: 16
Start: 2019-05-27 22:55:34 End: 2019-05-27 22:56:09 Words: 0
Start: 2019-05-27 22:58:47 End: 2019-05-27 23:00:47 Words: 17
Start: 2019-05-28 18:20:15 End: 2019-05-28 18:20:30 Words: -1
Start: 2019-05-28 18:21:04 End: 2019-05-28 18:21:24 Words: 0
Start: 2019-05-28 18:29:41 End: 2019-05-28 18:30:16 Words: 0
Start: 2019-05-28 18:30:59 End: 2019-05-28 18:32:06 Words: 0
Start: 2019-05-28 18:34:33 End: 2019-05-28 18:34:48 Words: 1
Start: 2019-05-28 18:43:08 End: 2019-05-28 18:43:21 Words: 0
Start: 2019-05-28 18:49:29 End: 2019-05-28 18:49:40 Words: 0
Start: 2019-05-28 19:13:20 End: 2019-05-28 19:13:24 Words: -1
Start: 2019-05-28 19:17:05 End: 2019-05-28 19:17:13 Words: 1
Start: 2019-05-28 19:49:44 End: 2019-05-28 19:52:23 Words: -1
Start: 2019-05-28 19:52:29 End: 2019-05-28 19:54:42 Words: 0
Start: 2019-05-28 19:55:33 End: 2019-05-28 19:55:56 Words: 0
Start: 2019-05-28 19:57:29 End: 2019-05-28 20:02:33 Words: 0
Start: 2019-05-28 20:02:38 End: 2019-05-28 20:05:19 Words: 0
Start: 2019-05-28 20:05:23 End: 2019-05-28 20:06:48 Words: 0
Start: 2019-05-28 20:07:00 End: 2019-05-28 20:12:26 Words: 0
Start: 2019-05-28 20:12:31 End: 2019-05-28 20:17:58 Words: -16
Start: 2019-05-28 20:28:31 End: 2019-05-28 20:28:53 Words: 0
Start: 2019-05-28 20:28:57 End: 2019-05-28 20:29:05 Words: 16
Start: 2019-05-28 21:00:49 End: 2019-05-28 21:01:45 Words: 1
Start: 2019-05-28 21:02:01 End: 2019-05-28 21:02:06 Words: 0
Start: 2019-05-28 21:17:57 End: 2019-05-28 21:25:48 Words: 0
Start: 2019-05-28 21:35:39 End: 2019-05-28 21:35:47 Words: 0
Start: 2019-05-28 22:22:54 End: 2019-05-28 22:23:12 Words: 0
Start: 2019-05-30 11:58:19 End: 2019-05-30 11:59:15 Words: 0
Start: 2019-05-30 11:59:19 End: 2019-05-30 11:59:26 Words: 0
Start: 2019-05-30 11:59:55 End: 2019-05-30 12:00:09 Words: 0
Start: 2019-05-30 12:01:47 End: 2019-05-30 12:02:08 Words: 0
Start: 2019-05-30 12:03:56 End: 2019-05-30 12:04:23 Words: 0
Start: 2019-05-30 12:09:24 End: 2019-05-30 12:09:47 Words: 0
Start: 2019-05-30 12:10:13 End: 2019-05-30 12:10:40 Words: 0
Start: 2019-05-30 12:12:14 End: 2019-05-30 12:12:33 Words: 0
Start: 2019-05-30 12:13:56 End: 2019-05-30 12:14:08 Words: 0
Start: 2019-05-30 12:14:24 End: 2019-05-30 12:14:44 Words: 0
Start: 2019-05-30 12:14:53 End: 2019-05-30 12:15:31 Words: 0
Start: 2019-05-30 12:17:36 End: 2019-05-30 12:17:49 Words: 0
Start: 2019-05-30 12:20:12 End: 2019-05-30 12:20:24 Words: 0
Start: 2019-05-30 12:36:42 End: 2019-05-30 12:37:21 Words: 0
Start: 2019-05-30 12:37:42 End: 2019-05-30 12:38:01 Words: 0
Start: 2019-05-30 12:47:13 End: 2019-05-30 12:47:27 Words: 0
Start: 2019-05-30 12:47:47 End: 2019-05-30 12:48:03 Words: 0
Start: 2019-05-30 12:50:01 End: 2019-05-30 12:50:15 Words: 0
Start: 2019-05-30 12:54:26 End: 2019-05-30 12:54:34 Words: 0
Start: 2019-05-30 15:25:50 End: 2019-05-30 15:25:59 Words: 0
Start: 2019-05-30 15:43:36 End: 2019-05-30 15:43:44 Words: 0
Start: 2019-05-30 15:52:46 End: 2019-05-30 15:53:12 Words: 0
Start: 2019-05-30 15:53:44 End: 2019-05-30 15:53:56 Words: 0
Start: 2019-05-30 15:56:29 End: 2019-05-30 15:57:35 Words: 0
Start: 2019-05-30 15:57:41 End: 2019-05-30 15:59:58 Words: 0
Start: 2019-05-30 16:00:01 End: 2019-05-30 16:00:28 Words: 0
Start: 2019-05-30 16:00:56 End: 2019-05-30 16:01:06 Words: 0
Start: 2019-05-30 16:01:32 End: 2019-05-30 16:01:48 Words: 0
Start: 2019-05-30 16:02:35 End: 2019-05-30 16:03:03 Words: 0
Start: 2019-05-30 17:00:23 End: 2019-05-30 17:21:07 Words: 0
Start: 2019-05-30 17:21:11 End: 2019-05-30 17:21:24 Words: 0
Start: 2019-05-30 17:24:41 End: 2019-05-30 17:24:46 Words: 0
Start: 2019-05-30 17:29:08 End: 2019-05-30 17:29:19 Words: 0
Start: 2019-05-30 18:12:59 End: 2019-05-30 18:13:07 Words: 0
Start: 2019-05-30 18:13:32 End: 2019-05-30 18:13:42 Words: 0
Start: 2019-05-30 18:16:13 End: 2019-05-30 18:16:18 Words: 0
Start: 2019-05-30 18:22:24 End: 2019-05-30 18:22:30 Words: 0
Start: 2019-05-30 18:22:43 End: 2019-05-30 18:24:11 Words: 0
Start: 2019-05-30 18:25:40 End: 2019-05-30 18:25:45 Words: 0
Start: 2019-05-30 18:26:01 End: 2019-05-30 18:27:56 Words: 0
Start: 2019-05-30 18:28:00 End: 2019-05-30 18:30:22 Words: 0
Start: 2019-05-30 18:42:16 End: 2019-05-30 18:43:10 Words: 0
Start: 2019-05-30 18:43:24 End: 2019-05-30 18:43:31 Words: 0
+13 -13
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-05-26 15:52:53"> <novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-05-30 18:43:31">
<project> <project>
<name>Sample Project</name> <name>Sample Project</name>
<title>Sample Project</title> <title>Sample Project</title>
@@ -10,7 +10,7 @@
<spellCheck>True</spellCheck> <spellCheck>True</spellCheck>
<lastEdited>636b6aa9b697b</lastEdited> <lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>None</lastViewed> <lastViewed>None</lastViewed>
<lastWordCount>540</lastWordCount> <lastWordCount>558</lastWordCount>
<status> <status>
<entry blue="100" green="100" red="100">New</entry> <entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Notes</entry> <entry blue="0" green="50" red="200">Notes</entry>
@@ -76,7 +76,7 @@
<charCount>656</charCount> <charCount>656</charCount>
<wordCount>121</wordCount> <wordCount>121</wordCount>
<paraCount>5</paraCount> <paraCount>5</paraCount>
<cursorPos>573</cursorPos> <cursorPos>729</cursorPos>
</item> </item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a"> <item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>New File</name> <name>New File</name>
@@ -85,8 +85,8 @@
<status>Notes</status> <status>Notes</status>
<expanded>False</expanded> <expanded>False</expanded>
<layout>SCENE</layout> <layout>SCENE</layout>
<charCount>69</charCount> <charCount>82</charCount>
<wordCount>17</wordCount> <wordCount>19</wordCount>
<paraCount>1</paraCount> <paraCount>1</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
@@ -111,10 +111,10 @@
<status>Minor</status> <status>Minor</status>
<expanded>False</expanded> <expanded>False</expanded>
<layout>NOTE</layout> <layout>NOTE</layout>
<charCount>42</charCount> <charCount>49</charCount>
<wordCount>8</wordCount> <wordCount>9</wordCount>
<paraCount>1</paraCount> <paraCount>1</paraCount>
<cursorPos>0</cursorPos> <cursorPos>24</cursorPos>
</item> </item>
<item handle="bb2c23b3c42cc" order="1" parent="f7e2d9f330615"> <item handle="bb2c23b3c42cc" order="1" parent="f7e2d9f330615">
<name>Jane Smith</name> <name>Jane Smith</name>
@@ -123,10 +123,10 @@
<status>Major</status> <status>Major</status>
<expanded>False</expanded> <expanded>False</expanded>
<layout>NOTE</layout> <layout>NOTE</layout>
<charCount>51</charCount> <charCount>55</charCount>
<wordCount>9</wordCount> <wordCount>9</wordCount>
<paraCount>1</paraCount> <paraCount>1</paraCount>
<cursorPos>0</cursorPos> <cursorPos>71</cursorPos>
</item> </item>
<item handle="15c4492bd5107" order="2" parent="None"> <item handle="15c4492bd5107" order="2" parent="None">
<name>Locations</name> <name>Locations</name>
@@ -142,9 +142,9 @@
<status>None</status> <status>None</status>
<expanded>False</expanded> <expanded>False</expanded>
<layout>NOTE</layout> <layout>NOTE</layout>
<charCount>0</charCount> <charCount>76</charCount>
<wordCount>0</wordCount> <wordCount>15</wordCount>
<paraCount>0</paraCount> <paraCount>1</paraCount>
<cursorPos>0</cursorPos> <cursorPos>0</cursorPos>
</item> </item>
<item handle="98acd8c76c93a" order="3" parent="None"> <item handle="98acd8c76c93a" order="3" parent="None">
+37
View File
@@ -11,6 +11,7 @@ from nwdummy import DummyMain
from nw.config import Config from nw.config import Config
from nw.project.project import NWProject from nw.project.project import NWProject
from nw.project.item import NWItem from nw.project.item import NWItem
from nw.project.index import NWIndex
from nw.enum import nwItemClass from nw.enum import nwItemClass
theConf = Config() theConf = Config()
@@ -60,3 +61,39 @@ def testProjectNewRoot(nwTempProj,nwRef):
assert theProject.saveProject() assert theProject.saveProject()
assert cmpFiles(projFile, refFile, [2]) assert cmpFiles(projFile, refFile, [2])
assert not theProject.projChanged assert not theProject.projChanged
@pytest.mark.project
def testIndexScanThis(nwTempProj):
projFile = path.join(nwTempProj,"nwProject.nwx")
assert theProject.openProject(projFile)
theIndex = NWIndex(theProject,theMain)
tHandle = "31489056e0916"
isValid, theBits, thePos = theIndex.scanThis("tag: this, and this")
assert not isValid
isValid, theBits, thePos = theIndex.scanThis("@:")
assert not isValid
isValid, theBits, thePos = theIndex.scanThis("@a:")
assert isValid
assert str(theBits) == "['@a']"
assert str(thePos) == "[0]"
isValid, theBits, thePos = theIndex.scanThis("@a:b")
assert isValid
assert str(theBits) == "['@a', 'b']"
assert str(thePos) == "[0, 3]"
isValid, theBits, thePos = theIndex.scanThis("@a:b,c,d")
assert isValid
assert str(theBits) == "['@a', 'b', 'c', 'd']"
assert str(thePos) == "[0, 3, 5, 7]"
isValid, theBits, thePos = theIndex.scanThis("@tag: this, and this")
assert isValid
assert str(theBits) == "['@tag', 'this', 'and this']"
assert str(thePos) == "[0, 6, 12]"
# assert False