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