Brand new session stats log with saved counts for novel and notes seperately, and new parsing code

This commit is contained in:
Veronica K. B. Olsen
2020-06-25 21:11:37 +02:00
parent 9e6feb6b05
commit 09be23a11e
7 changed files with 195 additions and 69 deletions
+1 -1
View File
@@ -51,7 +51,7 @@ class nwFiles():
PROJ_LOCK = "nwProject.lock" PROJ_LOCK = "nwProject.lock"
TOC_TXT = "ToC.txt" TOC_TXT = "ToC.txt"
TOC_JSON = "ToC.json" TOC_JSON = "ToC.json"
SESS_INFO = "sessionInfo.log" SESS_STATS = "sessionStats.log"
INDEX_FILE = "tagsIndex.json" INDEX_FILE = "tagsIndex.json"
OPTS_FILE = "guiOptions.json" OPTS_FILE = "guiOptions.json"
RECENT_FILE = "recentProjects.json" RECENT_FILE = "recentProjects.json"
+2
View File
@@ -53,6 +53,8 @@ class OptionState():
"widthCol2", "widthCol2",
"sortCol", "sortCol",
"sortOrder", "sortOrder",
"incNovel",
"incNotes",
"hideZeros", "hideZeros",
"hideNegative", "hideNegative",
"groupByDay", "groupByDay",
+28 -10
View File
@@ -93,6 +93,8 @@ class NWProject():
self.lastViewed = None # The handle of the last file to be viewed self.lastViewed = None # The handle of the last file to be viewed
self.lastWCount = 0 # The project word count from last session self.lastWCount = 0 # The project word count from last session
self.currWCount = 0 # The project word count in current session self.currWCount = 0 # The project word count in current session
self.novelWCount = 0 # Total number of words in novel files
self.notesWCount = 0 # Total number of words in note files
self.doBackup = True # Run project backup on exit self.doBackup = True # Run project backup on exit
# Set Defaults # Set Defaults
@@ -231,6 +233,8 @@ class NWProject():
self.lastViewed = None self.lastViewed = None
self.lastWCount = 0 self.lastWCount = 0
self.currWCount = 0 self.currWCount = 0
self.novelWCount = 0
self.notesWCount = 0
return return
@@ -435,6 +439,10 @@ class NWProject():
self.lastViewed = checkString(xItem.text, None, True) self.lastViewed = checkString(xItem.text, None, True)
elif xItem.tag == "lastWordCount": elif xItem.tag == "lastWordCount":
self.lastWCount = checkInt(xItem.text, 0, False) self.lastWCount = checkInt(xItem.text, 0, False)
elif xItem.tag == "novelWordCount":
self.novelWCount = checkInt(xItem.text, 0, False)
elif xItem.tag == "notesWordCount":
self.notesWCount = checkInt(xItem.text, 0, False)
elif xItem.tag == "status": elif xItem.tag == "status":
self.statusItems.unpackEntries(xItem) self.statusItems.unpackEntries(xItem)
elif xItem.tag == "importance": elif xItem.tag == "importance":
@@ -501,6 +509,10 @@ class NWProject():
}) })
editTime = int(self.editTime + saveTime - self.projOpened) editTime = int(self.editTime + saveTime - self.projOpened)
wcNovel, wcNotes = self.projTree.sumWords()
self.novelWCount = wcNovel
self.notesWCount = wcNotes
self.setProjectWordCount(wcNovel + wcNotes)
# Save Project Meta # Save Project Meta
xProject = etree.SubElement(nwXML, "project") xProject = etree.SubElement(nwXML, "project")
@@ -519,6 +531,8 @@ class NWProject():
self._packProjectValue(xSettings, "lastEdited", self.lastEdited) self._packProjectValue(xSettings, "lastEdited", self.lastEdited)
self._packProjectValue(xSettings, "lastViewed", self.lastViewed) self._packProjectValue(xSettings, "lastViewed", self.lastViewed)
self._packProjectValue(xSettings, "lastWordCount", self.currWCount) self._packProjectValue(xSettings, "lastWordCount", self.currWCount)
self._packProjectValue(xSettings, "novelWordCount", wcNovel)
self._packProjectValue(xSettings, "notesWordCount", wcNotes)
xAutoRep = etree.SubElement(xSettings, "autoReplace") xAutoRep = etree.SubElement(xSettings, "autoReplace")
for aKey, aValue in self.autoReplace.items(): for aKey, aValue in self.autoReplace.items():
@@ -1101,18 +1115,22 @@ class NWProject():
if not self.ensureFolderStructure(): if not self.ensureFolderStructure():
return False return False
sessionFile = path.join(self.projMeta, nwFiles.SESS_INFO) sessionFile = path.join(self.projMeta, nwFiles.SESS_STATS)
isFile = path.isfile(sessionFile)
with open(sessionFile, mode="a+", encoding="utf8") as outFile: with open(sessionFile, mode="a+", encoding="utf8") as outFile:
print(( if not isFile:
"Start: {opened:s} " # It's a new file, so add a header
"End: {closed:s} " outFile.write("# Initial: %d\n# %-17s %-19s %8s %8s\n" % (
"Words: {words:8d}" self.lastWCount, "Start Time", "End Time", "Novel", "Notes"
).format( ))
opened = formatTimeStamp(self.projOpened),
closed = formatTimeStamp(time()), outFile.write("%-19s %-19s %8d %8d\n" % (
words = self.getSessionWordCount(), formatTimeStamp(self.projOpened),
), file=outFile) formatTimeStamp(time()),
self.novelWCount,
self.notesWCount,
))
return True return True
+18 -1
View File
@@ -36,7 +36,7 @@ from time import time
from nw.core.item import NWItem from nw.core.item import NWItem
from nw.common import checkString from nw.common import checkString
from nw.constants import nwFiles, nwItemType, nwItemClass from nw.constants import nwFiles, nwItemType, nwItemClass, nwItemLayout
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -182,6 +182,23 @@ class NWTree():
return return
def sumWords(self):
"""Loops over all entries and adds up the word counts.
"""
noteWords = 0
novelWords = 0
for tHandle in self._treeOrder:
tItem = self.__getitem__(tHandle)
if tItem is None:
continue
if tItem.itemLayout == nwItemLayout.NO_LAYOUT:
pass
elif tItem.itemLayout == nwItemLayout.NOTE:
noteWords += tItem.wordCount
else:
novelWords += tItem.wordCount
return novelWords, noteWords
## ##
# Tree Structure Methods # Tree Structure Methods
## ##
+134 -47
View File
@@ -64,7 +64,7 @@ class GuiSessionLog(QDialog):
self.logData = [] self.logData = []
self.timeFilter = 0.0 self.timeFilter = 0.0
self.timeTotal = 0.0 self.timeTotal = 0.0
self.maxWords = 0 self.wordOffset = 0
self.setWindowTitle("Session Log") self.setWindowTitle("Session Log")
self.setMinimumWidth(self.mainConf.pxInt(420)) self.setMinimumWidth(self.mainConf.pxInt(420))
@@ -114,6 +114,7 @@ class GuiSessionLog(QDialog):
# Word Bar # Word Bar
self.barHeight = int(round(0.5*self.theTheme.fontPixelSize)) self.barHeight = int(round(0.5*self.theTheme.fontPixelSize))
self.barWidth = self.mainConf.pxInt(200)
self.barImage = QPixmap(self.barHeight, self.barHeight) self.barImage = QPixmap(self.barHeight, self.barHeight)
self.barImage.fill(self.palette().highlight().color()) self.barImage.fill(self.palette().highlight().color())
@@ -130,11 +131,29 @@ class GuiSessionLog(QDialog):
self.labelFilter.setFont(self.monoFont) self.labelFilter.setFont(self.monoFont)
self.labelFilter.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.labelFilter.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.infoForm.addWidget(QLabel("Total Time:"), 0, 0) self.novelWords = QLabel("0")
self.infoForm.addWidget(self.labelTotal, 0, 1) self.novelWords.setFont(self.monoFont)
self.infoForm.addWidget(QLabel("Filtered Time:"), 1, 0) self.novelWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.infoForm.addWidget(self.labelFilter, 1, 1)
self.infoForm.setRowStretch(2, 1) self.notesWords = QLabel("0")
self.notesWords.setFont(self.monoFont)
self.notesWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.totalWords = QLabel("0")
self.totalWords.setFont(self.monoFont)
self.totalWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.infoForm.addWidget(QLabel("Total Time:"), 0, 0)
self.infoForm.addWidget(self.labelTotal, 0, 1)
self.infoForm.addWidget(QLabel("Filtered Time:"), 1, 0)
self.infoForm.addWidget(self.labelFilter, 1, 1)
self.infoForm.addWidget(QLabel("Novel Word Count:"), 2, 0)
self.infoForm.addWidget(self.novelWords, 2, 1)
self.infoForm.addWidget(QLabel("Notes Word Count:"), 3, 0)
self.infoForm.addWidget(self.notesWords, 3, 1)
self.infoForm.addWidget(QLabel("Total Word Count:"), 4, 0)
self.infoForm.addWidget(self.totalWords, 4, 1)
self.infoForm.setRowStretch(5, 1)
# Filter Options # Filter Options
sPx = self.theTheme.baseIconSize sPx = self.theTheme.baseIconSize
@@ -143,6 +162,20 @@ class GuiSessionLog(QDialog):
self.filterForm = QGridLayout(self) self.filterForm = QGridLayout(self)
self.filterBox.setLayout(self.filterForm) self.filterBox.setLayout(self.filterForm)
self.labelNovel = QLabel("Count novel files")
self.incNovel = QSwitch(width=2*sPx, height=sPx)
self.incNovel.setChecked(
self.optState.getBool("GuiSessionLog", "incNovel", True)
)
self.incNovel.clicked.connect(self._updateListBox)
self.labelNotes = QLabel("Count note files")
self.incNotes = QSwitch(width=2*sPx, height=sPx)
self.incNotes.setChecked(
self.optState.getBool("GuiSessionLog", "incNotes", True)
)
self.incNotes.clicked.connect(self._updateListBox)
self.labelZeros = QLabel("Hide zero word count") self.labelZeros = QLabel("Hide zero word count")
self.hideZeros = QSwitch(width=2*sPx, height=sPx) self.hideZeros = QSwitch(width=2*sPx, height=sPx)
self.hideZeros.setChecked( self.hideZeros.setChecked(
@@ -164,12 +197,16 @@ class GuiSessionLog(QDialog):
) )
self.groupByDay.clicked.connect(self._updateListBox) self.groupByDay.clicked.connect(self._updateListBox)
self.filterForm.addWidget(self.labelZeros, 0, 0) self.filterForm.addWidget(self.labelNovel, 0, 0)
self.filterForm.addWidget(self.hideZeros, 0, 1) self.filterForm.addWidget(self.incNovel, 0, 1)
self.filterForm.addWidget(self.labelNegative, 1, 0) self.filterForm.addWidget(self.labelNotes, 1, 0)
self.filterForm.addWidget(self.hideNegative, 1, 1) self.filterForm.addWidget(self.incNotes, 1, 1)
self.filterForm.addWidget(self.labelByDay, 2, 0) self.filterForm.addWidget(self.labelZeros, 2, 0)
self.filterForm.addWidget(self.groupByDay, 2, 1) self.filterForm.addWidget(self.hideZeros, 2, 1)
self.filterForm.addWidget(self.labelNegative, 3, 0)
self.filterForm.addWidget(self.hideNegative, 3, 1)
self.filterForm.addWidget(self.labelByDay, 4, 0)
self.filterForm.addWidget(self.groupByDay, 4, 1)
# Buttons # Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
@@ -209,6 +246,8 @@ class GuiSessionLog(QDialog):
widthCol2 = self.mainConf.rpxInt(self.listBox.columnWidth(2)) widthCol2 = self.mainConf.rpxInt(self.listBox.columnWidth(2))
sortCol = self.listBox.sortColumn() sortCol = self.listBox.sortColumn()
sortOrder = self.listBox.header().sortIndicatorOrder() sortOrder = self.listBox.header().sortIndicatorOrder()
incNovel = self.incNovel.isChecked()
incNotes = self.incNotes.isChecked()
hideZeros = self.hideZeros.isChecked() hideZeros = self.hideZeros.isChecked()
hideNegative = self.hideNegative.isChecked() hideNegative = self.hideNegative.isChecked()
groupByDay = self.groupByDay.isChecked() groupByDay = self.groupByDay.isChecked()
@@ -220,6 +259,8 @@ class GuiSessionLog(QDialog):
self.optState.setValue("GuiSessionLog", "widthCol2", widthCol2) self.optState.setValue("GuiSessionLog", "widthCol2", widthCol2)
self.optState.setValue("GuiSessionLog", "sortCol", sortCol) self.optState.setValue("GuiSessionLog", "sortCol", sortCol)
self.optState.setValue("GuiSessionLog", "sortOrder", sortOrder) self.optState.setValue("GuiSessionLog", "sortOrder", sortOrder)
self.optState.setValue("GuiSessionLog", "incNovel", incNovel)
self.optState.setValue("GuiSessionLog", "incNotes", incNotes)
self.optState.setValue("GuiSessionLog", "hideZeros", hideZeros) self.optState.setValue("GuiSessionLog", "hideZeros", hideZeros)
self.optState.setValue("GuiSessionLog", "hideNegative", hideNegative) self.optState.setValue("GuiSessionLog", "hideNegative", hideNegative)
self.optState.setValue("GuiSessionLog", "groupByDay", groupByDay) self.optState.setValue("GuiSessionLog", "groupByDay", groupByDay)
@@ -239,29 +280,55 @@ class GuiSessionLog(QDialog):
self.logData = [] self.logData = []
logger.debug("Loading session log file") logger.debug("Loading session log file")
ttNovel = 0
ttNotes = 0
isFirst = True
osNovel = 0
osNotes = 0
try: try:
logFile = path.join(self.theProject.projMeta, nwFiles.SESS_INFO) logFile = path.join(self.theProject.projMeta, nwFiles.SESS_STATS)
with open(logFile, mode="r", encoding="utf8") as inFile: with open(logFile, mode="r", encoding="utf8") as inFile:
for inLine in inFile: for inLine in inFile:
if inLine.startswith("#"):
if inLine.startswith("# Initial:"):
self.wordOffset = int(inLine[11:].strip())
logger.verbose(
"Initial word count when log was started is %d" % self.wordOffset
)
continue
inData = inLine.split() inData = inLine.split()
if len(inData) != 8: if len(inData) != 6:
continue continue
dStart = datetime.strptime( dStart = datetime.strptime(
"%s %s" % (inData[1], inData[2]), nwConst.tStampFmt "%s %s" % (inData[0], inData[1]), nwConst.tStampFmt
) )
dEnd = datetime.strptime( dEnd = datetime.strptime(
"%s %s" % (inData[4], inData[5]), nwConst.tStampFmt "%s %s" % (inData[2], inData[3]), nwConst.tStampFmt
) )
nWords = int(inData[7])
tDiff = dEnd - dStart tDiff = dEnd - dStart
sDiff = tDiff.total_seconds() sDiff = tDiff.total_seconds()
self.timeTotal += sDiff self.timeTotal += sDiff
self.logData.append((dStart, sDiff, nWords)) wcNovel = int(inData[4])
self.maxWords = max(self.maxWords, nWords) wcNotes = int(inData[5])
ttNovel = wcNovel
ttNotes = wcNotes
if isFirst:
isFirst = False
if self.wordOffset > 0:
# First entry is used as the reference word
# count, and the data is then discarded.
osNovel = wcNovel
osNotes = wcNotes
continue
self.logData.append((dStart, sDiff, wcNovel-osNovel, wcNotes-osNotes))
except Exception as e: except Exception as e:
self.theParent.makeAlert( self.theParent.makeAlert(
@@ -270,6 +337,9 @@ class GuiSessionLog(QDialog):
return False return False
self.labelTotal.setText(self._formatTime(self.timeTotal)) self.labelTotal.setText(self._formatTime(self.timeTotal))
self.novelWords.setText("{:n}".format(ttNovel))
self.notesWords.setText("{:n}".format(ttNotes))
self.totalWords.setText("{:n}".format(ttNovel + ttNotes))
return True return True
@@ -279,49 +349,65 @@ class GuiSessionLog(QDialog):
self.listBox.clear() self.listBox.clear()
self.timeFilter = 0.0 self.timeFilter = 0.0
incNovel = self.incNovel.isChecked()
incNotes = self.incNotes.isChecked()
hideZeros = self.hideZeros.isChecked() hideZeros = self.hideZeros.isChecked()
hideNegative = self.hideNegative.isChecked() hideNegative = self.hideNegative.isChecked()
groupByDay = self.groupByDay.isChecked() groupByDay = self.groupByDay.isChecked()
# Group the data # Group the data
if groupByDay: if groupByDay:
listData = [] tempData = []
listMax = 0 sessDate = None
sessTime = 0
lstNovel = 0
lstNotes = 0
dayDate = None for n, (dStart, sDiff, wcNovel, wcNotes) in enumerate(self.logData):
daySDiff = 0
dayWords = 0
for n, (dStart, sDiff, nWords) in enumerate(self.logData):
if n == 0: if n == 0:
dayDate = dStart.date() sessDate = dStart.date()
if dayDate != dStart.date(): if sessDate != dStart.date():
listData.append((dayDate, daySDiff, dayWords)) tempData.append((sessDate, sessTime, lstNovel, lstNotes))
listMax = max(listMax, dayWords) sessDate = dStart.date()
dayDate = dStart.date() sessTime = sDiff
daySDiff = sDiff lstNovel = wcNovel
dayWords = nWords lstNotes = wcNotes
else: else:
daySDiff += sDiff sessTime += sDiff
dayWords += nWords lstNovel = wcNovel
lstNotes = wcNotes
if dayDate is not None: if sessDate is not None:
listData.append((dayDate, daySDiff, dayWords)) tempData.append((sessDate, sessTime, lstNovel, lstNotes))
else: else:
listData = self.logData tempData = self.logData
listMax = self.maxWords
# Calculate Word Diff
listData = []
pcTotal = 0
listMax = 0
for dStart, sDiff, wcNovel, wcNotes in tempData:
wcTotal = 0
if incNovel:
wcTotal += wcNovel
if incNotes:
wcTotal += wcNotes
dwTotal = wcTotal - pcTotal
if hideZeros and dwTotal == 0:
continue
if hideNegative and dwTotal < 0:
continue
listData.append((dStart, sDiff, dwTotal))
listMax = max(listMax, dwTotal)
pcTotal = wcTotal
# Populate the list # Populate the list
for dStart, sDiff, nWords in listData: for dStart, sDiff, nWords in listData:
if hideZeros and nWords == 0:
continue
if hideNegative and nWords < 0:
continue
self.timeFilter += sDiff
if groupByDay: if groupByDay:
sStart = dStart.strftime(nwConst.dStampFmt) sStart = dStart.strftime(nwConst.dStampFmt)
else: else:
@@ -332,7 +418,7 @@ class GuiSessionLog(QDialog):
newItem.setText(self.C_LENGTH, self._formatTime(sDiff)) newItem.setText(self.C_LENGTH, self._formatTime(sDiff))
newItem.setText(self.C_COUNT, str(nWords)) newItem.setText(self.C_COUNT, str(nWords))
if nWords > 0: if nWords > 0 and listMax > 0:
theBar = self.barImage.scaled( theBar = self.barImage.scaled(
int(200*nWords/listMax), int(200*nWords/listMax),
self.barHeight, self.barHeight,
@@ -350,6 +436,7 @@ class GuiSessionLog(QDialog):
newItem.setFont(self.C_COUNT, self.monoFont) newItem.setFont(self.C_COUNT, self.monoFont)
self.listBox.addTopLevelItem(newItem) self.listBox.addTopLevelItem(newItem)
self.timeFilter += sDiff
self.labelFilter.setText(self._formatTime(self.timeFilter)) self.labelFilter.setText(self._formatTime(self.timeFilter))
+1 -1
View File
@@ -21,4 +21,4 @@ Thin spaces and thin non-breaking spaces are also supported from the Insert menu
If you need to split a scene file up into further pieces, you can do so with the level four heading, like above. This is referred to as a section. If you need to split a scene file up into further pieces, you can do so with the level four heading, like above. This is referred to as a section.
Both scene and section titles can be left out of the final exported document. The formatting of titles can be selected from the export dialog. Both scene and section titles can be left out of the final exported document. The formatting of titles can be selected from the Build Novel Project dialog. You can also have them replaced with scene separators like “* * *”.
+11 -9
View File
@@ -1,21 +1,23 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.9.0rc1" hexVersion="0x000900c1" fileVersion="1.1" timeStamp="2020-06-19 19:50:54"> <novelWriterXML appVersion="0.10.0-rc1" hexVersion="0x001000c1" fileVersion="1.1" timeStamp="2020-06-25 21:09:22">
<project> <project>
<name>Sample Project</name> <name>Sample Project</name>
<title>Sample Project</title> <title>Sample Project</title>
<author>Jane Smith</author> <author>Jane Smith</author>
<author>Jay Doh</author> <author>Jay Doh</author>
<saveCount>407</saveCount> <saveCount>565</saveCount>
<autoCount>71</autoCount> <autoCount>99</autoCount>
<editTime>15118</editTime> <editTime>23298</editTime>
</project> </project>
<settings> <settings>
<doBackup>False</doBackup> <doBackup>False</doBackup>
<spellCheck>True</spellCheck> <spellCheck>True</spellCheck>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
<lastEdited>636b6aa9b697b</lastEdited> <lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>bb2c23b3c42cc</lastViewed> <lastViewed>b3e74dbc1f584</lastViewed>
<lastWordCount>967</lastWordCount> <lastWordCount>982</lastWordCount>
<novelWordCount>606</novelWordCount>
<notesWordCount>376</notesWordCount>
<autoReplace> <autoReplace>
<A>B</A> <A>B</A>
<B>E</B> <B>E</B>
@@ -114,10 +116,10 @@
<status>1st Draft</status> <status>1st Draft</status>
<exported>True</exported> <exported>True</exported>
<layout>SCENE</layout> <layout>SCENE</layout>
<charCount>1483</charCount> <charCount>1564</charCount>
<wordCount>263</wordCount> <wordCount>278</wordCount>
<paraCount>8</paraCount> <paraCount>8</paraCount>
<cursorPos>1086</cursorPos> <cursorPos>1633</cursorPos>
</item> </item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a"> <item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>Another Scene</name> <name>Another Scene</name>