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"
TOC_TXT = "ToC.txt"
TOC_JSON = "ToC.json"
SESS_INFO = "sessionInfo.log"
SESS_STATS = "sessionStats.log"
INDEX_FILE = "tagsIndex.json"
OPTS_FILE = "guiOptions.json"
RECENT_FILE = "recentProjects.json"
+2
View File
@@ -53,6 +53,8 @@ class OptionState():
"widthCol2",
"sortCol",
"sortOrder",
"incNovel",
"incNotes",
"hideZeros",
"hideNegative",
"groupByDay",
+28 -10
View File
@@ -93,6 +93,8 @@ class NWProject():
self.lastViewed = None # The handle of the last file to be viewed
self.lastWCount = 0 # The project word count from last 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
# Set Defaults
@@ -231,6 +233,8 @@ class NWProject():
self.lastViewed = None
self.lastWCount = 0
self.currWCount = 0
self.novelWCount = 0
self.notesWCount = 0
return
@@ -435,6 +439,10 @@ class NWProject():
self.lastViewed = checkString(xItem.text, None, True)
elif xItem.tag == "lastWordCount":
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":
self.statusItems.unpackEntries(xItem)
elif xItem.tag == "importance":
@@ -501,6 +509,10 @@ class NWProject():
})
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
xProject = etree.SubElement(nwXML, "project")
@@ -519,6 +531,8 @@ class NWProject():
self._packProjectValue(xSettings, "lastEdited", self.lastEdited)
self._packProjectValue(xSettings, "lastViewed", self.lastViewed)
self._packProjectValue(xSettings, "lastWordCount", self.currWCount)
self._packProjectValue(xSettings, "novelWordCount", wcNovel)
self._packProjectValue(xSettings, "notesWordCount", wcNotes)
xAutoRep = etree.SubElement(xSettings, "autoReplace")
for aKey, aValue in self.autoReplace.items():
@@ -1101,18 +1115,22 @@ class NWProject():
if not self.ensureFolderStructure():
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:
print((
"Start: {opened:s} "
"End: {closed:s} "
"Words: {words:8d}"
).format(
opened = formatTimeStamp(self.projOpened),
closed = formatTimeStamp(time()),
words = self.getSessionWordCount(),
), file=outFile)
if not isFile:
# It's a new file, so add a header
outFile.write("# Initial: %d\n# %-17s %-19s %8s %8s\n" % (
self.lastWCount, "Start Time", "End Time", "Novel", "Notes"
))
outFile.write("%-19s %-19s %8d %8d\n" % (
formatTimeStamp(self.projOpened),
formatTimeStamp(time()),
self.novelWCount,
self.notesWCount,
))
return True
+18 -1
View File
@@ -36,7 +36,7 @@ from time import time
from nw.core.item import NWItem
from nw.common import checkString
from nw.constants import nwFiles, nwItemType, nwItemClass
from nw.constants import nwFiles, nwItemType, nwItemClass, nwItemLayout
logger = logging.getLogger(__name__)
@@ -182,6 +182,23 @@ class NWTree():
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
##
+134 -47
View File
@@ -64,7 +64,7 @@ class GuiSessionLog(QDialog):
self.logData = []
self.timeFilter = 0.0
self.timeTotal = 0.0
self.maxWords = 0
self.wordOffset = 0
self.setWindowTitle("Session Log")
self.setMinimumWidth(self.mainConf.pxInt(420))
@@ -114,6 +114,7 @@ class GuiSessionLog(QDialog):
# Word Bar
self.barHeight = int(round(0.5*self.theTheme.fontPixelSize))
self.barWidth = self.mainConf.pxInt(200)
self.barImage = QPixmap(self.barHeight, self.barHeight)
self.barImage.fill(self.palette().highlight().color())
@@ -130,11 +131,29 @@ class GuiSessionLog(QDialog):
self.labelFilter.setFont(self.monoFont)
self.labelFilter.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.setRowStretch(2, 1)
self.novelWords = QLabel("0")
self.novelWords.setFont(self.monoFont)
self.novelWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
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
sPx = self.theTheme.baseIconSize
@@ -143,6 +162,20 @@ class GuiSessionLog(QDialog):
self.filterForm = QGridLayout(self)
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.hideZeros = QSwitch(width=2*sPx, height=sPx)
self.hideZeros.setChecked(
@@ -164,12 +197,16 @@ class GuiSessionLog(QDialog):
)
self.groupByDay.clicked.connect(self._updateListBox)
self.filterForm.addWidget(self.labelZeros, 0, 0)
self.filterForm.addWidget(self.hideZeros, 0, 1)
self.filterForm.addWidget(self.labelNegative, 1, 0)
self.filterForm.addWidget(self.hideNegative, 1, 1)
self.filterForm.addWidget(self.labelByDay, 2, 0)
self.filterForm.addWidget(self.groupByDay, 2, 1)
self.filterForm.addWidget(self.labelNovel, 0, 0)
self.filterForm.addWidget(self.incNovel, 0, 1)
self.filterForm.addWidget(self.labelNotes, 1, 0)
self.filterForm.addWidget(self.incNotes, 1, 1)
self.filterForm.addWidget(self.labelZeros, 2, 0)
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
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
@@ -209,6 +246,8 @@ class GuiSessionLog(QDialog):
widthCol2 = self.mainConf.rpxInt(self.listBox.columnWidth(2))
sortCol = self.listBox.sortColumn()
sortOrder = self.listBox.header().sortIndicatorOrder()
incNovel = self.incNovel.isChecked()
incNotes = self.incNotes.isChecked()
hideZeros = self.hideZeros.isChecked()
hideNegative = self.hideNegative.isChecked()
groupByDay = self.groupByDay.isChecked()
@@ -220,6 +259,8 @@ class GuiSessionLog(QDialog):
self.optState.setValue("GuiSessionLog", "widthCol2", widthCol2)
self.optState.setValue("GuiSessionLog", "sortCol", sortCol)
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", "hideNegative", hideNegative)
self.optState.setValue("GuiSessionLog", "groupByDay", groupByDay)
@@ -239,29 +280,55 @@ class GuiSessionLog(QDialog):
self.logData = []
logger.debug("Loading session log file")
ttNovel = 0
ttNotes = 0
isFirst = True
osNovel = 0
osNotes = 0
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:
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()
if len(inData) != 8:
if len(inData) != 6:
continue
dStart = datetime.strptime(
"%s %s" % (inData[1], inData[2]), nwConst.tStampFmt
"%s %s" % (inData[0], inData[1]), nwConst.tStampFmt
)
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
sDiff = tDiff.total_seconds()
self.timeTotal += sDiff
self.logData.append((dStart, sDiff, nWords))
self.maxWords = max(self.maxWords, nWords)
wcNovel = int(inData[4])
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:
self.theParent.makeAlert(
@@ -270,6 +337,9 @@ class GuiSessionLog(QDialog):
return False
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
@@ -279,49 +349,65 @@ class GuiSessionLog(QDialog):
self.listBox.clear()
self.timeFilter = 0.0
incNovel = self.incNovel.isChecked()
incNotes = self.incNotes.isChecked()
hideZeros = self.hideZeros.isChecked()
hideNegative = self.hideNegative.isChecked()
groupByDay = self.groupByDay.isChecked()
# Group the data
if groupByDay:
listData = []
listMax = 0
tempData = []
sessDate = None
sessTime = 0
lstNovel = 0
lstNotes = 0
dayDate = None
daySDiff = 0
dayWords = 0
for n, (dStart, sDiff, nWords) in enumerate(self.logData):
for n, (dStart, sDiff, wcNovel, wcNotes) in enumerate(self.logData):
if n == 0:
dayDate = dStart.date()
if dayDate != dStart.date():
listData.append((dayDate, daySDiff, dayWords))
listMax = max(listMax, dayWords)
dayDate = dStart.date()
daySDiff = sDiff
dayWords = nWords
sessDate = dStart.date()
if sessDate != dStart.date():
tempData.append((sessDate, sessTime, lstNovel, lstNotes))
sessDate = dStart.date()
sessTime = sDiff
lstNovel = wcNovel
lstNotes = wcNotes
else:
daySDiff += sDiff
dayWords += nWords
sessTime += sDiff
lstNovel = wcNovel
lstNotes = wcNotes
if dayDate is not None:
listData.append((dayDate, daySDiff, dayWords))
if sessDate is not None:
tempData.append((sessDate, sessTime, lstNovel, lstNotes))
else:
listData = self.logData
listMax = self.maxWords
tempData = self.logData
# 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
for dStart, sDiff, nWords in listData:
if hideZeros and nWords == 0:
continue
if hideNegative and nWords < 0:
continue
self.timeFilter += sDiff
if groupByDay:
sStart = dStart.strftime(nwConst.dStampFmt)
else:
@@ -332,7 +418,7 @@ class GuiSessionLog(QDialog):
newItem.setText(self.C_LENGTH, self._formatTime(sDiff))
newItem.setText(self.C_COUNT, str(nWords))
if nWords > 0:
if nWords > 0 and listMax > 0:
theBar = self.barImage.scaled(
int(200*nWords/listMax),
self.barHeight,
@@ -350,6 +436,7 @@ class GuiSessionLog(QDialog):
newItem.setFont(self.C_COUNT, self.monoFont)
self.listBox.addTopLevelItem(newItem)
self.timeFilter += sDiff
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.
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'?>
<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>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>407</saveCount>
<autoCount>71</autoCount>
<editTime>15118</editTime>
<saveCount>565</saveCount>
<autoCount>99</autoCount>
<editTime>23298</editTime>
</project>
<settings>
<doBackup>False</doBackup>
<spellCheck>True</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>bb2c23b3c42cc</lastViewed>
<lastWordCount>967</lastWordCount>
<lastViewed>b3e74dbc1f584</lastViewed>
<lastWordCount>982</lastWordCount>
<novelWordCount>606</novelWordCount>
<notesWordCount>376</notesWordCount>
<autoReplace>
<A>B</A>
<B>E</B>
@@ -114,10 +116,10 @@
<status>1st Draft</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>1483</charCount>
<wordCount>263</wordCount>
<charCount>1564</charCount>
<wordCount>278</wordCount>
<paraCount>8</paraCount>
<cursorPos>1086</cursorPos>
<cursorPos>1633</cursorPos>
</item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>Another Scene</name>