Annotate core modules

This commit is contained in:
Veronica Berglyd Olsen
2023-08-08 22:37:48 +02:00
parent d658a993c0
commit eeadfc95e3
16 changed files with 232 additions and 246 deletions
+8 -8
View File
@@ -30,7 +30,7 @@ import logging
import unicodedata import unicodedata
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from typing import Any, Literal from typing import Any, Literal, TypeGuard
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from configparser import ConfigParser from configparser import ConfigParser
@@ -134,7 +134,7 @@ def checkPath(value: Any, default: Path) -> Path:
# Validator Functions # Validator Functions
# =============================================================================================== # # =============================================================================================== #
def isHandle(value: Any) -> bool: def isHandle(value: Any) -> TypeGuard[str]:
"""Check if a string is a valid novelWriter handle. """Check if a string is a valid novelWriter handle.
Note: This is case sensitive. Must be lower case! Note: This is case sensitive. Must be lower case!
""" """
@@ -148,7 +148,7 @@ def isHandle(value: Any) -> bool:
return True return True
def isTitleTag(value: Any) -> bool: def isTitleTag(value: Any) -> TypeGuard[str]:
"""Check if a string is a valid title tag string.""" """Check if a string is a valid title tag string."""
if not isinstance(value, str): if not isinstance(value, str):
return False return False
@@ -288,8 +288,7 @@ def transferCase(source: str, target: str) -> str:
def fuzzyTime(seconds: int) -> str: def fuzzyTime(seconds: int) -> str:
"""Converts a time difference in seconds into a fuzzy time string. """Convert a time difference in seconds into a fuzzy time string."""
"""
if seconds < 0: if seconds < 0:
return QCoreApplication.translate( return QCoreApplication.translate(
"Common", "in the future" "Common", "in the future"
@@ -349,8 +348,7 @@ def fuzzyTime(seconds: int) -> str:
def numberToRoman(value: int, toLower: bool = False) -> str: def numberToRoman(value: int, toLower: bool = False) -> str:
"""Convert an integer to a Roman number. """Convert an integer to a Roman number."""
"""
if not isinstance(value, int): if not isinstance(value, int):
return "NAN" return "NAN"
if value < 1 or value > 4999: if value < 1 or value > 4999:
@@ -423,12 +421,14 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
return "".join(buffer) return "".join(buffer)
def xmlIndent(tree: ET.Element | ET.ElementTree): def xmlIndent(tree: ET.Element | ET.ElementTree) -> None:
"""A modified version of the XML indent function in the standard """A modified version of the XML indent function in the standard
library. It behaves more closely to how the one from lxml does. library. It behaves more closely to how the one from lxml does.
""" """
if isinstance(tree, ET.ElementTree): if isinstance(tree, ET.ElementTree):
tree = tree.getroot() tree = tree.getroot()
if not isinstance(tree, ET.Element):
return
indentations = ["\n"] indentations = ["\n"]
+16 -16
View File
@@ -153,7 +153,7 @@ class BuildSettings:
The settings can be packed/unpacked to/from a dictionary for JSON. The settings can be packed/unpacked to/from a dictionary for JSON.
""" """
def __init__(self): def __init__(self) -> None:
self._name = "" self._name = ""
self._uuid = str(uuid.uuid4()) self._uuid = str(uuid.uuid4())
self._path = Path.home() self._path = Path.home()
@@ -239,12 +239,12 @@ class BuildSettings:
# Setters # Setters
## ##
def setName(self, name: str): def setName(self, name: str) -> None:
"""Set the build setting display name.""" """Set the build setting display name."""
self._name = str(name) self._name = str(name)
return return
def setBuildID(self, value: str | uuid.UUID): def setBuildID(self, value: str | uuid.UUID) -> None:
"""Set a UUID build ID.""" """Set a UUID build ID."""
value = checkUuid(value, "") value = checkUuid(value, "")
if not value: if not value:
@@ -253,7 +253,7 @@ class BuildSettings:
self._uuid = value self._uuid = value
return return
def setLastPath(self, path: Path | str | None): def setLastPath(self, path: Path | str | None) -> None:
"""Set the last used build path.""" """Set the last used build path."""
if isinstance(path, str): if isinstance(path, str):
path = Path(path) path = Path(path)
@@ -264,41 +264,41 @@ class BuildSettings:
self._changed = True self._changed = True
return return
def setLastBuildName(self, name: str): def setLastBuildName(self, name: str) -> None:
"""Set the last used build name.""" """Set the last used build name."""
self._build = str(name).strip() self._build = str(name).strip()
self._changed = True self._changed = True
return return
def setLastFormat(self, value: nwBuildFmt): def setLastFormat(self, value: nwBuildFmt) -> None:
"""Set the last used build format.""" """Set the last used build format."""
if isinstance(value, nwBuildFmt): if isinstance(value, nwBuildFmt):
self._format = value self._format = value
self._changed = True self._changed = True
return return
def setFiltered(self, tHandle: str): def setFiltered(self, tHandle: str) -> None:
"""Set an item as filtered.""" """Set an item as filtered."""
self._excluded.discard(tHandle) self._excluded.discard(tHandle)
self._included.discard(tHandle) self._included.discard(tHandle)
self._changed = True self._changed = True
return return
def setIncluded(self, tHandle: str): def setIncluded(self, tHandle: str) -> None:
"""Set an item as explicitly included.""" """Set an item as explicitly included."""
self._excluded.discard(tHandle) self._excluded.discard(tHandle)
self._included.add(tHandle) self._included.add(tHandle)
self._changed = True self._changed = True
return return
def setExcluded(self, tHandle: str): def setExcluded(self, tHandle: str) -> None:
"""Set an item as explicitly excluded.""" """Set an item as explicitly excluded."""
self._excluded.add(tHandle) self._excluded.add(tHandle)
self._included.discard(tHandle) self._included.discard(tHandle)
self._changed = True self._changed = True
return return
def setAllowRoot(self, tHandle: str, state: bool): def setAllowRoot(self, tHandle: str, state: bool) -> None:
"""Set a specific root folder as allowed or not.""" """Set a specific root folder as allowed or not."""
if state is True: if state is True:
self._skipRoot.discard(tHandle) self._skipRoot.discard(tHandle)
@@ -386,7 +386,7 @@ class BuildSettings:
return result return result
def resetChangedState(self): def resetChangedState(self) -> None:
"""Reset the changed status of the settings object. This must be """Reset the changed status of the settings object. This must be
called when the changes have been safely saved or passed on. called when the changes have been safely saved or passed on.
""" """
@@ -410,7 +410,7 @@ class BuildSettings:
} }
} }
def unpack(self, data: dict): def unpack(self, data: dict) -> None:
"""Unpack a dictionary and populate the class.""" """Unpack a dictionary and populate the class."""
settings = data.get("settings", {}) settings = data.get("settings", {})
content = data.get("content", {}) content = data.get("content", {})
@@ -454,13 +454,13 @@ class BuildCollection:
project folder. project folder.
""" """
def __init__(self, project: NWProject): def __init__(self, project: NWProject) -> None:
self._project = project self._project = project
self._builds = {} self._builds = {}
self._loadCollection() self._loadCollection()
return return
def __len__(self): def __len__(self) -> int:
"""Return the number of builds.""" """Return the number of builds."""
return len(self._builds) return len(self._builds)
@@ -476,7 +476,7 @@ class BuildCollection:
build.unpack(self._builds[buildID]) build.unpack(self._builds[buildID])
return build return build
def setBuild(self, build: BuildSettings): def setBuild(self, build: BuildSettings) -> None:
"""Set build settings data in the collection.""" """Set build settings data in the collection."""
if isinstance(build, BuildSettings): if isinstance(build, BuildSettings):
buildID = build.buildID buildID = build.buildID
@@ -484,7 +484,7 @@ class BuildCollection:
self._saveCollection() self._saveCollection()
return return
def removeBuild(self, buildID: str): def removeBuild(self, buildID: str) -> None:
"""Remove the a build from the collection.""" """Remove the a build from the collection."""
self._builds.pop(buildID, None) self._builds.pop(buildID, None)
self._saveCollection() self._saveCollection()
+7 -6
View File
@@ -80,7 +80,7 @@ class DocMerger:
and a new doc label. Calling this function resets the class. and a new doc label. Calling this function resets the class.
""" """
srcItem = self._project.tree[srcHandle] srcItem = self._project.tree[srcHandle]
if srcItem is None: if srcItem is None or srcItem.itemParent is None:
return None return None
newHandle = self._project.newFile(docLabel, srcItem.itemParent) newHandle = self._project.newFile(docLabel, srcItem.itemParent)
@@ -210,7 +210,7 @@ class DocSplitter:
"""An iterator that will write each document in the buffer, and """An iterator that will write each document in the buffer, and
return its new handle, parent handle, and sibling handle. return its new handle, parent handle, and sibling handle.
""" """
if self._srcHandle is None or self._srcItem is None: if self._srcHandle is None or self._srcItem is None or self._parHandle is None:
return return
pHandle = self._parHandle pHandle = self._parHandle
@@ -385,9 +385,10 @@ class ProjectBuilder:
aDoc = project.storage.getDocument(hChapter) aDoc = project.storage.getDocument(hChapter)
aDoc.writeDocument(f"## {lblNewChapter}\n\n") aDoc.writeDocument(f"## {lblNewChapter}\n\n")
hScene = project.newFile(lblNewScene, hChapter) if hChapter:
aDoc = project.storage.getDocument(hScene) hScene = project.newFile(lblNewScene, hChapter)
aDoc.writeDocument(f"### {lblNewScene}\n\n") aDoc = project.storage.getDocument(hScene)
aDoc.writeDocument(f"### {lblNewScene}\n\n")
project.newRoot(nwItemClass.PLOT) project.newRoot(nwItemClass.PLOT)
project.newRoot(nwItemClass.CHARACTER) project.newRoot(nwItemClass.CHARACTER)
@@ -418,7 +419,7 @@ class ProjectBuilder:
aDoc.writeDocument(f"## {chTitle}\n\n% Synopsis: {chSynop}\n\n") aDoc.writeDocument(f"## {chTitle}\n\n% Synopsis: {chSynop}\n\n")
# Create chapter scenes # Create chapter scenes
if numScenes > 0: if numScenes > 0 and cHandle:
for sc in range(numScenes): for sc in range(numScenes):
scTitle = self.tr("Scene {0}").format(f"{ch+1:d}.{sc+1:d}") scTitle = self.tr("Scene {0}").format(f"{ch+1:d}.{sc+1:d}")
sHandle = project.newFile(scTitle, cHandle) sHandle = project.newFile(scTitle, cHandle)
+3 -3
View File
@@ -54,7 +54,7 @@ class NWBuildDocument:
__slots__ = ("_project", "_build", "_queue", "_error", "_cache") __slots__ = ("_project", "_build", "_queue", "_error", "_cache")
def __init__(self, project: NWProject, build: BuildSettings): def __init__(self, project: NWProject, build: BuildSettings) -> None:
self._project = project self._project = project
self._build = build self._build = build
self._queue = [] self._queue = []
@@ -91,12 +91,12 @@ class NWBuildDocument:
# Methods # Methods
## ##
def addDocument(self, tHandle: str): def addDocument(self, tHandle: str) -> None:
"""Add a document to the build queue manually.""" """Add a document to the build queue manually."""
self._queue.append(tHandle) self._queue.append(tHandle)
return return
def queueAll(self): def queueAll(self) -> None:
"""Queue all document as defined by the build settings.""" """Queue all document as defined by the build settings."""
self._queue = [] self._queue = []
filtered = self._build.buildItemFilter(self._project) filtered = self._build.buildItemFilter(self._project)
+23 -20
View File
@@ -26,13 +26,16 @@ from __future__ import annotations
import uuid import uuid
import logging import logging
from typing import Any from typing import TYPE_CHECKING, Any
from novelwriter.common import ( from novelwriter.common import (
checkBool, checkInt, checkStringNone, checkUuid, isHandle, simplified checkBool, checkInt, checkStringNone, checkUuid, isHandle, simplified
) )
from novelwriter.core.status import NWStatus from novelwriter.core.status import NWStatus
if TYPE_CHECKING: # pragma: no cover
from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -43,7 +46,7 @@ class NWProjectData:
the list of project items. the list of project items.
""" """
def __init__(self, project): def __init__(self, project: NWProject) -> None:
self._project = project self._project = project
@@ -184,13 +187,13 @@ class NWProjectData:
# Methods # Methods
## ##
def incSaveCount(self): def incSaveCount(self) -> None:
"""Increment the save count by one.""" """Increment the save count by one."""
self._saveCount += 1 self._saveCount += 1
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def incAutoCount(self): def incAutoCount(self) -> None:
"""Increment the auto save count by one.""" """Increment the auto save count by one."""
self._autoCount += 1 self._autoCount += 1
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
@@ -208,7 +211,7 @@ class NWProjectData:
# Setters # Setters
## ##
def setUuid(self, value: Any): def setUuid(self, value: Any) -> None:
"""Set the project id.""" """Set the project id."""
value = checkUuid(value, "") value = checkUuid(value, "")
if not value: if not value:
@@ -218,74 +221,74 @@ class NWProjectData:
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setName(self, value: str | None): def setName(self, value: str | None) -> None:
"""Set a new project name.""" """Set a new project name."""
if value != self._name: if value != self._name:
self._name = simplified(str(value or "")) self._name = simplified(str(value or ""))
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setTitle(self, value: str | None): def setTitle(self, value: str | None) -> None:
"""Set a new novel title.""" """Set a new novel title."""
if value != self._title: if value != self._title:
self._title = simplified(str(value or "")) self._title = simplified(str(value or ""))
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setAuthor(self, value: str | None): def setAuthor(self, value: str | None) -> None:
"""Set the author value.""" """Set the author value."""
if value != self._title: if value != self._title:
self._author = simplified(str(value or "")) self._author = simplified(str(value or ""))
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setSaveCount(self, value: Any): def setSaveCount(self, value: Any) -> None:
"""Set the save count from last session.""" """Set the save count from last session."""
self._saveCount = checkInt(value, 0) self._saveCount = checkInt(value, 0)
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setAutoCount(self, value: Any): def setAutoCount(self, value: Any) -> None:
"""Set the auto save count from last session.""" """Set the auto save count from last session."""
self._autoCount = checkInt(value, 0) self._autoCount = checkInt(value, 0)
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setEditTime(self, value: Any): def setEditTime(self, value: Any) -> None:
"""Set the edit time from last session.""" """Set the edit time from last session."""
self._editTime = checkInt(value, 0) self._editTime = checkInt(value, 0)
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setDoBackup(self, value: Any): def setDoBackup(self, value: Any) -> None:
"""Set the do write backup flag.""" """Set the do write backup flag."""
if value != self._doBackup: if value != self._doBackup:
self._doBackup = checkBool(value, False) self._doBackup = checkBool(value, False)
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setLanguage(self, value: str | None): def setLanguage(self, value: str | None) -> None:
"""Set the project language.""" """Set the project language."""
if value != self._language: if value != self._language:
self._language = checkStringNone(value, None) self._language = checkStringNone(value, None)
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setSpellCheck(self, value: Any): def setSpellCheck(self, value: Any) -> None:
"""Set the spell check flag.""" """Set the spell check flag."""
if value != self._spellCheck: if value != self._spellCheck:
self._spellCheck = checkBool(value, False) self._spellCheck = checkBool(value, False)
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setSpellLang(self, value: str | None): def setSpellLang(self, value: str | None) -> None:
"""Set the spell check language.""" """Set the spell check language."""
if value != self._spellLang: if value != self._spellLang:
self._spellLang = checkStringNone(value, None) self._spellLang = checkStringNone(value, None)
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setLastHandle(self, value: str | None, component: str): def setLastHandle(self, value: str | None, component: str) -> None:
"""Set a last used handle into the handle registry for a given """Set a last used handle into the handle registry for a given
component. component.
""" """
@@ -294,7 +297,7 @@ class NWProjectData:
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setLastHandles(self, value: dict): def setLastHandles(self, value: dict) -> None:
"""Set the full last handles dictionary to a new set of values. """Set the full last handles dictionary to a new set of values.
This is intended to be used at project load. This is intended to be used at project load.
""" """
@@ -305,7 +308,7 @@ class NWProjectData:
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setInitCounts(self, novel: Any = None, notes: Any = None): def setInitCounts(self, novel: Any = None, notes: Any = None) -> None:
"""Set the word count totals for novel and note files.""" """Set the word count totals for novel and note files."""
if novel is not None: if novel is not None:
self._initCounts[0] = checkInt(novel, 0) self._initCounts[0] = checkInt(novel, 0)
@@ -315,7 +318,7 @@ class NWProjectData:
self._currCounts[1] = checkInt(notes, 0) self._currCounts[1] = checkInt(notes, 0)
return return
def setCurrCounts(self, novel: Any = None, notes: Any = None): def setCurrCounts(self, novel: Any = None, notes: Any = None) -> None:
"""Set the word count totals for novel and note files.""" """Set the word count totals for novel and note files."""
if novel is not None: if novel is not None:
self._currCounts[0] = checkInt(novel, 0) self._currCounts[0] = checkInt(novel, 0)
@@ -323,7 +326,7 @@ class NWProjectData:
self._currCounts[1] = checkInt(notes, 0) self._currCounts[1] = checkInt(notes, 0)
return return
def setAutoReplace(self, value: dict): def setAutoReplace(self, value: dict) -> None:
"""Set the auto-replace dictionary.""" """Set the auto-replace dictionary."""
if isinstance(value, dict): if isinstance(value, dict):
self._autoReplace = {} self._autoReplace = {}
+15 -11
View File
@@ -110,7 +110,7 @@ class ProjectXMLReader:
Rev 1: Drops the titleFormat section of settings. Rev 1: Drops the titleFormat section of settings.
""" """
def __init__(self, path): def __init__(self, path: str | Path) -> None:
self._path = Path(path) self._path = Path(path)
self._state = XMLReadState.NO_ACTION self._state = XMLReadState.NO_ACTION
self._root = "" self._root = ""
@@ -236,7 +236,7 @@ class ProjectXMLReader:
# Internal Functions # Internal Functions
## ##
def _parseProjectMeta(self, xSection: ET.Element, data: NWProjectData): def _parseProjectMeta(self, xSection: ET.Element, data: NWProjectData) -> None:
"""Parse the project section of the XML file.""" """Parse the project section of the XML file."""
logger.debug("Parsing <project> section") logger.debug("Parsing <project> section")
@@ -267,7 +267,7 @@ class ProjectXMLReader:
return return
def _parseProjectSettings(self, xSection: ET.Element, data: NWProjectData): def _parseProjectSettings(self, xSection: ET.Element, data: NWProjectData) -> None:
"""Parse the settings section of the XML file.""" """Parse the settings section of the XML file."""
logger.debug("Parsing <settings> section") logger.debug("Parsing <settings> section")
@@ -307,7 +307,9 @@ class ProjectXMLReader:
return return
def _parseProjectContent(self, xSection: ET.Element, data: NWProjectData, content: list): def _parseProjectContent(
self, xSection: ET.Element, data: NWProjectData, content: list
) -> None:
"""Parse the content section of the XML file.""" """Parse the content section of the XML file."""
logger.debug("Parsing <content> section") logger.debug("Parsing <content> section")
@@ -362,7 +364,9 @@ class ProjectXMLReader:
return return
def _parseProjectContentLegacy(self, xSection: ET.Element, data: NWProjectData, content: list): def _parseProjectContentLegacy(
self, xSection: ET.Element, data: NWProjectData, content: list
) -> None:
"""Parse the content section of the XML file for older versions.""" """Parse the content section of the XML file for older versions."""
logger.debug("Parsing <content> section (legacy format)") logger.debug("Parsing <content> section (legacy format)")
@@ -438,7 +442,7 @@ class ProjectXMLReader:
return return
def _parseStatusImport(self, xItem: ET.Element, sObject: NWStatus): def _parseStatusImport(self, xItem: ET.Element, sObject: NWStatus) -> None:
"""Parse a status or importance entry.""" """Parse a status or importance entry."""
for xEntry in xItem: for xEntry in xItem:
if xEntry.tag == "entry": if xEntry.tag == "entry":
@@ -447,7 +451,7 @@ class ProjectXMLReader:
green = checkInt(xEntry.attrib.get("green", 0), 0) green = checkInt(xEntry.attrib.get("green", 0), 0)
blue = checkInt(xEntry.attrib.get("blue", 0), 0) blue = checkInt(xEntry.attrib.get("blue", 0), 0)
count = checkInt(xEntry.attrib.get("count", 0), 0) count = checkInt(xEntry.attrib.get("count", 0), 0)
sObject.write(key, xEntry.text, (red, green, blue), count) sObject.write(key, xEntry.text or "", (red, green, blue), count)
return return
def _parseDictKeyText(self, xItem: ET.Element) -> dict: def _parseDictKeyText(self, xItem: ET.Element) -> dict:
@@ -460,7 +464,7 @@ class ProjectXMLReader:
result[xEntry.attrib["key"]] = checkString(xEntry.text, "") result[xEntry.attrib["key"]] = checkString(xEntry.text, "")
return result return result
def _parseDictTagText(self, xItem): def _parseDictTagText(self, xItem) -> dict:
"""Parse a dictionary stored with key as the tag and the value """Parse a dictionary stored with key as the tag and the value
as the text property. as the text property.
""" """
@@ -476,7 +480,7 @@ class ProjectXMLWriter:
very latest spec. very latest spec.
""" """
def __init__(self, path): def __init__(self, path: str | Path) -> None:
self._path = Path(path) self._path = Path(path)
self._error = None self._error = None
return return
@@ -585,13 +589,13 @@ class ProjectXMLWriter:
def _packSingleValue( def _packSingleValue(
self, xParent: ET.Element, name: str, value: str | None, attrib: dict | None = None self, xParent: ET.Element, name: str, value: str | None, attrib: dict | None = None
): ) -> None:
"""Pack a single value into an XML element.""" """Pack a single value into an XML element."""
xItem = ET.SubElement(xParent, name, attrib=attrib or {}) xItem = ET.SubElement(xParent, name, attrib=attrib or {})
xItem.text = str(value) or "" xItem.text = str(value) or ""
return return
def _packDictKeyValue(self, xParent: ET.Element, name: str, data: dict): def _packDictKeyValue(self, xParent: ET.Element, name: str, data: dict) -> None:
"""Pack the entries of a dictionary into an XML element.""" """Pack the entries of a dictionary into an XML element."""
xItem = ET.SubElement(xParent, name) xItem = ET.SubElement(xParent, name)
for key, value in data.items(): for key, value in data.items():
+2 -2
View File
@@ -47,7 +47,7 @@ class NWSessionLog:
format. That is, one JSON object per line. format. That is, one JSON object per line.
""" """
def __init__(self, project: NWProject): def __init__(self, project: NWProject) -> None:
self._project = project self._project = project
self._start = 0.0 self._start = 0.0
return return
@@ -65,7 +65,7 @@ class NWSessionLog:
# Methods # Methods
## ##
def startSession(self): def startSession(self) -> None:
"""Start the writing session.""" """Start the writing session."""
self._start = time() self._start = time()
return return
+5 -5
View File
@@ -45,7 +45,7 @@ class NWSpellEnchant:
between spell check tools. between spell check tools.
""" """
def __init__(self, project: NWProject): def __init__(self, project: NWProject) -> None:
self._project = project self._project = project
self._dictObj = FakeEnchant() self._dictObj = FakeEnchant()
self._userDict = UserDictionary(project) self._userDict = UserDictionary(project)
@@ -165,7 +165,7 @@ class NWSpellEnchant:
class FakeEnchant: class FakeEnchant:
"""Fallback for when Enchant is selected, but not installed.""" """Fallback for when Enchant is selected, but not installed."""
def __init__(self): def __init__(self) -> None:
class FakeProvider: class FakeProvider:
name = "" name = ""
@@ -189,7 +189,7 @@ class FakeEnchant:
class UserDictionary: class UserDictionary:
def __init__(self, project: NWProject): def __init__(self, project: NWProject) -> None:
self._project = project self._project = project
self._words = set() self._words = set()
self._path = None self._path = None
@@ -210,7 +210,7 @@ class UserDictionary:
self._words.add(word) self._words.add(word)
return True return True
def load(self): def load(self) -> None:
"""Load the user's dictionary.""" """Load the user's dictionary."""
self._path = self._project.storage.getMetaFile(nwFiles.DICT_FILE) self._path = self._project.storage.getMetaFile(nwFiles.DICT_FILE)
if not isinstance(self._path, Path): if not isinstance(self._path, Path):
@@ -224,7 +224,7 @@ class UserDictionary:
logException() logException()
return return
def save(self): def save(self) -> None:
"""Save the user's dictionary.""" """Save the user's dictionary."""
if self._path is None: if self._path is None:
self._path = self._project.storage.getMetaFile(nwFiles.DICT_FILE) self._path = self._project.storage.getMetaFile(nwFiles.DICT_FILE)
+43 -61
View File
@@ -27,6 +27,8 @@ from __future__ import annotations
import random import random
import logging import logging
from typing import ItemsView, Iterator, KeysView, Literal, TypeGuard, ValuesView
from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor
from PyQt5.QtCore import QRectF, Qt from PyQt5.QtCore import QRectF, Qt
@@ -41,9 +43,9 @@ class NWStatus:
STATUS = 1 STATUS = 1
IMPORT = 2 IMPORT = 2
def __init__(self, type): def __init__(self, kind: Literal[1, 2]) -> None:
self._type = type self._type = kind
self._store = {} self._store = {}
self._default = None self._default = None
@@ -66,7 +68,7 @@ class NWStatus:
return return
def write(self, key, name, col, count=None): def write(self, key: str | None, name: str, col: tuple, count: int | None = None) -> str:
"""Add or update a status entry. If the key is invalid, a new """Add or update a status entry. If the key is invalid, a new
key is generated. key is generated.
""" """
@@ -96,10 +98,8 @@ class NWStatus:
return key return key
def remove(self, key): def remove(self, key: str) -> bool:
"""Remove an entry in the list, but not if the count is larger """Remove an entry in the list, except if the count > 0."""
than 0.
"""
if key not in self._store: if key not in self._store:
return False return False
if self._store[key]["count"] > 0: if self._store[key]["count"] > 0:
@@ -116,59 +116,49 @@ class NWStatus:
return True return True
def check(self, value): def check(self, value: str) -> str:
"""Check the key against the stored status names. """Check the key against the stored status names."""
"""
if self._isKey(value) and value in self._store: if self._isKey(value) and value in self._store:
return value return value
elif self._default is not None: elif self._default is not None:
return self._default return self._default
else: return ""
return ""
def name(self, key): def name(self, key: str) -> str:
"""Return the name associated with a given key. """Return the name associated with a given key."""
"""
if key in self._store: if key in self._store:
return self._store[key]["name"] return self._store[key]["name"]
elif self._default is not None: elif self._default is not None:
return self._store[self._default]["name"] return self._store[self._default]["name"]
else: return ""
return ""
def cols(self, key): def cols(self, key: str) -> tuple[int, int, int]:
"""Return the colours associated with a given key. """Return the colours associated with a given key.
""" """
if key in self._store: if key in self._store:
return self._store[key]["cols"] return self._store[key]["cols"]
elif self._default is not None: elif self._default is not None:
return self._store[self._default]["cols"] return self._store[self._default]["cols"]
else: return 100, 100, 100
return (100, 100, 100)
def count(self, key): def count(self, key: str) -> int:
"""Return the count associated with a given key. """Return the count associated with a given key."""
"""
if key in self._store: if key in self._store:
return self._store[key]["count"] return self._store[key]["count"]
elif self._default is not None: elif self._default is not None:
return self._store[self._default]["count"] return self._store[self._default]["count"]
else: return 0
return 0
def icon(self, key): def icon(self, key: str) -> QIcon:
"""Return the icon associated with a given key. """Return the icon associated with a given key."""
"""
if key in self._store: if key in self._store:
return self._store[key]["icon"] return self._store[key]["icon"]
elif self._default is not None: elif self._default is not None:
return self._store[self._default]["icon"] return self._store[self._default]["icon"]
else: return self._defaultIcon
return self._defaultIcon
def reorder(self, order): def reorder(self, order: list[str]) -> bool:
"""Reorder the items according to list. """Reorder the items according to list."""
"""
if len(order) != len(self._store): if len(order) != len(self._store):
logger.error("Length mismatch between new and old order") logger.error("Length mismatch between new and old order")
return False return False
@@ -188,23 +178,20 @@ class NWStatus:
return True return True
def resetCounts(self): def resetCounts(self) -> None:
"""Clear the counts of references to the status entries. """Clear the counts of references to the status entries."""
"""
for key in self._store: for key in self._store:
self._store[key]["count"] = 0 self._store[key]["count"] = 0
return return
def increment(self, key): def increment(self, key: str) -> None:
"""Increment the counter for a given entry. """Increment the counter for a given entry."""
"""
if key in self._store: if key in self._store:
self._store[key]["count"] += 1 self._store[key]["count"] += 1
return return
def pack(self): def pack(self) -> Iterator[tuple[str, dict]]:
"""Pack the status entries into a dictionary. """Pack the status entries into a dictionary."""
"""
for key, data in self._store.items(): for key, data in self._store.items():
yield (data["name"], { yield (data["name"], {
"key": key, "key": key,
@@ -215,25 +202,22 @@ class NWStatus:
}) })
return return
def unpack(self, data): def unpack(self, data: dict) -> None:
"""Unpack a data dictionary and set the class values. """Unpack a data dictionary and set the class values."""
"""
self._store = {} self._store = {}
self._default = None self._default = None
for key, entry in data.items(): for key, entry in data.items():
label = entry.get("label", "") label = entry.get("label", "")
colour = entry.get("colour", (100, 100, 100)) colour = entry.get("colour", (100, 100, 100))
count = entry.get("count", 0) count = entry.get("count", 0)
self.write(key, label, colour, count) self.write(key, label, colour, count)
return
return True
## ##
# Internal Functions # Internal Functions
## ##
def _newKey(self): def _newKey(self) -> str:
"""Generate a new key for a status flag. This method is """Generate a new key for a status flag. This method is
recursive, but should only fail if there is an issue with the recursive, but should only fail if there is an issue with the
random number generator or the user has added a lot of status random number generator or the user has added a lot of status
@@ -245,9 +229,8 @@ class NWStatus:
key = self._newKey() key = self._newKey()
return key return key
def _isKey(self, value): def _isKey(self, value: str | None) -> TypeGuard[str]:
"""Check if a value is a key or not. """Check if a value is a key or not."""
"""
if not isinstance(value, str): if not isinstance(value, str):
return False return False
if len(value) != 7: if len(value) != 7:
@@ -259,9 +242,8 @@ class NWStatus:
return False return False
return True return True
def _createIcon(self, red, green, blue): def _createIcon(self, red: int, green: int, blue: int) -> QIcon:
"""Generate an icon for a status label. """Generate an icon for a status label."""
"""
pixmap = QPixmap(self._iPX, self._iPX) pixmap = QPixmap(self._iPX, self._iPX)
pixmap.fill(Qt.transparent) pixmap.fill(Qt.transparent)
@@ -276,22 +258,22 @@ class NWStatus:
# Iterator Bits # Iterator Bits
## ##
def __len__(self): def __len__(self) -> int:
return len(self._store) return len(self._store)
def __getitem__(self, key): def __getitem__(self, key: str) -> dict:
return self._store[key] return self._store[key]
def __iter__(self): def __iter__(self) -> Iterator[dict]:
return iter(self._store) return iter(self._store)
def keys(self): def keys(self) -> KeysView[str]:
return self._store.keys() return self._store.keys()
def items(self): def items(self) -> ItemsView[str, dict]:
return self._store.items() return self._store.items()
def values(self): def values(self) -> ValuesView[dict]:
return self._store.values() return self._store.values()
# END Class NWStatus # END Class NWStatus
+9 -9
View File
@@ -55,7 +55,7 @@ class NWStorage:
MODE_INPLACE = 1 MODE_INPLACE = 1
MODE_ARCHIVE = 2 MODE_ARCHIVE = 2
def __init__(self, project: NWProject): def __init__(self, project: NWProject) -> None:
self._project = project self._project = project
self._storagePath = None self._storagePath = None
self._runtimePath = None self._runtimePath = None
@@ -63,7 +63,7 @@ class NWStorage:
self._openMode = self.MODE_INACTIVE self._openMode = self.MODE_INACTIVE
return return
def clear(self): def clear(self) -> None:
"""Reset internal variables.""" """Reset internal variables."""
self._storagePath = None self._storagePath = None
self._runtimePath = None self._runtimePath = None
@@ -145,7 +145,7 @@ class NWStorage:
return True return True
return True return True
def closeSession(self): def closeSession(self) -> None:
"""Run tasks related to closing the session.""" """Run tasks related to closing the session."""
self.clearLockFile() self.clearLockFile()
self.clear() self.clear()
@@ -353,11 +353,11 @@ class _LegacyStorage:
file/folder layout to the current project format. file/folder layout to the current project format.
""" """
def __init__(self, project: NWProject): def __init__(self, project: NWProject) -> None:
self._project = project self._project = project
return return
def legacyDataFolder(self, path: Path, child: Path): def legacyDataFolder(self, path: Path, child: Path) -> None:
"""Handle the content of a legacy data folder from a version 1.0 """Handle the content of a legacy data folder from a version 1.0
project. project.
""" """
@@ -396,7 +396,7 @@ class _LegacyStorage:
return return
def deprecatedFiles(self, path: Path): def deprecatedFiles(self, path: Path) -> None:
"""Handle files that are no longer used by novelWriter.""" """Handle files that are no longer used by novelWriter."""
self._convertOldWordList( # Changed in 2.1 Beta 1 self._convertOldWordList( # Changed in 2.1 Beta 1
path / "meta" / "wordlist.txt", path / "meta" / "wordlist.txt",
@@ -440,7 +440,7 @@ class _LegacyStorage:
# Internal Functions # Internal Functions
## ##
def _convertOldWordList(self, wordList: Path, wordJson: Path): def _convertOldWordList(self, wordList: Path, wordJson: Path) -> None:
"""Convert the old word list plain text file to new format.""" """Convert the old word list plain text file to new format."""
if wordJson.exists() or not wordList.exists(): if wordJson.exists() or not wordList.exists():
# If the new file already exists, we won't overwrite it # If the new file already exists, we won't overwrite it
@@ -466,7 +466,7 @@ class _LegacyStorage:
return return
def _convertOldLogFile(self, sessLog: Path, sessJson: Path): def _convertOldLogFile(self, sessLog: Path, sessJson: Path) -> None:
"""Convert the old text log file format to the new JSON Lines """Convert the old text log file format to the new JSON Lines
format. format.
""" """
@@ -507,7 +507,7 @@ class _LegacyStorage:
return return
def _convertOldOptionsFile(self, optsOld: Path, optsNew: Path): def _convertOldOptionsFile(self, optsOld: Path, optsNew: Path) -> None:
"""Convert the old options state file format to the format.""" """Convert the old options state file format to the format."""
if optsNew.exists() or not optsOld.exists(): if optsNew.exists() or not optsOld.exists():
# If the new file already exists, we won't overwrite it # If the new file already exists, we won't overwrite it
+12 -12
View File
@@ -49,12 +49,12 @@ class ToHtml(Tokenizer):
M_EXPORT = 1 # Tweak output for saving to HTML or printing M_EXPORT = 1 # Tweak output for saving to HTML or printing
M_EBOOK = 2 # Tweak output for converting to epub M_EBOOK = 2 # Tweak output for converting to epub
def __init__(self, project: NWProject): def __init__(self, project: NWProject) -> None:
super().__init__(project) super().__init__(project)
self._genMode = self.M_EXPORT self._genMode = self.M_EXPORT
self._cssStyles = True self._cssStyles = True
self._fullHTML = [] self._fullHTML: list[str] = []
# Internals # Internals
self._trMap = {} self._trMap = {}
@@ -67,14 +67,14 @@ class ToHtml(Tokenizer):
## ##
@property @property
def fullHTML(self): def fullHTML(self) -> list[str]:
return self._fullHTML return self._fullHTML
## ##
# Setters # Setters
## ##
def setPreview(self, doComments: bool, doSynopsis: bool): def setPreview(self, doComments: bool, doSynopsis: bool) -> None:
"""If we're using this class to generate markdown preview, we """If we're using this class to generate markdown preview, we
need to make a few changes to formatting, which is managed by need to make a few changes to formatting, which is managed by
these flags. these flags.
@@ -85,14 +85,14 @@ class ToHtml(Tokenizer):
self._doSynopsis = doSynopsis self._doSynopsis = doSynopsis
return return
def setStyles(self, cssStyles: bool): def setStyles(self, cssStyles: bool) -> None:
"""Enable or disable CSS styling. Some elements may still have """Enable or disable CSS styling. Some elements may still have
class tags. class tags.
""" """
self._cssStyles = cssStyles self._cssStyles = cssStyles
return return
def setReplaceUnicode(self, doReplace: bool): def setReplaceUnicode(self, doReplace: bool) -> None:
"""Set the translation map to either minimal or full unicode for """Set the translation map to either minimal or full unicode for
html entities replacement. html entities replacement.
""" """
@@ -113,7 +113,7 @@ class ToHtml(Tokenizer):
"""Return the size of the full HTML result.""" """Return the size of the full HTML result."""
return sum([len(x) for x in self._fullHTML]) return sum([len(x) for x in self._fullHTML])
def doPreProcessing(self): def doPreProcessing(self) -> None:
"""Extend the auto-replace to also properly encode some unicode """Extend the auto-replace to also properly encode some unicode
characters into their respective HTML entities. characters into their respective HTML entities.
""" """
@@ -121,7 +121,7 @@ class ToHtml(Tokenizer):
self._text = self._text.translate(self._trMap) self._text = self._text.translate(self._trMap)
return return
def doConvert(self): def doConvert(self) -> None:
"""Convert the list of text tokens into a HTML document saved """Convert the list of text tokens into a HTML document saved
to _result. to _result.
""" """
@@ -299,7 +299,7 @@ class ToHtml(Tokenizer):
return return
def saveHtml5(self, path: str | Path): def saveHtml5(self, path: str | Path) -> None:
"""Save the data to an HTML file.""" """Save the data to an HTML file."""
with open(path, mode="w", encoding="utf-8") as fObj: with open(path, mode="w", encoding="utf-8") as fObj:
fObj.write(( fObj.write((
@@ -326,7 +326,7 @@ class ToHtml(Tokenizer):
logger.info("Wrote file: %s", path) logger.info("Wrote file: %s", path)
return return
def saveHtmlJson(self, path: str | Path): def saveHtmlJson(self, path: str | Path) -> None:
"""Save the data to a JSON file.""" """Save the data to a JSON file."""
timeStamp = time() timeStamp = time()
data = { data = {
@@ -347,7 +347,7 @@ class ToHtml(Tokenizer):
logger.info("Wrote file: %s", path) logger.info("Wrote file: %s", path)
return return
def replaceTabs(self, nSpaces: int = 8, spaceChar: str = "&nbsp;"): def replaceTabs(self, nSpaces: int = 8, spaceChar: str = "&nbsp;") -> None:
"""Replace tabs with spaces in the html.""" """Replace tabs with spaces in the html."""
htmlText = [] htmlText = []
tabSpace = spaceChar*nSpaces tabSpace = spaceChar*nSpaces
@@ -357,7 +357,7 @@ class ToHtml(Tokenizer):
self._fullHTML = htmlText self._fullHTML = htmlText
return return
def getStyleSheet(self) -> list: def getStyleSheet(self) -> list[str]:
"""Generate a stylesheet for the current settings.""" """Generate a stylesheet for the current settings."""
styles = [] styles = []
if not self._cssStyles: if not self._cssStyles:
+34 -34
View File
@@ -44,7 +44,7 @@ from novelwriter.core.project import NWProject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def stripEscape(text): def stripEscape(text) -> str:
"""Helper function to strip escaped Markdown characters from """Helper function to strip escaped Markdown characters from
paragraph text. paragraph text.
""" """
@@ -100,7 +100,7 @@ class Tokenizer(ABC):
A_IND_L = 0x0100 # Left indentation A_IND_L = 0x0100 # Left indentation
A_IND_R = 0x0200 # Right indentation A_IND_R = 0x0200 # Right indentation
def __init__(self, project: NWProject): def __init__(self, project: NWProject) -> None:
self._project = project self._project = project
@@ -191,116 +191,116 @@ class Tokenizer(ABC):
# Setters # Setters
## ##
def setTitleFormat(self, hFormat: str): def setTitleFormat(self, hFormat: str) -> None:
"""Set the title format pattern.""" """Set the title format pattern."""
self._fmtTitle = hFormat.strip() self._fmtTitle = hFormat.strip()
return return
def setChapterFormat(self, hFormat: str): def setChapterFormat(self, hFormat: str) -> None:
"""Set the chapert format pattern.""" """Set the chapert format pattern."""
self._fmtChapter = hFormat.strip() self._fmtChapter = hFormat.strip()
return return
def setUnNumberedFormat(self, hFormat: str): def setUnNumberedFormat(self, hFormat: str) -> None:
"""Set the unnumbered format pattern.""" """Set the unnumbered format pattern."""
self._fmtUnNum = hFormat.strip() self._fmtUnNum = hFormat.strip()
return return
def setSceneFormat(self, hFormat: str, hide: bool): def setSceneFormat(self, hFormat: str, hide: bool) -> None:
"""Set the scene format pattern and hidden status.""" """Set the scene format pattern and hidden status."""
self._fmtScene = hFormat.strip() self._fmtScene = hFormat.strip()
self._hideScene = hide self._hideScene = hide
return return
def setSectionFormat(self, hFormat: str, hide: bool): def setSectionFormat(self, hFormat: str, hide: bool) -> None:
"""Set the section format pattern and hidden status.""" """Set the section format pattern and hidden status."""
self._fmtSection = hFormat.strip() self._fmtSection = hFormat.strip()
self._hideSection = hide self._hideSection = hide
return return
def setFont(self, family: str, size: int, isFixed: bool = False): def setFont(self, family: str, size: int, isFixed: bool = False) -> None:
"""Set the build font.""" """Set the build font."""
self._textFont = family self._textFont = family
self._textSize = round(int(size)) self._textSize = round(int(size))
self._textFixed = isFixed self._textFixed = isFixed
return return
def setLineHeight(self, height: float): def setLineHeight(self, height: float) -> None:
"""Set the line height between 0.5 and 5.0.""" """Set the line height between 0.5 and 5.0."""
self._lineHeight = min(max(float(height), 0.5), 5.0) self._lineHeight = min(max(float(height), 0.5), 5.0)
return return
def setBlockIndent(self, indent: float): def setBlockIndent(self, indent: float) -> None:
"""Set the block indent between 0.0 and 10.0.""" """Set the block indent between 0.0 and 10.0."""
self._blockIndent = min(max(float(indent), 0.0), 10.0) self._blockIndent = min(max(float(indent), 0.0), 10.0)
return return
def setJustify(self, state: bool): def setJustify(self, state: bool) -> None:
"""Enable or disable text justification.""" """Enable or disable text justification."""
self._doJustify = state self._doJustify = state
return return
def setTitleMargins(self, upper: float, lower: float): def setTitleMargins(self, upper: float, lower: float) -> None:
"""Set the upper and lower title margin.""" """Set the upper and lower title margin."""
self._marginTitle = (float(upper), float(lower)) self._marginTitle = (float(upper), float(lower))
return return
def setHead1Margins(self, upper: float, lower: float): def setHead1Margins(self, upper: float, lower: float) -> None:
"""Set the upper and lower header 1 margin.""" """Set the upper and lower header 1 margin."""
self._marginHead1 = (float(upper), float(lower)) self._marginHead1 = (float(upper), float(lower))
return return
def setHead2Margins(self, upper: float, lower: float): def setHead2Margins(self, upper: float, lower: float) -> None:
"""Set the upper and lower header 2 margin.""" """Set the upper and lower header 2 margin."""
self._marginHead2 = (float(upper), float(lower)) self._marginHead2 = (float(upper), float(lower))
return return
def setHead3Margins(self, upper: float, lower: float): def setHead3Margins(self, upper: float, lower: float) -> None:
"""Set the upper and lower header 3 margin.""" """Set the upper and lower header 3 margin."""
self._marginHead3 = (float(upper), float(lower)) self._marginHead3 = (float(upper), float(lower))
return return
def setHead4Margins(self, upper: float, lower: float): def setHead4Margins(self, upper: float, lower: float) -> None:
"""Set the upper and lower header 4 margin.""" """Set the upper and lower header 4 margin."""
self._marginHead4 = (float(upper), float(lower)) self._marginHead4 = (float(upper), float(lower))
return return
def setTextMargins(self, upper: float, lower: float): def setTextMargins(self, upper: float, lower: float) -> None:
"""Set the upper and lower text margin.""" """Set the upper and lower text margin."""
self._marginText = (float(upper), float(lower)) self._marginText = (float(upper), float(lower))
return return
def setMetaMargins(self, upper: float, lower: float): def setMetaMargins(self, upper: float, lower: float) -> None:
"""Set the upper and lower meta text margin.""" """Set the upper and lower meta text margin."""
self._marginMeta = (float(upper), float(lower)) self._marginMeta = (float(upper), float(lower))
return return
def setLinkHeaders(self, state: bool): def setLinkHeaders(self, state: bool) -> None:
"""Enable or disable adding an anchor before headers.""" """Enable or disable adding an anchor before headers."""
self._linkHeaders = state self._linkHeaders = state
return return
def setBodyText(self, state: bool): def setBodyText(self, state: bool) -> None:
"""Include body text in build.""" """Include body text in build."""
self._doBodyText = state self._doBodyText = state
return return
def setSynopsis(self, state: bool): def setSynopsis(self, state: bool) -> None:
"""Include synopsis comments in build.""" """Include synopsis comments in build."""
self._doSynopsis = state self._doSynopsis = state
return return
def setComments(self, state: bool): def setComments(self, state: bool) -> None:
"""Include comments in build.""" """Include comments in build."""
self._doComments = state self._doComments = state
return return
def setKeywords(self, state: bool): def setKeywords(self, state: bool) -> None:
"""Include keywords in build.""" """Include keywords in build."""
self._doKeywords = state self._doKeywords = state
return return
def setKeepMarkdown(self, state: bool): def setKeepMarkdown(self, state: bool) -> None:
"""Keep original markdown during build.""" """Keep original markdown during build."""
self._keepMarkdown = state self._keepMarkdown = state
return return
@@ -310,7 +310,7 @@ class Tokenizer(ABC):
## ##
@abstractmethod @abstractmethod
def doConvert(self): def doConvert(self) -> None:
raise NotImplementedError raise NotImplementedError
def addRootHeading(self, tHandle: str) -> bool: def addRootHeading(self, tHandle: str) -> bool:
@@ -365,7 +365,7 @@ class Tokenizer(ABC):
return True return True
def doPreProcessing(self): def doPreProcessing(self) -> None:
"""Run trough the various replace dictionaries.""" """Run trough the various replace dictionaries."""
# Process the user's auto-replace dictionary # Process the user's auto-replace dictionary
autoReplace = self._project.data.autoReplace autoReplace = self._project.data.autoReplace
@@ -382,7 +382,7 @@ class Tokenizer(ABC):
return return
def tokenizeText(self): def tokenizeText(self) -> None:
"""Scan the text for either lines starting with specific """Scan the text for either lines starting with specific
characters that indicate headers, comments, commands etc, or characters that indicate headers, comments, commands etc, or
just contain plain text. In the case of plain text, apply the just contain plain text. In the case of plain text, apply the
@@ -742,14 +742,14 @@ class Tokenizer(ABC):
return True return True
def saveRawMarkdown(self, path: str | Path): def saveRawMarkdown(self, path: str | Path) -> None:
"""Save the raw text to a plain text file.""" """Save the raw text to a plain text file."""
with open(path, mode="w", encoding="utf-8") as outFile: with open(path, mode="w", encoding="utf-8") as outFile:
for nwdPage in self._allMarkdown: for nwdPage in self._allMarkdown:
outFile.write(nwdPage) outFile.write(nwdPage)
return return
def saveRawMarkdownJSON(self, path: str | Path): def saveRawMarkdownJSON(self, path: str | Path) -> None:
"""Save the raw text to a JSON file.""" """Save the raw text to a JSON file."""
timeStamp = time() timeStamp = time()
data = { data = {
@@ -773,30 +773,30 @@ class Tokenizer(ABC):
class HeadingFormatter: class HeadingFormatter:
def __init__(self, project: NWProject): def __init__(self, project: NWProject) -> None:
self._project = project self._project = project
self._chCount = 0 self._chCount = 0
self._scChCount = 0 self._scChCount = 0
self._scAbsCount = 0 self._scAbsCount = 0
return return
def incChapter(self): def incChapter(self) -> None:
"""Increment the chapter counter.""" """Increment the chapter counter."""
self._chCount += 1 self._chCount += 1
return return
def incScene(self): def incScene(self) -> None:
"""Increment the scene counters.""" """Increment the scene counters."""
self._scChCount += 1 self._scChCount += 1
self._scAbsCount += 1 self._scAbsCount += 1
return return
def resetScene(self): def resetScene(self) -> None:
"""Reset the chapter scene counter.""" """Reset the chapter scene counter."""
self._scChCount = 0 self._scChCount = 0
return return
def apply(self, hFormat: str, text: str): def apply(self, hFormat: str, text: str) -> str:
"""Apply formatting to a specific heading.""" """Apply formatting to a specific heading."""
hFormat = hFormat.replace(nwHeadFmt.TITLE, text) hFormat = hFormat.replace(nwHeadFmt.TITLE, text)
hFormat = hFormat.replace(nwHeadFmt.CH_NUM, str(self._chCount)) hFormat = hFormat.replace(nwHeadFmt.CH_NUM, str(self._chCount))
+8 -10
View File
@@ -45,12 +45,10 @@ class ToMarkdown(Tokenizer):
M_STD = 0 # Standard Markdown M_STD = 0 # Standard Markdown
M_GH = 1 # GitHub Markdown M_GH = 1 # GitHub Markdown
def __init__(self, project: NWProject): def __init__(self, project: NWProject) -> None:
super().__init__(project) super().__init__(project)
self._genMode = self.M_STD self._genMode = self.M_STD
self._fullMD = [] self._fullMD: list[str] = []
return return
## ##
@@ -58,7 +56,7 @@ class ToMarkdown(Tokenizer):
## ##
@property @property
def fullMD(self) -> list: def fullMD(self) -> list[str]:
"""Return the markdown as a list.""" """Return the markdown as a list."""
return self._fullMD return self._fullMD
@@ -66,11 +64,11 @@ class ToMarkdown(Tokenizer):
# Setters # Setters
## ##
def setStandardMarkdown(self): def setStandardMarkdown(self) -> None:
self._genMode = self.M_STD self._genMode = self.M_STD
return return
def setGitHubMarkdown(self): def setGitHubMarkdown(self) -> None:
self._genMode = self.M_GH self._genMode = self.M_GH
return return
@@ -82,7 +80,7 @@ class ToMarkdown(Tokenizer):
"""Return the size of the full Markdown result.""" """Return the size of the full Markdown result."""
return sum([len(x) for x in self._fullMD]) return sum([len(x) for x in self._fullMD])
def doConvert(self): def doConvert(self) -> None:
"""Convert the list of text tokens into a HTML document saved """Convert the list of text tokens into a HTML document saved
to theResult. to theResult.
""" """
@@ -175,14 +173,14 @@ class ToMarkdown(Tokenizer):
return return
def saveMarkdown(self, path: str | Path): def saveMarkdown(self, path: str | Path) -> None:
"""Save the data to a plain text file.""" """Save the data to a plain text file."""
with open(path, mode="w", encoding="utf-8") as outFile: with open(path, mode="w", encoding="utf-8") as outFile:
outFile.write("".join(self._fullMD)) outFile.write("".join(self._fullMD))
logger.info("Wrote file: %s", path) logger.info("Wrote file: %s", path)
return return
def replaceTabs(self, nSpaces: int = 8, spaceChar: str = " "): def replaceTabs(self, nSpaces: int = 8, spaceChar: str = " ") -> None:
"""Replace tabs with spaces.""" """Replace tabs with spaces."""
spaces = spaceChar*nSpaces spaces = spaceChar*nSpaces
self._fullMD = [p.replace("\t", spaces) for p in self._fullMD] self._fullMD = [p.replace("\t", spaces) for p in self._fullMD]
+44 -46
View File
@@ -98,7 +98,7 @@ class ToOdt(Tokenizer):
Test with: https://odfvalidator.org/ Test with: https://odfvalidator.org/
""" """
def __init__(self, project: NWProject, isFlat: bool): def __init__(self, project: NWProject, isFlat: bool) -> None:
super().__init__(project) super().__init__(project)
self._isFlat = isFlat # Flat: .fodt, otherwise .odt self._isFlat = isFlat # Flat: .fodt, otherwise .odt
@@ -188,7 +188,7 @@ class ToOdt(Tokenizer):
# Setters # Setters
## ##
def setLanguage(self, language: str): def setLanguage(self, language: str) -> None:
"""Set language for the document.""" """Set language for the document."""
if language: if language:
langBits = language.split("_") langBits = language.split("_")
@@ -197,7 +197,7 @@ class ToOdt(Tokenizer):
self._dCountry = langBits[1] self._dCountry = langBits[1]
return return
def setColourHeaders(self, state: bool): def setColourHeaders(self, state: bool) -> None:
"""Enable/disable coloured headings and comments.""" """Enable/disable coloured headings and comments."""
self._colourHead = state self._colourHead = state
return return
@@ -205,7 +205,7 @@ class ToOdt(Tokenizer):
def setPageLayout( def setPageLayout(
self, width: int | float, height: int | float, self, width: int | float, height: int | float,
top: int | float, bottom: int | float, left: int | float, right: int | float top: int | float, bottom: int | float, left: int | float, right: int | float
): ) -> None:
"""Set the document page size and margins in millimetres.""" """Set the document page size and margins in millimetres."""
self._mDocWidth = f"{width/10.0:.3f}cm" self._mDocWidth = f"{width/10.0:.3f}cm"
self._mDocHeight = f"{height/10.0:.3f}cm" self._mDocHeight = f"{height/10.0:.3f}cm"
@@ -219,7 +219,7 @@ class ToOdt(Tokenizer):
# Class Methods # Class Methods
## ##
def initDocument(self): def initDocument(self) -> None:
"""Initialises a new open document XML tree.""" """Initialises a new open document XML tree."""
# Initialise Variables # Initialise Variables
# ==================== # ====================
@@ -381,7 +381,7 @@ class ToOdt(Tokenizer):
return return
def doConvert(self): def doConvert(self) -> None:
"""Convert the list of text tokens into XML elements.""" """Convert the list of text tokens into XML elements."""
self._result = "" # Not used, but cleared just in case self._result = "" # Not used, but cleared just in case
@@ -599,7 +599,7 @@ class ToOdt(Tokenizer):
def _addTextPar( def _addTextPar(
self, styleName: str, oStyle: ODTParagraphStyle, tText: str, tFmt: str = "", self, styleName: str, oStyle: ODTParagraphStyle, tText: str, tFmt: str = "",
isHead: bool = False, oLevel: str | None = None isHead: bool = False, oLevel: str | None = None
): ) -> None:
"""Add a text paragraph to the text XML element.""" """Add a text paragraph to the text XML element."""
tAttr = {} tAttr = {}
tAttr[_mkTag("text", "style-name")] = self._paraStyle(styleName, oStyle) tAttr[_mkTag("text", "style-name")] = self._paraStyle(styleName, oStyle)
@@ -726,7 +726,7 @@ class ToOdt(Tokenizer):
# Style Elements # Style Elements
## ##
def _pageStyles(self): def _pageStyles(self) -> None:
"""Set the default page style.""" """Set the default page style."""
tAttr = {} tAttr = {}
tAttr[_mkTag("style", "name")] = "PM1" tAttr[_mkTag("style", "name")] = "PM1"
@@ -756,7 +756,7 @@ class ToOdt(Tokenizer):
return return
def _defaultStyles(self): def _defaultStyles(self) -> None:
"""Set the default styles.""" """Set the default styles."""
# Add Paragraph Family Style # Add Paragraph Family Style
# ========================== # ==========================
@@ -829,7 +829,7 @@ class ToOdt(Tokenizer):
return return
def _useableStyles(self): def _useableStyles(self) -> None:
"""Set the usable styles.""" """Set the usable styles."""
# Add Text Body Style # Add Text Body Style
# =================== # ===================
@@ -1002,7 +1002,7 @@ class ToOdt(Tokenizer):
return return
def _writeHeader(self): def _writeHeader(self) -> None:
"""Write the header elements.""" """Write the header elements."""
tAttr = {} tAttr = {}
tAttr[_mkTag("style", "name")] = "Standard" tAttr[_mkTag("style", "name")] = "Standard"
@@ -1048,7 +1048,7 @@ class ODTParagraphStyle:
VALID_CLASS = ["text", "chapter"] VALID_CLASS = ["text", "chapter"]
VALID_WEIGHT = ["normal", "inherit", "bold"] VALID_WEIGHT = ["normal", "inherit", "bold"]
def __init__(self): def __init__(self) -> None:
# Attributes # Attributes
self._mAttr = { self._mAttr = {
@@ -1087,26 +1087,26 @@ class ODTParagraphStyle:
# Attribute Setters # Attribute Setters
## ##
def setDisplayName(self, value: str | None): def setDisplayName(self, value: str | None) -> None:
self._mAttr["display-name"][1] = value self._mAttr["display-name"][1] = value
return return
def setParentStyleName(self, value: str | None): def setParentStyleName(self, value: str | None) -> None:
self._mAttr["parent-style-name"][1] = value self._mAttr["parent-style-name"][1] = value
return return
def setNextStyleName(self, value: str | None): def setNextStyleName(self, value: str | None) -> None:
self._mAttr["next-style-name"][1] = value self._mAttr["next-style-name"][1] = value
return return
def setOutlineLevel(self, value: str | None): def setOutlineLevel(self, value: str | None) -> None:
if value in self.VALID_LEVEL: if value in self.VALID_LEVEL:
self._mAttr["default-outline-level"][1] = value self._mAttr["default-outline-level"][1] = value
else: else:
self._mAttr["default-outline-level"][1] = None self._mAttr["default-outline-level"][1] = None
return return
def setClass(self, value: str | None): def setClass(self, value: str | None) -> None:
if value in self.VALID_CLASS: if value in self.VALID_CLASS:
self._mAttr["class"][1] = value self._mAttr["class"][1] = value
else: else:
@@ -1117,41 +1117,41 @@ class ODTParagraphStyle:
# Paragraph Setters # Paragraph Setters
## ##
def setMarginTop(self, value: str | None): def setMarginTop(self, value: str | None) -> None:
self._pAttr["margin-top"][1] = value self._pAttr["margin-top"][1] = value
return return
def setMarginBottom(self, value: str | None): def setMarginBottom(self, value: str | None) -> None:
self._pAttr["margin-bottom"][1] = value self._pAttr["margin-bottom"][1] = value
return return
def setMarginLeft(self, value: str | None): def setMarginLeft(self, value: str | None) -> None:
self._pAttr["margin-left"][1] = value self._pAttr["margin-left"][1] = value
return return
def setMarginRight(self, value: str | None): def setMarginRight(self, value: str | None) -> None:
self._pAttr["margin-right"][1] = value self._pAttr["margin-right"][1] = value
return return
def setLineHeight(self, value: str | None): def setLineHeight(self, value: str | None) -> None:
self._pAttr["line-height"][1] = value self._pAttr["line-height"][1] = value
return return
def setTextAlign(self, value: str | None): def setTextAlign(self, value: str | None) -> None:
if value in self.VALID_ALIGN: if value in self.VALID_ALIGN:
self._pAttr["text-align"][1] = value self._pAttr["text-align"][1] = value
else: else:
self._pAttr["text-align"][1] = None self._pAttr["text-align"][1] = None
return return
def setBreakBefore(self, value: str | None): def setBreakBefore(self, value: str | None) -> None:
if value in self.VALID_BREAK: if value in self.VALID_BREAK:
self._pAttr["break-before"][1] = value self._pAttr["break-before"][1] = value
else: else:
self._pAttr["break-before"][1] = None self._pAttr["break-before"][1] = None
return return
def setBreakAfter(self, value: str | None): def setBreakAfter(self, value: str | None) -> None:
if value in self.VALID_BREAK: if value in self.VALID_BREAK:
self._pAttr["break-after"][1] = value self._pAttr["break-after"][1] = value
else: else:
@@ -1162,30 +1162,30 @@ class ODTParagraphStyle:
# Text Setters # Text Setters
## ##
def setFontName(self, value: str | None): def setFontName(self, value: str | None) -> None:
self._tAttr["font-name"][1] = value self._tAttr["font-name"][1] = value
return return
def setFontFamily(self, value: str | None): def setFontFamily(self, value: str | None) -> None:
self._tAttr["font-family"][1] = value self._tAttr["font-family"][1] = value
return return
def setFontSize(self, value: str | None): def setFontSize(self, value: str | None) -> None:
self._tAttr["font-size"][1] = value self._tAttr["font-size"][1] = value
return return
def setFontWeight(self, value: str | None): def setFontWeight(self, value: str | None) -> None:
if value in self.VALID_WEIGHT: if value in self.VALID_WEIGHT:
self._tAttr["font-weight"][1] = value self._tAttr["font-weight"][1] = value
else: else:
self._tAttr["font-weight"][1] = None self._tAttr["font-weight"][1] = None
return return
def setColor(self, value: str | None): def setColor(self, value: str | None) -> None:
self._tAttr["color"][1] = value self._tAttr["color"][1] = value
return return
def setOpacity(self, value: str | None): def setOpacity(self, value: str | None) -> None:
self._tAttr["opacity"][1] = value self._tAttr["opacity"][1] = value
return return
@@ -1193,7 +1193,7 @@ class ODTParagraphStyle:
# Methods # Methods
## ##
def checkNew(self, refStyle: ODTParagraphStyle): def checkNew(self, refStyle: ODTParagraphStyle) -> bool:
"""Check if there are new settings in refStyle that differ from """Check if there are new settings in refStyle that differ from
those in the current object. those in the current object.
""" """
@@ -1217,7 +1217,7 @@ class ODTParagraphStyle:
) )
return sha256(theString.encode()).hexdigest() return sha256(theString.encode()).hexdigest()
def packXML(self, xParent: ET.Element, name: str): def packXML(self, xParent: ET.Element, name: str) -> None:
"""Pack the content into an xml element.""" """Pack the content into an xml element."""
theAttr = {} theAttr = {}
theAttr[_mkTag("style", "name")] = name theAttr[_mkTag("style", "name")] = name
@@ -1259,8 +1259,7 @@ class ODTTextStyle:
VALID_LSTYLE = ["none", "solid"] VALID_LSTYLE = ["none", "solid"]
VALID_LTYPE = ["none", "single", "double"] VALID_LTYPE = ["none", "single", "double"]
def __init__(self): def __init__(self) -> None:
# Text Attributes # Text Attributes
self._tAttr = { self._tAttr = {
"font-weight": ["fo", None], "font-weight": ["fo", None],
@@ -1268,35 +1267,34 @@ class ODTTextStyle:
"text-line-through-style": ["style", None], "text-line-through-style": ["style", None],
"text-line-through-type": ["style", None], "text-line-through-type": ["style", None],
} }
return return
## ##
# Setters # Setters
## ##
def setFontWeight(self, value: str | None): def setFontWeight(self, value: str | None) -> None:
if value in self.VALID_WEIGHT: if value in self.VALID_WEIGHT:
self._tAttr["font-weight"][1] = value self._tAttr["font-weight"][1] = value
else: else:
self._tAttr["font-weight"][1] = None self._tAttr["font-weight"][1] = None
return return
def setFontStyle(self, value: str | None): def setFontStyle(self, value: str | None) -> None:
if value in self.VALID_STYLE: if value in self.VALID_STYLE:
self._tAttr["font-style"][1] = value self._tAttr["font-style"][1] = value
else: else:
self._tAttr["font-style"][1] = None self._tAttr["font-style"][1] = None
return return
def setStrikeStyle(self, value: str | None): def setStrikeStyle(self, value: str | None) -> None:
if value in self.VALID_LSTYLE: if value in self.VALID_LSTYLE:
self._tAttr["text-line-through-style"][1] = value self._tAttr["text-line-through-style"][1] = value
else: else:
self._tAttr["text-line-through-style"][1] = None self._tAttr["text-line-through-style"][1] = None
return return
def setStrikeType(self, value: str | None): def setStrikeType(self, value: str | None) -> None:
if value in self.VALID_LTYPE: if value in self.VALID_LTYPE:
self._tAttr["text-line-through-type"][1] = value self._tAttr["text-line-through-type"][1] = value
else: else:
@@ -1307,7 +1305,7 @@ class ODTTextStyle:
# Methods # Methods
## ##
def packXML(self, xParent: ET.Element, name: str): def packXML(self, xParent: ET.Element, name: str) -> None:
"""Pack the content into an xml element.""" """Pack the content into an xml element."""
theAttr = {} theAttr = {}
theAttr[_mkTag("style", "name")] = name theAttr[_mkTag("style", "name")] = name
@@ -1357,7 +1355,7 @@ class XMLParagraph:
object and attribute is written to, object and attribute is written to,
""" """
def __init__(self, xRoot: ET.Element): def __init__(self, xRoot: ET.Element) -> None:
self._xRoot = xRoot self._xRoot = xRoot
self._xTail = ET.Element("") self._xTail = ET.Element("")
@@ -1370,7 +1368,7 @@ class XMLParagraph:
return return
def appendText(self, tText: str): def appendText(self, tText: str) -> None:
"""Append text to the XML element. We do this one character at """Append text to the XML element. We do this one character at
the time in order to be able to process line breaks, tabs and the time in order to be able to process line breaks, tabs and
spaces separately. Multiple spaces are concatenated into a spaces separately. Multiple spaces are concatenated into a
@@ -1435,7 +1433,7 @@ class XMLParagraph:
return return
def appendSpan(self, tText: str, tFmt: str): def appendSpan(self, tText: str, tFmt: str) -> None:
"""Append a text span to the XML element. The span is always """Append a text span to the XML element. The span is always
closed since we do not allow nested spans (like Libre Office). closed since we do not allow nested spans (like Libre Office).
Therefore we return to the root element level when we're done Therefore we return to the root element level when we're done
@@ -1449,7 +1447,7 @@ class XMLParagraph:
self._nState = X_ROOT_TAIL self._nState = X_ROOT_TAIL
return return
def checkError(self): def checkError(self) -> tuple[int, str]:
"""Check that the number of characters written matches the """Check that the number of characters written matches the
number of characters received. number of characters received.
""" """
@@ -1463,7 +1461,7 @@ class XMLParagraph:
# Internal Functions # Internal Functions
## ##
def _processSpaces(self, nSpaces: int): def _processSpaces(self, nSpaces: int) -> None:
"""Add spaces to paragraph. The first space is always written """Add spaces to paragraph. The first space is always written
as-is (unless it's the first character of the paragraph). The as-is (unless it's the first character of the paragraph). The
second space uses the dedicated tag for spaces, and from the second space uses the dedicated tag for spaces, and from the
+2 -2
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.1-beta1" hexVersion="0x020100b1" fileVersion="1.5" fileRevision="1" timeStamp="2023-08-08 10:33:12"> <novelWriterXML appVersion="2.1-beta1" hexVersion="0x020100b1" fileVersion="1.5" fileRevision="1" timeStamp="2023-08-08 22:19:23">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1516" autoCount="237" editTime="75236"> <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1517" autoCount="237" editTime="75241">
<name>Sample Project</name> <name>Sample Project</name>
<title>Sample Project</title> <title>Sample Project</title>
<author>Jane Smith</author> <author>Jane Smith</author>
+1 -1
View File
@@ -344,7 +344,7 @@ def testCoreStatus_PackUnpack(mockRnd):
# Unpack # Unpack
theStatus = NWStatus(NWStatus.STATUS) theStatus = NWStatus(NWStatus.STATUS)
assert theStatus.unpack({ theStatus.unpack({
statusKeys[0]: {"label": "New0", "colour": (100, 100, 100), "count": countTo[0]}, statusKeys[0]: {"label": "New0", "colour": (100, 100, 100), "count": countTo[0]},
statusKeys[1]: {"label": "New1", "colour": (150, 150, 150), "count": countTo[1]}, statusKeys[1]: {"label": "New1", "colour": (150, 150, 150), "count": countTo[1]},
statusKeys[2]: {"label": "New2", "colour": (200, 200, 200), "count": countTo[2]}, statusKeys[2]: {"label": "New2", "colour": (200, 200, 200), "count": countTo[2]},