Assemble most of the Manuscript Build tool
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
novelWriter – Init File
|
||||
=======================
|
||||
Application initialisation
|
||||
|
||||
File History:
|
||||
Created: 2018-09-22 [0.0.1]
|
||||
|
||||
@@ -19,6 +19,7 @@ licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
|
||||
add = typ_plus.svg
|
||||
backward = typ_chevron-left.svg
|
||||
bookmark = typ_bookmark.svg
|
||||
browse = typ_folder-open.svg
|
||||
build_excluded = typ_cancel.svg
|
||||
build_filtered = typ_filter.svg
|
||||
build_included = typ_pin.svg
|
||||
@@ -40,6 +41,7 @@ cls_world = typ_location.svg
|
||||
cross = typ_times.svg
|
||||
down = typ_chevron-down.svg
|
||||
edit = typ_pencil.svg
|
||||
export = typ_export-button.svg
|
||||
forward = typ_chevron-right.svg
|
||||
maximise = typ_arrow-maximise.svg
|
||||
menu = typ_th-menu.svg
|
||||
@@ -57,6 +59,7 @@ proj_title = mixed_document-title.svg
|
||||
reference = typ_at.svg
|
||||
refresh = typ_refresh.svg
|
||||
remove = typ_minus.svg
|
||||
revert = typ_refresh-flipped.svg
|
||||
search = typ_search.svg
|
||||
search_cancel = typ_cancel-grey.svg
|
||||
search_case = nw_search-case.svg
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m6.4444 16.444v0.55556c1.8789-2.8644 4-4.3922 6.6667-4.4444v3.3333c0 0.61222 0.56778 1.1111 1.27 1.1111 0.40444 0 0.75-0.17556 0.98111-0.43444 2.1478-2.2544 6.6378-6.7878 6.6378-6.7878s-4.49-4.5356-6.6389-6.8189c-0.23111-0.22778-0.57556-0.40333-0.98-0.40333-0.70222 0-1.27 0.49667-1.27 1.1111v3.3333c-5.1778 0-6.6667 5.4122-6.6667 9.4444zm-3.3333 5h15.556c0.61444 0 1.1111-0.49778 1.1111-1.1111v-6.7178c-0.73778 0.75111-1.5156 1.5478-2.2222 2.2744v3.3322h-13.333v-13.333h7.7778v-2.2222h-8.8889c-0.61444 0-1.1111 0.49778-1.1111 1.1111v15.556c0 0.61333 0.49667 1.1111 1.1111 1.1111z" fill="#aeaeae" stroke-width="1.1111"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 775 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m21.316 8.1937h-2.2836c-0.38061-1.1418-1.4273-1.903-2.6643-1.903h-5.7091c0-1.0466-0.85636-1.903-1.903-1.903h-3.9012c-1.6175 0-2.8545 1.237-2.8545 2.8545v9.5154c0 1.6175 1.237 2.8545 2.8545 2.8545h11.418c1.6175 0 3.2352-1.237 3.6157-2.8545l2.0934-7.6121c0.09515-0.57091-0.1903-0.95154-0.66605-0.95154zm-17.413 0.95154v-1.903c0-0.57091 0.38061-0.95154 0.95154-0.95154h3.8061c0 1.0466 0.85636 1.903 1.903 1.903h5.7091c0.57091 0 0.95154 0.38061 0.95154 0.95154h-10.562c-0.57091 0-1.0466 0.38061-1.237 0.95154l-1.5225 5.9945zm14.177 7.1363c-0.1903 0.76121-1.0466 1.4273-1.8079 1.4273h-11.418s-0.38061-0.1903-0.1903-0.76121l1.8079-6.6605c0-0.09515 0.1903-0.1903 0.28545-0.1903h13.035z" fill="#aeaeae" stroke-width=".95154"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 873 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m10.837 12.256h-6.8373v-6.8347c0.010667-1.5131 0.89467-1.877 1.964-0.80652l1.5387 1.5437c1.3533-1.0598 3.0093-1.6397 4.7547-1.6397 2.0667 0 4.012 0.80518 5.472 2.2676 1.4653 1.4557 2.2707 3.4034 2.2707 5.4696 0 2.0703-0.80533 4.0153-2.268 5.475-1.4627 1.4624-3.4067 2.2689-5.4747 2.2689-2.0667 0-4.012-0.80652-5.4747-2.2702-0.39467-0.39593-0.744-0.82784-1.04-1.2864-0.46267-0.7212-0.25333-1.6797 0.46667-2.1396 0.71867-0.46125 1.6773-0.25195 2.1387 0.46658 0.17733 0.27595 0.38933 0.53323 0.624 0.77319 0.87867 0.87717 2.0453 1.3597 3.2853 1.3597s2.4067-0.48124 3.2853-1.3597c0.876-0.87717 1.36-2.0436 1.36-3.2861 0-1.2398-0.48267-2.4062-1.36-3.2807-0.87867-0.87717-2.0453-1.3611-3.2853-1.3611-0.91733 0-1.7947 0.26795-2.5453 0.76252l1.9307 1.9343c1.0707 1.0691 0.70667 1.9436-0.80533 1.9436z" fill="#9c9" stroke-width="1.3332"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 984 B |
@@ -19,6 +19,7 @@ licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
|
||||
add = typ_plus.svg
|
||||
backward = typ_chevron-left.svg
|
||||
bookmark = typ_bookmark.svg
|
||||
browse = typ_folder-open.svg
|
||||
build_excluded = typ_cancel.svg
|
||||
build_filtered = typ_filter.svg
|
||||
build_included = typ_pin.svg
|
||||
@@ -40,6 +41,7 @@ cls_world = typ_location.svg
|
||||
cross = typ_times.svg
|
||||
down = typ_chevron-down.svg
|
||||
edit = typ_pencil.svg
|
||||
export = typ_export-button.svg
|
||||
forward = typ_chevron-right.svg
|
||||
maximise = typ_arrow-maximise.svg
|
||||
menu = typ_th-menu.svg
|
||||
@@ -57,6 +59,7 @@ proj_title = mixed_document-title.svg
|
||||
reference = typ_at.svg
|
||||
refresh = typ_refresh.svg
|
||||
remove = typ_minus.svg
|
||||
revert = typ_refresh-flipped.svg
|
||||
search = typ_search.svg
|
||||
search_cancel = typ_cancel-grey.svg
|
||||
search_case = nw_search-case.svg
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m6.4444 16.444v0.55556c1.8789-2.8644 4-4.3922 6.6667-4.4444v3.3333c0 0.61222 0.56778 1.1111 1.27 1.1111 0.40444 0 0.75-0.17556 0.98111-0.43444 2.1478-2.2544 6.6378-6.7878 6.6378-6.7878s-4.49-4.5356-6.6389-6.8189c-0.23111-0.22778-0.57556-0.40333-0.98-0.40333-0.70222 0-1.27 0.49667-1.27 1.1111v3.3333c-5.1778 0-6.6667 5.4122-6.6667 9.4444zm-3.3333 5h15.556c0.61444 0 1.1111-0.49778 1.1111-1.1111v-6.7178c-0.73778 0.75111-1.5156 1.5478-2.2222 2.2744v3.3322h-13.333v-13.333h7.7778v-2.2222h-8.8889c-0.61444 0-1.1111 0.49778-1.1111 1.1111v15.556c0 0.61333 0.49667 1.1111 1.1111 1.1111z" fill-opacity=".72157" stroke-width="1.1111"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 782 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m21.316 8.1937h-2.2836c-0.38061-1.1418-1.4273-1.903-2.6643-1.903h-5.7091c0-1.0466-0.85636-1.903-1.903-1.903h-3.9012c-1.6175 0-2.8545 1.237-2.8545 2.8545v9.5154c0 1.6175 1.237 2.8545 2.8545 2.8545h11.418c1.6175 0 3.2352-1.237 3.6157-2.8545l2.0934-7.6121c0.09515-0.57091-0.1903-0.95154-0.66605-0.95154zm-17.413 0.95154v-1.903c0-0.57091 0.38061-0.95154 0.95154-0.95154h3.8061c0 1.0466 0.85636 1.903 1.903 1.903h5.7091c0.57091 0 0.95154 0.38061 0.95154 0.95154h-10.562c-0.57091 0-1.0466 0.38061-1.237 0.95154l-1.5225 5.9945zm14.177 7.1363c-0.1903 0.76121-1.0466 1.4273-1.8079 1.4273h-11.418s-0.38061-0.1903-0.1903-0.76121l1.8079-6.6605c0-0.09515 0.1903-0.1903 0.28545-0.1903h13.035z" fill-opacity=".72157" stroke-width="1.0467"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 880 B |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m10.837 12.256h-6.8373v-6.8347c0.010667-1.5131 0.89467-1.877 1.964-0.80652l1.5387 1.5437c1.3533-1.0598 3.0093-1.6397 4.7547-1.6397 2.0667 0 4.012 0.80518 5.472 2.2676 1.4653 1.4557 2.2707 3.4034 2.2707 5.4696 0 2.0703-0.80533 4.0153-2.268 5.475-1.4627 1.4624-3.4067 2.2689-5.4747 2.2689-2.0667 0-4.012-0.80652-5.4747-2.2702-0.39467-0.39593-0.744-0.82784-1.04-1.2864-0.46267-0.7212-0.25333-1.6797 0.46667-2.1396 0.71867-0.46125 1.6773-0.25195 2.1387 0.46658 0.17733 0.27595 0.38933 0.53323 0.624 0.77319 0.87867 0.87717 2.0453 1.3597 3.2853 1.3597s2.4067-0.48124 3.2853-1.3597c0.876-0.87717 1.36-2.0436 1.36-3.2861 0-1.2398-0.48267-2.4062-1.36-3.2807-0.87867-0.87717-2.0453-1.3611-3.2853-1.3611-0.91733 0-1.7947 0.26795-2.5453 0.76252l1.9307 1.9343c1.0707 1.0691 0.70667 1.9436-0.80533 1.9436z" fill="#718c00" stroke-width="1.3332"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 987 B |
@@ -27,6 +27,7 @@ import json
|
||||
import uuid
|
||||
import hashlib
|
||||
import logging
|
||||
import unicodedata
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from typing import Any, Literal
|
||||
@@ -469,8 +470,7 @@ def xmlIndent(tree: ET.Element | ET.ElementTree):
|
||||
# =============================================================================================== #
|
||||
|
||||
def readTextFile(path: str | Path) -> str:
|
||||
"""Read the content of a text file in a robust manner.
|
||||
"""
|
||||
"""Read the content of a text file in a robust manner."""
|
||||
path = Path(path)
|
||||
if not path.is_file():
|
||||
return ""
|
||||
@@ -482,14 +482,13 @@ def readTextFile(path: str | Path) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def makeFileNameSafe(value: str) -> str:
|
||||
"""Returns a filename safe string of the value.
|
||||
def makeFileNameSafe(text: str) -> str:
|
||||
"""Return a filename safe string.
|
||||
See: https://unicode.org/reports/tr15/#Norm_Forms
|
||||
"""
|
||||
clean = ""
|
||||
for c in str(value).strip():
|
||||
if c.isalpha() or c.isdigit() or c == " ":
|
||||
clean += c
|
||||
return clean
|
||||
text = unicodedata.normalize("NFKC", text).strip()
|
||||
allowed = (" ", ".", "-", "_")
|
||||
return "".join(c for c in text if c.isalnum() or c in allowed)
|
||||
|
||||
|
||||
def sha256sum(path: str | Path) -> str | None:
|
||||
|
||||
@@ -199,6 +199,16 @@ class nwLabels:
|
||||
nwOutline.CUSTOM: KEY_NAME[nwKeyWords.CUSTOM_KEY],
|
||||
nwOutline.SYNOP: QT_TRANSLATE_NOOP("Constant", "Synopsis"),
|
||||
}
|
||||
BUILD_FORMATS = {
|
||||
"odt": ("odt", QT_TRANSLATE_NOOP("Constant", "Open Document (.odt)")),
|
||||
"fodt": ("fodt", QT_TRANSLATE_NOOP("Constant", "Flat Open Document (.fodt)")),
|
||||
"html": ("html", QT_TRANSLATE_NOOP("Constant", "novelWriter HTML (.html)")),
|
||||
"nwd": ("nwd", QT_TRANSLATE_NOOP("Constant", "novelWriter Markdown (.nwd)")),
|
||||
"md": ("md", QT_TRANSLATE_NOOP("Constant", "Standard Markdown (.md)")),
|
||||
"md+": ("md", QT_TRANSLATE_NOOP("Constant", "Extended Markdown (.md)")),
|
||||
"jhtml": ("json", QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter HTML (.json)")),
|
||||
"jnwd": ("json", QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter Markdown (.json)")),
|
||||
}
|
||||
|
||||
# END Class nwLabels
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ from pathlib import Path
|
||||
from PyQt5.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
from novelwriter.common import checkUuid, isHandle, jsonEncode
|
||||
from novelwriter.constants import nwFiles, nwHeadFmt
|
||||
from novelwriter.constants import nwFiles, nwHeadFmt, nwLabels
|
||||
from novelwriter.core.item import NWItem
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.error import logException
|
||||
@@ -72,6 +72,9 @@ SETTINGS_TEMPLATE = {
|
||||
"format.replaceTabs": (bool, False),
|
||||
"odt.addColours": (bool, True),
|
||||
"html.addStyles": (bool, False),
|
||||
"build.splitNovel": (bool, False),
|
||||
"build.splitNotes": (bool, False),
|
||||
"build.splitChapters": (bool, False),
|
||||
}
|
||||
|
||||
SETTINGS_LABELS = {
|
||||
@@ -112,6 +115,11 @@ SETTINGS_LABELS = {
|
||||
|
||||
"html": QT_TRANSLATE_NOOP("Builds", "HTML"),
|
||||
"html.addStyles": QT_TRANSLATE_NOOP("Builds", "Add CSS Styles"),
|
||||
|
||||
"build": QT_TRANSLATE_NOOP("Builds", "Build Options"),
|
||||
"build.splitNovel": QT_TRANSLATE_NOOP("Builds", "Split Novel Root Folders"),
|
||||
"build.splitNotes": QT_TRANSLATE_NOOP("Builds", "Split Note Root Folders"),
|
||||
"build.splitChapters": QT_TRANSLATE_NOOP("Builds", "Split By Chapter Document"),
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +145,9 @@ class BuildSettings:
|
||||
def __init__(self):
|
||||
self._name = ""
|
||||
self._uuid = str(uuid.uuid4())
|
||||
self._path = Path.home()
|
||||
self._build = ""
|
||||
self._format = "odt"
|
||||
self._skipRoot = set()
|
||||
self._excluded = set()
|
||||
self._included = set()
|
||||
@@ -158,6 +169,23 @@ class BuildSettings:
|
||||
"""The build ID as an UUID."""
|
||||
return self._uuid
|
||||
|
||||
@property
|
||||
def lastPath(self) -> Path:
|
||||
"""The last used build path."""
|
||||
if self._path.is_dir():
|
||||
return self._path
|
||||
return Path.home()
|
||||
|
||||
@property
|
||||
def lastBuildName(self) -> str:
|
||||
"""The last used build name."""
|
||||
return self._build
|
||||
|
||||
@property
|
||||
def lastFormat(self) -> str:
|
||||
"""The last used build format."""
|
||||
return self._format
|
||||
|
||||
@property
|
||||
def changed(self) -> bool:
|
||||
"""The changed status of the build."""
|
||||
@@ -214,6 +242,30 @@ class BuildSettings:
|
||||
self._uuid = value
|
||||
return
|
||||
|
||||
def setLastPath(self, path: Path | str | None):
|
||||
"""Set the last used build path."""
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
if isinstance(path, Path) and path.is_dir():
|
||||
self._path = path
|
||||
else:
|
||||
self._path = Path.home()
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def setLastBuildName(self, name: str):
|
||||
"""Set the last used build name."""
|
||||
self._build = str(name).strip()
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def setLastFormat(self, key: str):
|
||||
"""Set the last used build format."""
|
||||
if key in nwLabels.BUILD_FORMATS:
|
||||
self._format = key
|
||||
self._changed = True
|
||||
return
|
||||
|
||||
def setFiltered(self, tHandle: str):
|
||||
"""Set an item as filtered."""
|
||||
self._excluded.discard(tHandle)
|
||||
@@ -325,6 +377,9 @@ class BuildSettings:
|
||||
return {
|
||||
"name": self._name,
|
||||
"uuid": self._uuid,
|
||||
"path": str(self._path),
|
||||
"build": self._build,
|
||||
"format": self._format,
|
||||
"settings": self._settings.copy(),
|
||||
"content": {
|
||||
"included": list(self._included),
|
||||
@@ -343,6 +398,9 @@ class BuildSettings:
|
||||
|
||||
self.setName(data.get("name", ""))
|
||||
self.setBuildID(data.get("uuid", ""))
|
||||
self.setLastPath(data.get("path", None))
|
||||
self.setLastBuildName(data.get("build", ""))
|
||||
self.setLastFormat(data.get("format", "odt"))
|
||||
if isinstance(included, list):
|
||||
self._included = set([h for h in included if isHandle(h)])
|
||||
if isinstance(excluded, list):
|
||||
|
||||
@@ -63,6 +63,9 @@ VALID_MAP = {
|
||||
"GuiManuscript": {
|
||||
"winWidth", "winHeight", "optsWidth", "viewWidth"
|
||||
},
|
||||
"GuiManuscriptBuild": {
|
||||
"winWidth", "winHeight", "fmtWidth", "optsWidth"
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
novelWriter – Custom Widget: Switch Box
|
||||
=======================================
|
||||
A box of icons, labels and switches
|
||||
|
||||
File History:
|
||||
Created: 2023-04-16 [2.1b1]
|
||||
@@ -22,32 +21,35 @@ General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QGridLayout, QLabel, QScrollArea, QSizePolicy, QWidget
|
||||
|
||||
from novelwriter.extensions.switch import NSwitch
|
||||
|
||||
|
||||
class NSwitchBox(QScrollArea):
|
||||
"""Extension: Switch Box Widget
|
||||
|
||||
A widget that can hold a list of switches with labels and optional
|
||||
icons. The switch toggles emits a common signal with a switch key.
|
||||
"""
|
||||
|
||||
switchToggled = pyqtSignal(str, bool)
|
||||
|
||||
def __init__(self, parent, baseSize):
|
||||
def __init__(self, parent: QWidget, baseSize: int):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._index = 0
|
||||
self._hSwitch = baseSize
|
||||
self._wSwitch = 2*self._hSwitch
|
||||
self._sIcon = baseSize
|
||||
|
||||
self.clear()
|
||||
|
||||
return
|
||||
|
||||
def clear(self):
|
||||
"""Rebuild the content of the core widget.
|
||||
"""
|
||||
"""Rebuild the content of the core widget."""
|
||||
self._content = QGridLayout()
|
||||
self._content.setColumnStretch(1, 1)
|
||||
|
||||
@@ -60,9 +62,8 @@ class NSwitchBox(QScrollArea):
|
||||
|
||||
return
|
||||
|
||||
def addLabel(self, text):
|
||||
"""Add a header label to the content box.
|
||||
"""
|
||||
def addLabel(self, text: str):
|
||||
"""Add a header label to the content box."""
|
||||
label = QLabel(text)
|
||||
font = label.font()
|
||||
font.setBold(True)
|
||||
@@ -71,9 +72,8 @@ class NSwitchBox(QScrollArea):
|
||||
self._bumpIndex()
|
||||
return
|
||||
|
||||
def addItem(self, qIcon, text, identifier, default=False):
|
||||
"""Add an item to the content box.
|
||||
"""
|
||||
def addItem(self, qIcon: QIcon, text: str, identifier: str, default: bool = False):
|
||||
"""Add an item to the content box."""
|
||||
icon = QLabel("")
|
||||
icon.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
icon.setPixmap(qIcon.pixmap(self._sIcon, self._sIcon))
|
||||
@@ -100,13 +100,17 @@ class NSwitchBox(QScrollArea):
|
||||
self._bumpIndex()
|
||||
return
|
||||
|
||||
def setInnerContentsMargins(self, left: int, top: int, right: int, bottom: int):
|
||||
"""Set the contents margins of the inner layout."""
|
||||
self._content.setContentsMargins(left, top, right, bottom)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _emitSwitchSignal(self, identifier, state):
|
||||
"""Emit a signal for a switch toggle.
|
||||
"""
|
||||
def _emitSwitchSignal(self, identifier: str, state: bool):
|
||||
"""Emit a signal for a switch toggle."""
|
||||
self.switchToggled.emit(identifier, state)
|
||||
return
|
||||
|
||||
|
||||
@@ -454,9 +454,9 @@ class GuiIcons:
|
||||
"view_novel", "view_outline",
|
||||
|
||||
# General Button Icons
|
||||
"add", "backward", "bookmark", "checked", "close", "cross", "down", "edit", "forward",
|
||||
"maximise", "menu", "minimise", "noncheckable", "reference", "refresh", "remove",
|
||||
"search_replace", "search", "settings", "unchecked", "up",
|
||||
"add", "backward", "bookmark", "browse", "checked", "close", "cross", "down", "edit",
|
||||
"export", "forward", "maximise", "menu", "minimise", "noncheckable", "reference",
|
||||
"refresh", "remove", "revert", "search_replace", "search", "settings", "unchecked", "up",
|
||||
|
||||
# Switches
|
||||
"sticky-on", "sticky-off",
|
||||
|
||||
@@ -25,7 +25,23 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5.QtWidgets import QDialog, QWidget
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractButton, QDialog, QDialogButtonBox, QFrame, QGridLayout,
|
||||
QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem, QProgressBar,
|
||||
QPushButton, QSplitter, QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.common import makeFileNameSafe
|
||||
from novelwriter.constants import nwLabels
|
||||
from novelwriter.core.buildsettings import BuildSettings
|
||||
from novelwriter.extensions.switchbox import NSwitchBox
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from novelwriter.guimain import GuiMain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -37,13 +53,146 @@ class GuiManuscriptBuild(QDialog):
|
||||
independently of the Manuscript Build Tool.
|
||||
"""
|
||||
|
||||
def __init__(self, parent: QWidget):
|
||||
def __init__(self, parent: QWidget, mainGui: GuiMain, build: BuildSettings):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
logger.debug("Create: GuiManuscriptBuild")
|
||||
self.setObjectName("GuiManuscriptBuild")
|
||||
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
self.theProject = mainGui.theProject
|
||||
|
||||
self._parent = parent
|
||||
self._build = build
|
||||
|
||||
self.setWindowTitle(self.tr("Build Manuscript"))
|
||||
self.setMinimumWidth(CONFIG.pxInt(500))
|
||||
self.setMinimumHeight(CONFIG.pxInt(250))
|
||||
|
||||
wWin = CONFIG.pxInt(660)
|
||||
hWin = CONFIG.pxInt(350)
|
||||
|
||||
pOptions = self.theProject.options
|
||||
self.resize(
|
||||
CONFIG.pxInt(pOptions.getInt("GuiManuscriptBuild", "winWidth", wWin)),
|
||||
CONFIG.pxInt(pOptions.getInt("GuiManuscriptBuild", "winHeight", hWin))
|
||||
)
|
||||
|
||||
# Formats
|
||||
# =======
|
||||
|
||||
self.lblFormat = QLabel("<b>{0}</b>".format(self.tr("Build Format")))
|
||||
self.listFormats = QListWidget()
|
||||
current = None
|
||||
for key, (_, label) in nwLabels.BUILD_FORMATS.items():
|
||||
item = QListWidgetItem()
|
||||
item.setText(label)
|
||||
item.setData(Qt.UserRole, key)
|
||||
self.listFormats.addItem(item)
|
||||
if key == self._build.lastFormat:
|
||||
current = item
|
||||
if current:
|
||||
self.listFormats.setCurrentItem(current)
|
||||
|
||||
self.formatBox = QVBoxLayout()
|
||||
self.formatBox.addWidget(self.lblFormat, 0)
|
||||
self.formatBox.addWidget(self.listFormats, 1)
|
||||
self.formatBox.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.formatWidget = QWidget()
|
||||
self.formatWidget.setLayout(self.formatBox)
|
||||
self.formatWidget.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# Build Controls
|
||||
# ==============
|
||||
|
||||
# Build Options
|
||||
self.swtOptions = NSwitchBox(self, self.mainTheme.baseIconSize)
|
||||
self.swtOptions.switchToggled.connect(self._applyBuildOptions)
|
||||
self.swtOptions.setFrameStyle(QFrame.NoFrame)
|
||||
self.swtOptions.setInnerContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.swtOptions.addLabel(self._build.getLabel("build"))
|
||||
self.swtOptions.addItem(
|
||||
self.mainTheme.getIcon("cls_novel"), self._build.getLabel("build.splitNovel"),
|
||||
"build.splitNovel", default=self._build.getBool("build.splitNovel")
|
||||
)
|
||||
self.swtOptions.addItem(
|
||||
self.mainTheme.getIcon("cls_custom"), self._build.getLabel("build.splitNotes"),
|
||||
"build.splitNotes", default=self._build.getBool("build.splitNotes")
|
||||
)
|
||||
self.swtOptions.addItem(
|
||||
self.mainTheme.getIcon("proj_chapter"), self._build.getLabel("build.splitChapters"),
|
||||
"build.splitChapters", default=self._build.getBool("build.splitChapters")
|
||||
)
|
||||
|
||||
# Dialog Controls
|
||||
# ===============
|
||||
|
||||
# Build Path
|
||||
self.lblPath = QLabel(self.tr("Build Folder"))
|
||||
self.buildPath = QLineEdit()
|
||||
self.buildPath.setText(str(self._build.lastPath))
|
||||
self.btnBrowse = QPushButton(self.mainTheme.getIcon("browse"), "")
|
||||
|
||||
self.pathBox = QHBoxLayout()
|
||||
self.pathBox.addWidget(self.buildPath)
|
||||
self.pathBox.addWidget(self.btnBrowse)
|
||||
|
||||
# Build Name
|
||||
self.lblName = QLabel(self.tr("Build Name"))
|
||||
self.buildName = QLineEdit()
|
||||
self.btnReset = QPushButton(self.mainTheme.getIcon("revert"), "")
|
||||
self.btnReset.setToolTip(self.tr("Reset Build Name to default"))
|
||||
self.btnReset.clicked.connect(self._doResetBuildName)
|
||||
|
||||
self.nameBox = QHBoxLayout()
|
||||
self.nameBox.addWidget(self.buildName)
|
||||
self.nameBox.addWidget(self.btnReset)
|
||||
|
||||
# Build Progress
|
||||
self.lblProgress = QLabel(self.tr("Build Progress"))
|
||||
self.buildProgress = QProgressBar()
|
||||
|
||||
# Dialog Buttons
|
||||
self.dlgButtons = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
self.dlgButtons.addButton(
|
||||
QPushButton(self.mainTheme.getIcon("export"), self.tr("&Build")),
|
||||
QDialogButtonBox.ActionRole
|
||||
)
|
||||
self.dlgButtons.clicked.connect(self._dialogButtonClicked)
|
||||
|
||||
# Assemble GUI
|
||||
# ============
|
||||
|
||||
self.mainSplit = QSplitter()
|
||||
self.mainSplit.addWidget(self.formatWidget)
|
||||
self.mainSplit.addWidget(self.swtOptions)
|
||||
self.mainSplit.setHandleWidth(CONFIG.pxInt(16))
|
||||
self.mainSplit.setCollapsible(0, False)
|
||||
self.mainSplit.setCollapsible(1, False)
|
||||
self.mainSplit.setSizes([
|
||||
CONFIG.pxInt(pOptions.getInt("GuiManuscriptBuild", "fmtWidth", int(0.45*wWin))),
|
||||
CONFIG.pxInt(pOptions.getInt("GuiManuscriptBuild", "optsWidth", int(0.55*wWin))),
|
||||
])
|
||||
|
||||
self.outerBox = QGridLayout()
|
||||
self.outerBox.addWidget(self.mainSplit, 0, 0, 1, 2)
|
||||
self.outerBox.addWidget(self.lblPath, 1, 0, 1, 1)
|
||||
self.outerBox.addLayout(self.pathBox, 1, 1, 1, 1)
|
||||
self.outerBox.addWidget(self.lblName, 2, 0, 1, 1)
|
||||
self.outerBox.addLayout(self.nameBox, 2, 1, 1, 1)
|
||||
self.outerBox.addWidget(self.lblProgress, 3, 0, 1, 1)
|
||||
self.outerBox.addWidget(self.buildProgress, 3, 1, 1, 1)
|
||||
self.outerBox.addWidget(self.dlgButtons, 4, 0, 1, 2)
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
if self._build.lastBuildName:
|
||||
self.buildName.setText(makeFileNameSafe(self._build.lastBuildName))
|
||||
else:
|
||||
self._doResetBuildName()
|
||||
|
||||
logger.debug("Ready: GuiManuscriptBuild")
|
||||
|
||||
@@ -54,4 +203,91 @@ class GuiManuscriptBuild(QDialog):
|
||||
logger.debug("Delete: GuiManuscriptBuild")
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Capture the user closing the window so we can save GUI
|
||||
settings.
|
||||
"""
|
||||
self._saveSettings()
|
||||
event.accept()
|
||||
self.deleteLater()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str, bool)
|
||||
def _applyBuildOptions(self, key: str, state: bool):
|
||||
"""Set the build options for the build."""
|
||||
self._build.setValue(key, state)
|
||||
return
|
||||
|
||||
@pyqtSlot("QAbstractButton*")
|
||||
def _dialogButtonClicked(self, button: QAbstractButton):
|
||||
"""Handle button clicks from the dialog button box."""
|
||||
role = self.dlgButtons.buttonRole(button)
|
||||
if role == QDialogButtonBox.ActionRole:
|
||||
self._runBuild()
|
||||
elif role == QDialogButtonBox.RejectRole:
|
||||
self.close()
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doResetBuildName(self):
|
||||
"""Generate a default build name."""
|
||||
bName = makeFileNameSafe(f"{self.theProject.data.name} - {self._build.name}")
|
||||
self.buildName.setText(bName)
|
||||
self._build.setLastBuildName(bName)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _runBuild(self) -> bool:
|
||||
"""Run the currently selected build."""
|
||||
bFormat = self._getSelectedFormat()
|
||||
if not bFormat:
|
||||
return False
|
||||
|
||||
bPath = self.buildPath.text()
|
||||
bName = self.buildName.text()
|
||||
|
||||
self._build.setLastFormat(bFormat)
|
||||
self._build.setLastPath(bPath)
|
||||
self._build.setLastBuildName(bName)
|
||||
|
||||
return True
|
||||
|
||||
def _getSelectedFormat(self) -> str | None:
|
||||
"""Get the currently selected format."""
|
||||
items = self.listFormats.selectedItems()
|
||||
if items and isinstance(items[0], QListWidgetItem):
|
||||
return str(items[0].data(Qt.UserRole))
|
||||
return None
|
||||
|
||||
def _saveSettings(self):
|
||||
"""Save the user GUI settings."""
|
||||
logger.debug("Saving GuiManuscriptBuild settings")
|
||||
|
||||
winWidth = CONFIG.rpxInt(self.width())
|
||||
winHeight = CONFIG.rpxInt(self.height())
|
||||
|
||||
mainSplit = self.mainSplit.sizes()
|
||||
fmtWidth = CONFIG.rpxInt(mainSplit[0])
|
||||
optsWidth = CONFIG.rpxInt(mainSplit[1])
|
||||
|
||||
pOptions = self.theProject.options
|
||||
pOptions.setValue("GuiManuscriptBuild", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiManuscriptBuild", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiManuscriptBuild", "fmtWidth", fmtWidth)
|
||||
pOptions.setValue("GuiManuscriptBuild", "optsWidth", optsWidth)
|
||||
pOptions.saveSettings()
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiManuscriptBuild
|
||||
|
||||
@@ -44,6 +44,7 @@ from novelwriter.common import checkInt, fuzzyTime
|
||||
from novelwriter.core.tohtml import ToHtml
|
||||
from novelwriter.core.docbuild import NWBuildDocument
|
||||
from novelwriter.core.buildsettings import BuildCollection, BuildSettings
|
||||
from novelwriter.tools.manusbuild import GuiManuscriptBuild
|
||||
from novelwriter.tools.manussettings import GuiBuildSettings
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
@@ -107,26 +108,16 @@ class GuiManuscript(QDialog):
|
||||
self.btnPreview.clicked.connect(self._generatePreview)
|
||||
self.manPreview = _PreviewWidget(self.mainGui)
|
||||
|
||||
self.btnBuild = QPushButton(self.tr("Build"))
|
||||
self.btnBuild.clicked.connect(self._buildManuscript)
|
||||
|
||||
self.menuPrint = QMenu(self)
|
||||
self.aPrintSend = self.menuPrint.addAction(self.tr("Print Preview"))
|
||||
self.aPrintFile = self.menuPrint.addAction(self.tr("Print to PDF"))
|
||||
|
||||
self.menuSave = QMenu(self)
|
||||
self.aSaveODT = self.menuSave.addAction(self.tr("Open Document (.odt)"))
|
||||
self.aSaveFODT = self.menuSave.addAction(self.tr("Flat Open Document (.fodt)"))
|
||||
self.aSaveHTM = self.menuSave.addAction(self.tr("novelWriter HTML (.htm)"))
|
||||
self.aSaveNWD = self.menuSave.addAction(self.tr("novelWriter Markdown (.nwd)"))
|
||||
self.aSaveMD = self.menuSave.addAction(self.tr("Standard Markdown (.md)"))
|
||||
self.aSaveGH = self.menuSave.addAction(self.tr("GitHub Markdown (.md)"))
|
||||
self.aSaveJsonH = self.menuSave.addAction(self.tr("JSON + novelWriter HTML (.json)"))
|
||||
self.aSaveJsonM = self.menuSave.addAction(self.tr("JSON + novelWriter Markdown (.json)"))
|
||||
|
||||
self.btnPrint = QPushButton(self.tr("Print"))
|
||||
self.btnPrint.setMenu(self.menuPrint)
|
||||
|
||||
self.btnSave = QPushButton(self.tr("Save As"))
|
||||
self.btnSave.setMenu(self.menuSave)
|
||||
|
||||
self.btnClose = QPushButton(self.tr("Close"))
|
||||
self.btnClose.clicked.connect(self._doClose)
|
||||
|
||||
@@ -139,7 +130,7 @@ class GuiManuscript(QDialog):
|
||||
self.buildBox.addWidget(self.btnDelete)
|
||||
|
||||
self.processBox = QHBoxLayout()
|
||||
self.processBox.addWidget(self.btnSave)
|
||||
self.processBox.addWidget(self.btnBuild)
|
||||
self.processBox.addWidget(self.btnPrint)
|
||||
self.processBox.addWidget(self.btnClose)
|
||||
|
||||
@@ -283,6 +274,23 @@ class GuiManuscript(QDialog):
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _buildManuscript(self):
|
||||
"""Open the build dialog and build the manuscript."""
|
||||
build = self._getSelectedBuild()
|
||||
if build is None:
|
||||
return
|
||||
|
||||
dlgBuild = GuiManuscriptBuild(self, self.mainGui, build)
|
||||
dlgBuild.exec_()
|
||||
|
||||
# After the build is done, save build settings changes
|
||||
if build.changed:
|
||||
self._builds.setBuild(build)
|
||||
self._builds.saveCollection()
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doClose(self):
|
||||
"""Forward the close button to the default close method."""
|
||||
@@ -514,7 +522,7 @@ class _PreviewWidget(QTextBrowser):
|
||||
if self._docTime > 0:
|
||||
strBuildTime = "%s (%s)" % (
|
||||
datetime.fromtimestamp(self._docTime).strftime("%x %X"),
|
||||
fuzzyTime(time() - self._docTime)
|
||||
fuzzyTime(int(time()) - self._docTime)
|
||||
)
|
||||
else:
|
||||
strBuildTime = self.tr("Unknown")
|
||||
|
||||
@@ -25,24 +25,23 @@ import hashlib
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from mocked import causeOSError
|
||||
from tools import writeFile
|
||||
from mocked import causeOSError
|
||||
|
||||
from novelwriter.guimain import GuiMain
|
||||
from novelwriter.common import (
|
||||
checkStringNone, checkString, checkInt, checkFloat, checkBool, checkHandle,
|
||||
checkUuid, checkPath, isHandle, isTitleTag, isItemClass, isItemType,
|
||||
isItemLayout, hexToInt, minmax, checkIntTuple, formatInt, formatTimeStamp,
|
||||
formatTime, simplified, yesNo, transferCase, fuzzyTime, numberToRoman,
|
||||
jsonEncode, readTextFile, makeFileNameSafe, sha256sum, getGuiItem,
|
||||
NWConfigParser
|
||||
checkBool, checkFloat, checkHandle, checkInt, checkIntTuple, checkPath,
|
||||
checkString, checkStringNone, checkUuid, formatInt, formatTime,
|
||||
formatTimeStamp, fuzzyTime, getGuiItem, hexToInt, isHandle, isItemClass,
|
||||
isItemLayout, isItemType, isTitleTag, jsonEncode, makeFileNameSafe, minmax,
|
||||
numberToRoman, NWConfigParser, readTextFile, sha256sum, simplified,
|
||||
transferCase, yesNo
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckStringNone():
|
||||
"""Test the checkStringNone function.
|
||||
"""
|
||||
"""Test the checkStringNone function."""
|
||||
assert checkStringNone("Stuff", "NotNone") == "Stuff"
|
||||
assert checkStringNone("None", "NotNone") is None
|
||||
assert checkStringNone(None, "NotNone") is None
|
||||
@@ -151,8 +150,7 @@ def testBaseCommon_CheckBool():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckHandle():
|
||||
"""Test the checkHandle function.
|
||||
"""
|
||||
"""Test the checkHandle function."""
|
||||
assert checkHandle("None", 1, True) is None
|
||||
assert checkHandle("None", 1, False) == 1
|
||||
assert checkHandle(None, 1, True) is None
|
||||
@@ -165,8 +163,7 @@ def testBaseCommon_CheckHandle():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckUuid():
|
||||
"""Test the checkUuid function.
|
||||
"""
|
||||
"""Test the checkUuid function."""
|
||||
testUuid = "e2be99af-f9bf-4403-857a-c3d1ac25abea"
|
||||
assert checkUuid("", None) is None
|
||||
assert checkUuid("e2be99af-f9bf-4403-857a-c3d1ac25abe", None) is None
|
||||
@@ -179,8 +176,7 @@ def testBaseCommon_CheckUuid():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckPath():
|
||||
"""Test the checkPath function.
|
||||
"""
|
||||
"""Test the checkPath function."""
|
||||
assert checkPath(Path("test"), None) == Path("test")
|
||||
assert checkPath("test", None) == Path("test")
|
||||
assert checkPath(None, None) is None
|
||||
@@ -192,8 +188,7 @@ def testBaseCommon_CheckPath():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsHandle():
|
||||
"""Test the isHandle function.
|
||||
"""
|
||||
"""Test the isHandle function."""
|
||||
assert isHandle("47666c91c7ccf") is True
|
||||
assert isHandle("47666C91C7CCF") is False
|
||||
assert isHandle("h7666c91c7ccf") is False
|
||||
@@ -206,8 +201,7 @@ def testBaseCommon_IsHandle():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsTitleTag():
|
||||
"""Test the isItemClass function.
|
||||
"""
|
||||
"""Test the isItemClass function."""
|
||||
assert isTitleTag("T1234") is True
|
||||
|
||||
assert isTitleTag("t1234") is False
|
||||
@@ -224,8 +218,7 @@ def testBaseCommon_IsTitleTag():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsItemClass():
|
||||
"""Test the isItemClass function.
|
||||
"""
|
||||
"""Test the isItemClass function."""
|
||||
assert isItemClass("NO_CLASS") is True
|
||||
assert isItemClass("NOVEL") is True
|
||||
assert isItemClass("PLOT") is True
|
||||
@@ -248,8 +241,7 @@ def testBaseCommon_IsItemClass():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsItemType():
|
||||
"""Test the isItemType function.
|
||||
"""
|
||||
"""Test the isItemType function."""
|
||||
assert isItemType("NO_TYPE") is True
|
||||
assert isItemType("ROOT") is True
|
||||
assert isItemType("FOLDER") is True
|
||||
@@ -268,8 +260,7 @@ def testBaseCommon_IsItemType():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_IsItemLayout():
|
||||
"""Test the isItemLayout function.
|
||||
"""
|
||||
"""Test the isItemLayout function."""
|
||||
assert isItemLayout("NO_LAYOUT") is True
|
||||
assert isItemLayout("DOCUMENT") is True
|
||||
assert isItemLayout("NOTE") is True
|
||||
@@ -293,8 +284,7 @@ def testBaseCommon_IsItemLayout():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_HexToInt():
|
||||
"""Test the hexToInt function.
|
||||
"""
|
||||
"""Test the hexToInt function."""
|
||||
assert hexToInt(1) == 0
|
||||
assert hexToInt("1") == 1
|
||||
assert hexToInt("0xff") == 255
|
||||
@@ -307,8 +297,7 @@ def testBaseCommon_HexToInt():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_MinMax():
|
||||
"""Test the minmax function.
|
||||
"""
|
||||
"""Test the minmax function."""
|
||||
for i in range(-5, 15):
|
||||
assert 0 <= minmax(i, 0, 10) <= 10
|
||||
|
||||
@@ -317,8 +306,7 @@ def testBaseCommon_MinMax():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_CheckIntTuple():
|
||||
"""Test the checkIntTuple function.
|
||||
"""
|
||||
"""Test the checkIntTuple function."""
|
||||
assert checkIntTuple(0, (0, 1, 2), 3) == 0
|
||||
assert checkIntTuple(5, (0, 1, 2), 3) == 3
|
||||
|
||||
@@ -327,8 +315,7 @@ def testBaseCommon_CheckIntTuple():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_FormatTimeStamp():
|
||||
"""Test the formatTimeStamp function.
|
||||
"""
|
||||
"""Test the formatTimeStamp function."""
|
||||
tTime = time.mktime(time.gmtime(0))
|
||||
assert formatTimeStamp(tTime, False) == "1970-01-01 00:00:00"
|
||||
assert formatTimeStamp(tTime, True) == "1970-01-01 00.00.00"
|
||||
@@ -338,8 +325,7 @@ def testBaseCommon_FormatTimeStamp():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_FormatTime():
|
||||
"""Test the formatTime function.
|
||||
"""
|
||||
"""Test the formatTime function."""
|
||||
assert formatTime("1") == "ERROR"
|
||||
assert formatTime(1.0) == "ERROR"
|
||||
assert formatTime(1) == "00:00:01"
|
||||
@@ -361,8 +347,7 @@ def testBaseCommon_FormatTime():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_Simplified():
|
||||
"""Test the simplified function.
|
||||
"""
|
||||
"""Test the simplified function."""
|
||||
assert simplified("Hello World") == "Hello World"
|
||||
assert simplified(" Hello World ") == "Hello World"
|
||||
assert simplified("\tHello\n\r\tWorld") == "Hello World"
|
||||
@@ -372,8 +357,7 @@ def testBaseCommon_Simplified():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_YesNo():
|
||||
"""Test the yesNo function.
|
||||
"""
|
||||
"""Test the yesNo function."""
|
||||
# Bool
|
||||
assert yesNo(True) == "yes"
|
||||
assert yesNo(False) == "no"
|
||||
@@ -400,8 +384,7 @@ def testBaseCommon_YesNo():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_FormatInt():
|
||||
"""Test the formatInt function.
|
||||
"""
|
||||
"""Test the formatInt function."""
|
||||
# Normal Cases
|
||||
assert formatInt(1) == "1"
|
||||
assert formatInt(12) == "12"
|
||||
@@ -424,8 +407,7 @@ def testBaseCommon_FormatInt():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_TransferCase():
|
||||
"""Test the transferCase function.
|
||||
"""
|
||||
"""Test the transferCase function."""
|
||||
assert transferCase(1, "TaRgEt") == "TaRgEt"
|
||||
assert transferCase("source", 1) == 1
|
||||
assert transferCase("", "TaRgEt") == "TaRgEt"
|
||||
@@ -439,8 +421,7 @@ def testBaseCommon_TransferCase():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_FuzzyTime():
|
||||
"""Test the fuzzyTime function.
|
||||
"""
|
||||
"""Test the fuzzyTime function."""
|
||||
assert fuzzyTime(-1) == "in the future"
|
||||
assert fuzzyTime(0) == "just now"
|
||||
assert fuzzyTime(29) == "just now"
|
||||
@@ -475,8 +456,7 @@ def testBaseCommon_FuzzyTime():
|
||||
|
||||
@pytest.mark.core
|
||||
def testBaseCommon_RomanNumbers():
|
||||
"""Test conversion of integers to Roman numbers.
|
||||
"""
|
||||
"""Test conversion of integers to Roman numbers."""
|
||||
assert numberToRoman(None, False) == "NAN"
|
||||
assert numberToRoman(0, False) == "OOR"
|
||||
assert numberToRoman(1, False) == "I"
|
||||
@@ -503,8 +483,7 @@ def testBaseCommon_RomanNumbers():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_JsonEncode():
|
||||
"""Test the jsonEncode function.
|
||||
"""
|
||||
"""Test the jsonEncode function."""
|
||||
# Wrong type
|
||||
assert jsonEncode(None) == "[]"
|
||||
|
||||
@@ -587,8 +566,7 @@ def testBaseCommon_JsonEncode():
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_ReadTextFile(monkeypatch, fncPath, ipsumText):
|
||||
"""Test the readTextFile function.
|
||||
"""
|
||||
"""Test the readTextFile function."""
|
||||
testText = "\n\n".join(ipsumText) + "\n"
|
||||
testFile = fncPath / "ipsum.txt"
|
||||
writeFile(testFile, testText)
|
||||
@@ -605,20 +583,20 @@ def testBaseCommon_ReadTextFile(monkeypatch, fncPath, ipsumText):
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_MakeFileNameSafe():
|
||||
"""Test the makeFileNameSafe function.
|
||||
"""
|
||||
"""Test the makeFileNameSafe function."""
|
||||
assert makeFileNameSafe(" aaaa ") == "aaaa"
|
||||
assert makeFileNameSafe("aaaa,bbbb") == "aaaabbbb"
|
||||
assert makeFileNameSafe("aaaa\tbbbb") == "aaaabbbb"
|
||||
assert makeFileNameSafe("aaaa bbbb") == "aaaa bbbb"
|
||||
assert makeFileNameSafe("æøå") == "æøå"
|
||||
assert makeFileNameSafe("Stuff œfi2⁵") == "Stuff œfi25"
|
||||
|
||||
# END Test testBaseCommon_MakeFileNameSafe
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_Sha256Sum(monkeypatch, fncPath, ipsumText):
|
||||
"""Test the sha256sum function.
|
||||
"""
|
||||
"""Test the sha256sum function."""
|
||||
longText = 50*(" ".join(ipsumText) + " ")
|
||||
shortText = "This is a short file"
|
||||
noneText = ""
|
||||
@@ -657,8 +635,7 @@ def testBaseCommon_Sha256Sum(monkeypatch, fncPath, ipsumText):
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_GetGuiItem(nwGUI):
|
||||
"""Check the GUI item function.
|
||||
"""
|
||||
"""Check the GUI item function."""
|
||||
assert getGuiItem("gibberish") is None
|
||||
assert isinstance(getGuiItem("GuiMain"), GuiMain)
|
||||
|
||||
@@ -667,8 +644,7 @@ def testBaseCommon_GetGuiItem(nwGUI):
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_NWConfigParser(fncPath):
|
||||
"""Test the NWConfigParser subclass.
|
||||
"""
|
||||
"""Test the NWConfigParser subclass."""
|
||||
tstConf = fncPath / "test.cfg"
|
||||
writeFile(tstConf, (
|
||||
"[main]\n"
|
||||
|
||||
Reference in New Issue
Block a user