Merge release 2.7.1 into 2.8a0
This commit is contained in:
@@ -8,10 +8,13 @@ jobs:
|
|||||||
|
|
||||||
buildLinux-AppImage:
|
buildLinux-AppImage:
|
||||||
needs: buildAssets
|
needs: buildAssets
|
||||||
runs-on: ubuntu-latest
|
# Needs to stay on 22.04 as long as we're using manylinux_2_28
|
||||||
|
# as libxcb-cursor0 in 22.04 supports glibc >= 2.17
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: "3.13"
|
PYTHON_VERSION: "3.13"
|
||||||
LINUX_TAG: "manylinux_2_28_x86_64"
|
LINUX_TAG: "manylinux_2_28"
|
||||||
|
LINUX_ARCH: "x86_64"
|
||||||
steps:
|
steps:
|
||||||
- name: Python Setup
|
- name: Python Setup
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
@@ -19,6 +22,11 @@ jobs:
|
|||||||
python-version: "3.13"
|
python-version: "3.13"
|
||||||
architecture: x64
|
architecture: x64
|
||||||
|
|
||||||
|
- name: Install Packages (apt)
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install libxcb-cursor0
|
||||||
|
|
||||||
- name: Install Packages (pip)
|
- name: Install Packages (pip)
|
||||||
run: pip install python-appimage setuptools
|
run: pip install python-appimage setuptools
|
||||||
|
|
||||||
@@ -34,8 +42,11 @@ jobs:
|
|||||||
- name: Build AppImage
|
- name: Build AppImage
|
||||||
id: build
|
id: build
|
||||||
run: |
|
run: |
|
||||||
|
wget https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$LINUX_ARCH.AppImage
|
||||||
|
chmod +x appimagetool-$LINUX_ARCH.AppImage
|
||||||
|
export APPIMAGE_TOOL_EXEC="$(pwd)/appimagetool-$LINUX_ARCH.AppImage"
|
||||||
echo "BUILD_VERSION=$(python pkgutils.py version)" >> $GITHUB_OUTPUT
|
echo "BUILD_VERSION=$(python pkgutils.py version)" >> $GITHUB_OUTPUT
|
||||||
python pkgutils.py build-appimage --linux-tag $LINUX_TAG --python-version $PYTHON_VERSION
|
python pkgutils.py build-appimage $LINUX_TAG $LINUX_ARCH $PYTHON_VERSION
|
||||||
|
|
||||||
- name: Upload Artifacts
|
- name: Upload Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
@@ -1,5 +1,38 @@
|
|||||||
# novelWriter Changelog
|
# novelWriter Changelog
|
||||||
|
|
||||||
|
## Version 2.7.1 [2025-06-09]
|
||||||
|
|
||||||
|
### Release Notes
|
||||||
|
|
||||||
|
This is a patch release that fixes some issues with tags and references, an issue with the
|
||||||
|
auto-complete menu on Windows, and some issues with the AppImage on Linux. The Czech translation
|
||||||
|
for 2.7 has also been completed.
|
||||||
|
|
||||||
|
### Detailed Changelog
|
||||||
|
|
||||||
|
**Bugfixes**
|
||||||
|
|
||||||
|
* Fix a bug where tags would be listed in the auto-complete menu for mentions even if the file
|
||||||
|
defining it had been moved to Archive or Trash. Issue #2387. PR #2389.
|
||||||
|
* Fixed a bug that seems to only have occurred on Windows where the auto-complete menu would hold
|
||||||
|
on to the return and arrow keys even when it was hidden. Issue #2386. PR #2389.
|
||||||
|
|
||||||
|
**Improvements**
|
||||||
|
|
||||||
|
* The auto-complete menu in the editor no longer shows any suggestions when defining the tag.
|
||||||
|
This never made sense anyway. PR #2389.
|
||||||
|
|
||||||
|
**Internationalisation**
|
||||||
|
|
||||||
|
* Updated the Czech. PR #2393.
|
||||||
|
|
||||||
|
**Packaging**
|
||||||
|
|
||||||
|
* Fixed an issue with the AppImage release where on some platforms `libxcb-cursor0` was missing.
|
||||||
|
The library is now included in the AppImage. Issue #2374. PR #2392.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
## Version 2.7 [2025-06-01]
|
## Version 2.7 [2025-06-01]
|
||||||
|
|
||||||
### Release Notes
|
### Release Notes
|
||||||
|
|||||||
+184
-184
File diff suppressed because it is too large
Load Diff
+235
-235
File diff suppressed because it is too large
Load Diff
+184
-184
File diff suppressed because it is too large
Load Diff
+184
-184
File diff suppressed because it is too large
Load Diff
+184
-184
File diff suppressed because it is too large
Load Diff
+184
-184
File diff suppressed because it is too large
Load Diff
+184
-184
File diff suppressed because it is too large
Load Diff
+184
-184
File diff suppressed because it is too large
Load Diff
+184
-184
File diff suppressed because it is too large
Load Diff
+184
-184
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,8 @@
|
|||||||
"Short Description": "Krátký popis",
|
"Short Description": "Krátký popis",
|
||||||
"Footnotes": "Poznámky pod čarou",
|
"Footnotes": "Poznámky pod čarou",
|
||||||
"Comment": "Komentář",
|
"Comment": "Komentář",
|
||||||
|
"Story Structure": "Struktura příběhu",
|
||||||
|
"Note": "Poznámka",
|
||||||
"Notes": "Poznámka",
|
"Notes": "Poznámka",
|
||||||
"Tag": "Štítek",
|
"Tag": "Štítek",
|
||||||
"Point of View": "Úhel pohledu",
|
"Point of View": "Úhel pohledu",
|
||||||
|
|||||||
@@ -185,6 +185,10 @@ class nwKeyWords:
|
|||||||
POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
|
POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
|
||||||
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY,
|
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY,
|
||||||
]
|
]
|
||||||
|
CAN_LOOKUP: Final[list[str]] = [
|
||||||
|
POV_KEY, FOCUS_KEY, CHAR_KEY, PLOT_KEY, TIME_KEY, WORLD_KEY,
|
||||||
|
OBJECT_KEY, ENTITY_KEY, CUSTOM_KEY, STORY_KEY, MENTION_KEY,
|
||||||
|
]
|
||||||
|
|
||||||
# Set of Valid Keys
|
# Set of Valid Keys
|
||||||
VALID_KEYS: Final[set[str]] = set(ALL_KEYS)
|
VALID_KEYS: Final[set[str]] = set(ALL_KEYS)
|
||||||
|
|||||||
+16
-10
@@ -131,12 +131,6 @@ class Index:
|
|||||||
self._novelExtra = extra
|
self._novelExtra = extra
|
||||||
return
|
return
|
||||||
|
|
||||||
def setItemClass(self, tHandle: str, itemClass: nwItemClass) -> None:
|
|
||||||
"""Update the class for all tags of a handle."""
|
|
||||||
logger.info("Updating class for '%s'", tHandle)
|
|
||||||
self._tagsIndex.updateClass(tHandle, itemClass.name)
|
|
||||||
return
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Public Methods
|
# Public Methods
|
||||||
##
|
##
|
||||||
@@ -183,6 +177,16 @@ class Index:
|
|||||||
self.scanText(tHandle, self._project.storage.getDocumentText(tHandle))
|
self.scanText(tHandle, self._project.storage.getDocumentText(tHandle))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def refreshHandle(self, tHandle: str) -> None:
|
||||||
|
"""Update the class for all tags of a handle."""
|
||||||
|
if item := self._project.tree[tHandle]:
|
||||||
|
logger.info("Updating class for '%s'", tHandle)
|
||||||
|
if item.isInactiveClass():
|
||||||
|
self.deleteHandle(tHandle)
|
||||||
|
else:
|
||||||
|
self._tagsIndex.updateClass(tHandle, item.itemClass.name)
|
||||||
|
return
|
||||||
|
|
||||||
def indexChangedSince(self, checkTime: int | float) -> bool:
|
def indexChangedSince(self, checkTime: int | float) -> bool:
|
||||||
"""Check if the index has changed since a given time."""
|
"""Check if the index has changed since a given time."""
|
||||||
return self._indexChange > float(checkTime)
|
return self._indexChange > float(checkTime)
|
||||||
@@ -753,10 +757,12 @@ class Index:
|
|||||||
"""Return all tags used by a specific document."""
|
"""Return all tags used by a specific document."""
|
||||||
return self._itemIndex.allItemTags(tHandle) if tHandle else []
|
return self._itemIndex.allItemTags(tHandle) if tHandle else []
|
||||||
|
|
||||||
def getClassTags(self, itemClass: nwItemClass | None) -> list[str]:
|
def getKeyWordTags(self, keyWord: str) -> list[str]:
|
||||||
"""Return all tags based on itemClass."""
|
"""Return all tags usable for a specific keyword."""
|
||||||
name = None if itemClass is None else itemClass.name
|
if keyWord in nwKeyWords.CAN_LOOKUP:
|
||||||
return self._tagsIndex.filterTagNames(name)
|
itemClass = nwKeyWords.KEY_CLASS.get(keyWord)
|
||||||
|
return self._tagsIndex.filterTagNames(itemClass.name if itemClass else None)
|
||||||
|
return []
|
||||||
|
|
||||||
def getTagsData(
|
def getTagsData(
|
||||||
self, activeOnly: bool = True
|
self, activeOnly: bool = True
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ class NWItem:
|
|||||||
self.setClass(itemClass)
|
self.setClass(itemClass)
|
||||||
if self._type == nwItemType.FILE:
|
if self._type == nwItemType.FILE:
|
||||||
# Notify the index of the class change
|
# Notify the index of the class change
|
||||||
self._project.index.setItemClass(self._handle, itemClass)
|
self._project.index.refreshHandle(self._handle)
|
||||||
|
|
||||||
if self._layout == nwItemLayout.NO_LAYOUT:
|
if self._layout == nwItemLayout.NO_LAYOUT:
|
||||||
# If no layout is set, pick one
|
# If no layout is set, pick one
|
||||||
|
|||||||
@@ -1090,11 +1090,14 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
show = self._completer.updateMetaText(text, bPos)
|
show = self._completer.updateMetaText(text, bPos)
|
||||||
else:
|
else:
|
||||||
show = self._completer.updateCommentText(text, bPos)
|
show = self._completer.updateCommentText(text, bPos)
|
||||||
point = self.cursorRect().bottomRight()
|
if show:
|
||||||
self._completer.move(viewport.mapToGlobal(point))
|
point = self.cursorRect().bottomRight()
|
||||||
self._completer.setVisible(show)
|
self._completer.move(viewport.mapToGlobal(point))
|
||||||
|
self._completer.show()
|
||||||
|
else:
|
||||||
|
self._completer.close()
|
||||||
else:
|
else:
|
||||||
self._completer.setVisible(False)
|
self._completer.close()
|
||||||
|
|
||||||
if self._doReplace and added == 1:
|
if self._doReplace and added == 1:
|
||||||
cursor = self.textCursor()
|
cursor = self.textCursor()
|
||||||
@@ -2113,9 +2116,7 @@ class CommandCompleter(QMenu):
|
|||||||
length = len(lookup)
|
length = len(lookup)
|
||||||
suffix = ""
|
suffix = ""
|
||||||
options = sorted(filter(
|
options = sorted(filter(
|
||||||
lambda x: lookup in x.lower(), SHARED.project.index.getClassTags(
|
lambda x: lookup in x.lower(), SHARED.project.index.getKeyWordTags(kw.strip())
|
||||||
nwKeyWords.KEY_CLASS.get(kw.strip())
|
|
||||||
)
|
|
||||||
))[:15]
|
))[:15]
|
||||||
|
|
||||||
if not options:
|
if not options:
|
||||||
|
|||||||
+6
-18
@@ -272,24 +272,12 @@ if __name__ == "__main__":
|
|||||||
cmdBuildUbuntu.set_defaults(func=utils.build_debian.launchpad)
|
cmdBuildUbuntu.set_defaults(func=utils.build_debian.launchpad)
|
||||||
|
|
||||||
# Build AppImage
|
# Build AppImage
|
||||||
cmdBuildAppImage = parsers.add_parser(
|
# See https://github.com/pypa/manylinux
|
||||||
"build-appimage", help=(
|
# See https://python-appimage.readthedocs.io/en/latest/#available-python-appimages
|
||||||
"Build an AppImage. "
|
cmdBuildAppImage = parsers.add_parser("build-appimage", help="Build an AppImage.")
|
||||||
"Argument --linux-tag defaults manylinux_2_28_x86_64, and --python-version to 3.13."
|
cmdBuildAppImage.add_argument("linux", help="Manylinux version, e.g. manylinux_2_28.")
|
||||||
)
|
cmdBuildAppImage.add_argument("arch", help="Architecture, e.g. x86_64.")
|
||||||
)
|
cmdBuildAppImage.add_argument("python", help="Python version, e.g. 3.13.")
|
||||||
cmdBuildAppImage.add_argument(
|
|
||||||
"--linux-tag",
|
|
||||||
default="manylinux_2_28_x86_64",
|
|
||||||
help=(
|
|
||||||
"Linux compatibility tag (e.g. manylinux_2_28_x86_64) "
|
|
||||||
"see https://python-appimage.readthedocs.io/en/latest/#available-python-appimages "
|
|
||||||
"and https://github.com/pypa/manylinux for a list of valid tags."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
cmdBuildAppImage.add_argument(
|
|
||||||
"--python-version", default="3.13", help="Python version (e.g. 3.13)"
|
|
||||||
)
|
|
||||||
cmdBuildAppImage.set_defaults(func=utils.build_appimage.appImage)
|
cmdBuildAppImage.set_defaults(func=utils.build_appimage.appImage)
|
||||||
|
|
||||||
# Build Windows Inno Setup Installer
|
# Build Windows Inno Setup Installer
|
||||||
|
|||||||
+2
-2
@@ -7,9 +7,9 @@ name = "novelWriter"
|
|||||||
authors = [
|
authors = [
|
||||||
{name = "Veronica Berglyd Olsen", email = "code@vkbo.net"},
|
{name = "Veronica Berglyd Olsen", email = "code@vkbo.net"},
|
||||||
]
|
]
|
||||||
description = "A markdown-like text editor for planning and writing novels"
|
description = "A plain text editor for planning and writing novels"
|
||||||
readme = {file = "setup/description_pypi.md", content-type = "text/markdown"}
|
readme = {file = "setup/description_pypi.md", content-type = "text/markdown"}
|
||||||
license = {text = "GNU General Public License v3"}
|
license = "GPL-3.0"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.7" hexVersion="0x020700f0" fileVersion="1.5" fileRevision="5" timeStamp="2025-05-31 23:25:11">
|
<novelWriterXML appVersion="2.7.1" hexVersion="0x020701f0" fileVersion="1.5" fileRevision="5" timeStamp="2025-06-09 22:54:50">
|
||||||
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2193" autoCount="286" editTime="96887">
|
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2194" autoCount="286" editTime="96892">
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<author>Jane Smith</author>
|
<author>Jane Smith</author>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ cat > $HOME/.local/share/applications/novelWriter.desktop <<EOF
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=novelWriter
|
Name=novelWriter
|
||||||
Comment=A markdown-like text editor for planning and writing novels
|
Comment=A plain text editor for planning and writing novels
|
||||||
Exec=${IMGPATH} %f
|
Exec=${IMGPATH} %f
|
||||||
Icon=${ICONPATH}
|
Icon=${ICONPATH}
|
||||||
Categories=Qt;Office;WordProcessor;
|
Categories=Qt;Office;WordProcessor;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=novelWriter
|
Name=novelWriter
|
||||||
Comment=A markdown-like text editor for planning and writing novels
|
Comment=A plain text editor for planning and writing novels
|
||||||
Exec=novelwriter %f
|
Exec=novelwriter %f
|
||||||
Icon=novelwriter
|
Icon=novelwriter
|
||||||
Categories=Qt;Office;WordProcessor;
|
Categories=Qt;Office;WordProcessor;
|
||||||
|
|||||||
@@ -10,10 +10,4 @@ X-Python3-Version: >= 3.10
|
|||||||
Package: novelwriter
|
Package: novelwriter
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: ${misc:Depends}, ${python3:Depends}, python3 (>=3.10), python3-pyqt6 (>= 6.4), python3-pyqt6.qtsvg (>= 6.4), python3-enchant (>= 2.0)
|
Depends: ${misc:Depends}, ${python3:Depends}, python3 (>=3.10), python3-pyqt6 (>= 6.4), python3-pyqt6.qtsvg (>= 6.4), python3-enchant (>= 2.0)
|
||||||
Description: A markdown-like text editor for planning and writing novels
|
Description: A plain text editor for planning and writing novels
|
||||||
novelWriter is a plain text editor designed for writing novels assembled from
|
|
||||||
many smaller text documents. It uses a minimal formatting syntax inspired by
|
|
||||||
Markdown, and adds a meta data syntax for comments, synopsis, and
|
|
||||||
cross-referencing. It's designed to be a simple text editor that allows for
|
|
||||||
easy organisation of text and notes, using human readable text files as
|
|
||||||
storage for robustness.
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>novelWriter</string>
|
<string>novelWriter</string>
|
||||||
<key>CFBundleGetInfoString</key>
|
<key>CFBundleGetInfoString</key>
|
||||||
<string>novelWriter: A markdown-like text editor for planning and writing novels.</string>
|
<string>novelWriter: A plain text editor for planning and writing novels.</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>novelwriter.icns</string>
|
<string>novelwriter.icns</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ rm lib/python3.*/site-packages/PyQt6/QtWebEngine* || true
|
|||||||
rm -r lib/python3.*/site-packages/PyQt6/Qt6/translations/qtwebengine* || true
|
rm -r lib/python3.*/site-packages/PyQt6/Qt6/translations/qtwebengine* || true
|
||||||
rm -r lib/python3.*/site-packages/PyQt6/Qt6/plugins/webview/libqtwebview* || true
|
rm -r lib/python3.*/site-packages/PyQt6/Qt6/plugins/webview/libqtwebview* || true
|
||||||
|
|
||||||
# Remove unneeded QtQuick/Decaritive components
|
# Remove unneeded QtQuick/Declarative components
|
||||||
rm lib/python3.*/site-packages/PyQt6/QtQml* || true
|
rm lib/python3.*/site-packages/PyQt6/QtQml* || true
|
||||||
rm lib/python3.*/site-packages/PyQt6/QtQuick* || true
|
rm lib/python3.*/site-packages/PyQt6/QtQuick* || true
|
||||||
rm lib/python3.*/site-packages/PyQt6/WebChannel* || true
|
rm lib/python3.*/site-packages/PyQt6/WebChannel* || true
|
||||||
@@ -200,16 +200,16 @@ mkdir -p $RLS_DIR
|
|||||||
# --- Create DMG -------------------------------------------------------------------------------- #
|
# --- Create DMG -------------------------------------------------------------------------------- #
|
||||||
|
|
||||||
# Generate .dmg
|
# Generate .dmg
|
||||||
echo "Packageing DMG ..."
|
echo "Packaging DMG ..."
|
||||||
brew install create-dmg
|
brew install create-dmg
|
||||||
|
|
||||||
create-dmg --volname "novelWriter $VERSION" --volicon $SRC_DIR/setup/macos/novelwriter.icns \
|
create-dmg --volname "novelWriter $VERSION" --volicon $SRC_DIR/setup/macos/novelwriter.icns \
|
||||||
--window-pos 200 120 --window-size 800 400 --icon-size 100 \
|
--window-pos 200 120 --window-size 800 400 --icon-size 100 \
|
||||||
--icon novelWriter.app 200 190 --hide-extension novelWriter.app \
|
--icon novelWriter.app 200 190 --hide-extension novelWriter.app \
|
||||||
--app-drop-link 600 185 $RLS_DIR/novelWriter-"${VERSION}"-$ARCH.dmg "$BUILD_DIR"/
|
--app-drop-link 600 185 $RLS_DIR/novelwriter-"${VERSION}"-$ARCH.dmg "$BUILD_DIR"/
|
||||||
|
|
||||||
pushd $RLS_DIR || exit 1
|
pushd $RLS_DIR || exit 1
|
||||||
shasum -a 256 novelWriter-"${VERSION}"-$ARCH.dmg | tee novelWriter-"${VERSION}"-$ARCH.dmg.sha256
|
shasum -a 256 novelwriter-"${VERSION}"-$ARCH.dmg | tee novelwriter-"${VERSION}"-$ARCH.dmg.sha256
|
||||||
popd || exit 1
|
popd || exit 1
|
||||||
|
|
||||||
rm -r $CONDA_PATH
|
rm -r $CONDA_PATH
|
||||||
|
|||||||
@@ -95,12 +95,35 @@ def testCoreIndex_LoadSave(qtbot, monkeypatch, prjLipsum, nwGUI, tstPaths):
|
|||||||
tagIndex = str(index._tagsIndex.packData())
|
tagIndex = str(index._tagsIndex.packData())
|
||||||
itemsIndex = str(index._itemIndex.packData())
|
itemsIndex = str(index._itemIndex.packData())
|
||||||
|
|
||||||
|
# Update item class
|
||||||
|
bHandle = "4c4f28287af27"
|
||||||
|
bItem = project.tree[bHandle]
|
||||||
|
assert bItem is not None
|
||||||
|
|
||||||
|
tagsBod = index._tagsIndex["Bod"]
|
||||||
|
assert tagsBod is not None
|
||||||
|
assert tagsBod["handle"] == bHandle
|
||||||
|
assert tagsBod["class"] == "CHARACTER"
|
||||||
|
|
||||||
|
bItem.setClass(nwItemClass.CUSTOM)
|
||||||
|
index.refreshHandle(bHandle)
|
||||||
|
assert tagsBod is not None
|
||||||
|
assert tagsBod["handle"] == bHandle
|
||||||
|
assert tagsBod["class"] == "CUSTOM"
|
||||||
|
|
||||||
|
# Update item class to inactive
|
||||||
|
bItem.setClass(nwItemClass.TRASH)
|
||||||
|
index.refreshHandle(bHandle)
|
||||||
|
assert "Bod" not in index._tagsIndex
|
||||||
|
bItem.setClass(nwItemClass.CHARACTER)
|
||||||
|
index.reIndexHandle(bHandle)
|
||||||
|
|
||||||
# Delete a handle
|
# Delete a handle
|
||||||
assert index._tagsIndex["Bod"] is not None
|
assert index._tagsIndex["Bod"] is not None
|
||||||
assert index._itemIndex["4c4f28287af27"] is not None
|
assert index._itemIndex[bHandle] is not None
|
||||||
index.deleteHandle("4c4f28287af27")
|
index.deleteHandle(bHandle)
|
||||||
assert index._tagsIndex["Bod"] is None
|
assert index._tagsIndex["Bod"] is None
|
||||||
assert index._itemIndex["4c4f28287af27"] is None
|
assert index._itemIndex[bHandle] is None
|
||||||
|
|
||||||
# Clear the index
|
# Clear the index
|
||||||
index.clear()
|
index.clear()
|
||||||
@@ -766,11 +789,12 @@ def testCoreIndex_ExtractData(nwGUI, fncPath, mockRnd):
|
|||||||
assert index.getDocumentTags(cHandle) == ["jane"]
|
assert index.getDocumentTags(cHandle) == ["jane"]
|
||||||
assert index.getDocumentTags(None) == []
|
assert index.getDocumentTags(None) == []
|
||||||
|
|
||||||
# getClassTags
|
# getKeyWordTags
|
||||||
# ============
|
# ==============
|
||||||
assert index.getClassTags(None) == ["Jane", "John"]
|
assert index.getKeyWordTags("@mention") == ["Jane", "John"]
|
||||||
assert index.getClassTags(nwItemClass.CHARACTER) == ["Jane", "John"]
|
assert index.getKeyWordTags("@char") == ["Jane", "John"]
|
||||||
assert index.getClassTags(nwItemClass.PLOT) == []
|
assert index.getKeyWordTags("@plot") == []
|
||||||
|
assert index.getKeyWordTags("@tag") == []
|
||||||
|
|
||||||
# getTagsData
|
# getTagsData
|
||||||
# ===========
|
# ===========
|
||||||
|
|||||||
+46
-71
@@ -22,13 +22,16 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import datetime
|
import datetime
|
||||||
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from utils.common import (
|
from utils.common import (
|
||||||
ROOT_DIR, SETUP_DIR, appdataXml, copyPackageFiles, copySourceCode,
|
ROOT_DIR, SETUP_DIR, appdataXml, copyPackageFiles, copySourceCode,
|
||||||
extractVersion, makeCheckSum, toUpload, writeFile
|
extractVersion, freshFolder, makeCheckSum, removeRedundantQt, systemCall,
|
||||||
|
toUpload, writeFile
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -49,115 +52,87 @@ def appImage(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
print("")
|
print("")
|
||||||
print("Build AppImage")
|
print("Build AppImage")
|
||||||
print("==============")
|
print("="*120)
|
||||||
print("")
|
|
||||||
|
|
||||||
linuxTag = args.linux_tag
|
mLinux = args.linux
|
||||||
pythonVer = args.python_version
|
mArch = args.arch
|
||||||
|
pyVer = args.python
|
||||||
|
|
||||||
# Version Info
|
# Version Info
|
||||||
# ============
|
|
||||||
|
|
||||||
pkgVers, _, relDate = extractVersion()
|
pkgVers, _, relDate = extractVersion()
|
||||||
relDate = datetime.datetime.strptime(relDate, "%Y-%m-%d")
|
relDate = datetime.datetime.strptime(relDate, "%Y-%m-%d")
|
||||||
print("")
|
|
||||||
|
|
||||||
# Set Up Folder
|
# Set Up Folder
|
||||||
# =============
|
|
||||||
|
|
||||||
bldDir = ROOT_DIR / "dist_appimage"
|
bldDir = ROOT_DIR / "dist_appimage"
|
||||||
bldPkg = f"novelwriter_{pkgVers}"
|
bldPkg = f"novelwriter-{pkgVers}-{mArch}"
|
||||||
|
bldImg = f"{bldPkg}.AppImage"
|
||||||
outDir = bldDir / bldPkg
|
outDir = bldDir / bldPkg
|
||||||
imgDir = bldDir / "appimage"
|
imgDir = bldDir / "appimage"
|
||||||
|
appDir = bldDir / f"novelWriter-{mArch}"
|
||||||
# Set Up Folders
|
|
||||||
# ==============
|
|
||||||
|
|
||||||
bldDir.mkdir(exist_ok=True)
|
bldDir.mkdir(exist_ok=True)
|
||||||
|
freshFolder(outDir)
|
||||||
if outDir.exists():
|
freshFolder(imgDir)
|
||||||
print("Removing old build files ...")
|
freshFolder(appDir)
|
||||||
print("")
|
|
||||||
shutil.rmtree(outDir)
|
|
||||||
|
|
||||||
outDir.mkdir()
|
|
||||||
|
|
||||||
if imgDir.exists():
|
|
||||||
print("Removing old build metadata files ...")
|
|
||||||
print("")
|
|
||||||
shutil.rmtree(imgDir)
|
|
||||||
|
|
||||||
imgDir.mkdir()
|
|
||||||
|
|
||||||
# Remove old AppImages
|
# Remove old AppImages
|
||||||
if images := bldDir.glob("*.AppImage"):
|
if images := bldDir.glob("*.AppImage"):
|
||||||
print("Removing old AppImages")
|
print("Removing old AppImages")
|
||||||
print("")
|
|
||||||
for image in images:
|
for image in images:
|
||||||
image.unlink()
|
image.unlink()
|
||||||
|
|
||||||
# Copy novelWriter Source
|
# Copy novelWriter Source
|
||||||
# =======================
|
|
||||||
|
|
||||||
print("Copying novelWriter source ...")
|
print("Copying novelWriter source ...")
|
||||||
print("")
|
|
||||||
|
|
||||||
copySourceCode(outDir)
|
copySourceCode(outDir)
|
||||||
|
|
||||||
print("")
|
|
||||||
print("Copying or generating additional files ...")
|
print("Copying or generating additional files ...")
|
||||||
print("")
|
|
||||||
|
|
||||||
copyPackageFiles(outDir)
|
copyPackageFiles(outDir)
|
||||||
|
|
||||||
# Write Metadata
|
# Write Metadata
|
||||||
# ==============
|
|
||||||
|
|
||||||
writeFile(imgDir / "novelwriter.appdata.xml", appdataXml())
|
writeFile(imgDir / "novelwriter.appdata.xml", appdataXml())
|
||||||
print("Wrote: novelwriter.appdata.xml")
|
writeFile(imgDir / "requirements.txt", str(outDir))
|
||||||
|
|
||||||
writeFile(imgDir / "entrypoint.sh", (
|
writeFile(imgDir / "entrypoint.sh", (
|
||||||
'#! /bin/bash \n'
|
# f"export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${{APPDIR}}/usr/lib/{mArch}-linux-gnu/\n"
|
||||||
'{{ python-executable }} -sE ${APPDIR}/opt/python{{ python-version }}/bin/novelwriter "$@"'
|
'{{ python-executable }} -sE ${APPDIR}/opt/python{{ python-version }}/bin/novelwriter "$@"'
|
||||||
))
|
))
|
||||||
print("Wrote: entrypoint.sh")
|
|
||||||
|
|
||||||
writeFile(imgDir / "requirements.txt", str(outDir))
|
|
||||||
print("Wrote: requirements.txt")
|
|
||||||
|
|
||||||
shutil.copyfile(SETUP_DIR / "data" / "novelwriter.desktop", imgDir / "novelwriter.desktop")
|
shutil.copyfile(SETUP_DIR / "data" / "novelwriter.desktop", imgDir / "novelwriter.desktop")
|
||||||
print("Copied: novelwriter.desktop")
|
print("Copied: novelwriter.desktop")
|
||||||
|
|
||||||
shutil.copyfile(SETUP_DIR / "icons" / "novelwriter.svg", imgDir / "novelwriter.svg")
|
shutil.copyfile(SETUP_DIR / "icons" / "novelwriter.png", imgDir / "novelwriter.png")
|
||||||
print("Copied: novelwriter.svg")
|
|
||||||
|
|
||||||
shutil.copyfile(
|
|
||||||
SETUP_DIR / "data" / "hicolor" / "256x256" / "apps" / "novelwriter.png",
|
|
||||||
imgDir / "novelwriter.png"
|
|
||||||
)
|
|
||||||
print("Copied: novelwriter.png")
|
print("Copied: novelwriter.png")
|
||||||
|
|
||||||
# Build AppImage
|
# Build AppDir
|
||||||
# ==============
|
systemCall([
|
||||||
|
sys.executable, "-m", "python_appimage", "build", "app", "--no-packaging",
|
||||||
|
"-l", f"{mLinux}_{mArch}", "-p", pyVer, "appimage"
|
||||||
|
], cwd=bldDir)
|
||||||
|
|
||||||
try:
|
# Copy Libraries
|
||||||
subprocess.call([
|
libPath = Path(f"/usr/lib/{mArch}-linux-gnu")
|
||||||
sys.executable, "-m", "python_appimage", "build", "app",
|
siteDir = appDir / "opt" / f"python{pyVer}" / "lib" / f"python{pyVer}" / "site-packages"
|
||||||
"-l", linuxTag, "-p", pythonVer, "appimage"
|
qt6Lib = siteDir / "PyQt6" / "Qt6" / "lib"
|
||||||
], cwd=bldDir)
|
shutil.copyfile(libPath / "libxcb-cursor.so.0", qt6Lib / "libxcb-cursor.so.0")
|
||||||
except Exception as exc:
|
|
||||||
print("AppImage build: FAILED")
|
|
||||||
print("")
|
|
||||||
print(str(exc))
|
|
||||||
print("")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
bldFile = list(bldDir.glob("*.AppImage"))[0]
|
# Remove Redundant
|
||||||
outFile = bldDir / f"novelWriter-{pkgVers}.AppImage"
|
removeRedundantQt(siteDir)
|
||||||
bldFile.rename(outFile)
|
|
||||||
shaFile = makeCheckSum(outFile.name, cwd=bldDir)
|
|
||||||
|
|
||||||
toUpload(outFile)
|
# Build Image
|
||||||
|
appToolExec = os.environ.get("APPIMAGE_TOOL_EXEC", "appimagetool")
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["ARCH"] = mArch
|
||||||
|
systemCall([
|
||||||
|
appToolExec, "--no-appstream", "--updateinformation",
|
||||||
|
f"gh-releases-zsync|vkbo|novelwriter|latest|novelwriter-*-{mArch}.AppImage.zsync",
|
||||||
|
str(appDir), bldImg
|
||||||
|
], cwd=bldDir, env=env)
|
||||||
|
|
||||||
|
updFile = bldDir / f"{bldImg}.zsync"
|
||||||
|
bldFile = bldDir / bldImg
|
||||||
|
shaFile = makeCheckSum(bldFile.name, cwd=bldDir)
|
||||||
|
|
||||||
|
toUpload(bldFile)
|
||||||
|
toUpload(updFile)
|
||||||
toUpload(shaFile)
|
toUpload(shaFile)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -24,12 +24,11 @@ import argparse
|
|||||||
import datetime
|
import datetime
|
||||||
import email.utils
|
import email.utils
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from utils.common import (
|
from utils.common import (
|
||||||
ROOT_DIR, SETUP_DIR, checkAssetsExist, copyPackageFiles, copySourceCode,
|
ROOT_DIR, SETUP_DIR, checkAssetsExist, copyPackageFiles, copySourceCode,
|
||||||
extractVersion, makeCheckSum, toUpload, writeFile
|
extractVersion, makeCheckSum, systemCall, toUpload, writeFile
|
||||||
)
|
)
|
||||||
|
|
||||||
SIGN_KEY = "D6A9F6B8F227CF7C6F6D1EE84DBBE4B734B0BD08"
|
SIGN_KEY = "D6A9F6B8F227CF7C6F6D1EE84DBBE4B734B0BD08"
|
||||||
@@ -134,10 +133,10 @@ def makeDebianPackage(
|
|||||||
signArgs = [f"-k{signKey}"]
|
signArgs = [f"-k{signKey}"]
|
||||||
|
|
||||||
if sourceBuild:
|
if sourceBuild:
|
||||||
subprocess.call(["debuild", "-S", *signArgs], cwd=outDir)
|
systemCall(["debuild", "-S", *signArgs], cwd=outDir)
|
||||||
toUpload(bldDir / f"{bldPkg}.tar.xz")
|
toUpload(bldDir / f"{bldPkg}.tar.xz")
|
||||||
else:
|
else:
|
||||||
subprocess.call(["dpkg-buildpackage", *signArgs], cwd=outDir)
|
systemCall(["dpkg-buildpackage", *signArgs], cwd=outDir)
|
||||||
shutil.copyfile(bldDir / f"{bldPkg}.tar.xz", bldDir / f"{bldPkg}.debian.tar.xz")
|
shutil.copyfile(bldDir / f"{bldPkg}.tar.xz", bldDir / f"{bldPkg}.debian.tar.xz")
|
||||||
toUpload(bldDir / f"{bldPkg}.debian.tar.xz")
|
toUpload(bldDir / f"{bldPkg}.debian.tar.xz")
|
||||||
toUpload(bldDir / f"{bldPkg}_all.deb")
|
toUpload(bldDir / f"{bldPkg}_all.deb")
|
||||||
@@ -185,6 +184,7 @@ def launchpad(args: argparse.Namespace) -> None:
|
|||||||
("24.04", "noble"),
|
("24.04", "noble"),
|
||||||
("24.10", "oracular"),
|
("24.10", "oracular"),
|
||||||
("25.04", "plucky"),
|
("25.04", "plucky"),
|
||||||
|
("25.10", "questing"),
|
||||||
]
|
]
|
||||||
|
|
||||||
print("Building Ubuntu packages for:")
|
print("Building Ubuntu packages for:")
|
||||||
|
|||||||
+8
-99
@@ -23,14 +23,16 @@ from __future__ import annotations
|
|||||||
import argparse
|
import argparse
|
||||||
import compileall
|
import compileall
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from utils.common import ROOT_DIR, SETUP_DIR, copySourceCode, extractVersion, readFile, writeFile
|
from utils.common import (
|
||||||
|
ROOT_DIR, SETUP_DIR, copySourceCode, extractVersion, readFile,
|
||||||
|
removeRedundantQt, systemCall, writeFile
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def prepareCode(outDir: Path) -> None:
|
def prepareCode(outDir: Path) -> None:
|
||||||
@@ -86,99 +88,11 @@ def embedPython(bldDir: Path, outDir: Path) -> None:
|
|||||||
def installRequirements(libDir: Path) -> None:
|
def installRequirements(libDir: Path) -> None:
|
||||||
"""Install dependencies."""
|
"""Install dependencies."""
|
||||||
print("Install dependencies ...")
|
print("Install dependencies ...")
|
||||||
|
systemCall([
|
||||||
try:
|
sys.executable, "-m", "pip", "install", "-r", "requirements.txt", "--target", libDir
|
||||||
subprocess.call([
|
])
|
||||||
sys.executable, "-m",
|
|
||||||
"pip", "install", "-r", "requirements.txt", "--target", str(libDir)
|
|
||||||
])
|
|
||||||
except Exception as exc:
|
|
||||||
print("Failed with error:")
|
|
||||||
print(str(exc))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print("Done")
|
print("Done")
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def removeRedundantQt(libDir: Path) -> None:
|
|
||||||
"""Delete Qt files that are not needed"""
|
|
||||||
|
|
||||||
def unlinkIfFound(file: Path) -> None:
|
|
||||||
if file.is_file():
|
|
||||||
file.unlink()
|
|
||||||
print(f"Deleted: {file}")
|
|
||||||
|
|
||||||
def deleteFolder(folder: Path) -> None:
|
|
||||||
if folder.is_dir():
|
|
||||||
shutil.rmtree(folder)
|
|
||||||
print(f"Deleted: {folder}")
|
|
||||||
|
|
||||||
def unlinkIfPrefix(folder: Path, prefix: tuple[str, ...]) -> None:
|
|
||||||
if folder.is_dir():
|
|
||||||
for item in folder.iterdir():
|
|
||||||
if item.name.startswith(prefix):
|
|
||||||
if item.is_file():
|
|
||||||
unlinkIfFound(item)
|
|
||||||
elif item.is_dir():
|
|
||||||
deleteFolder(item)
|
|
||||||
|
|
||||||
print("Deleting Redundant Files")
|
|
||||||
print("========================")
|
|
||||||
print("")
|
|
||||||
|
|
||||||
pyQt6Dir = libDir / "PyQt6"
|
|
||||||
bindDir = libDir / "PyQt6" / "bindings"
|
|
||||||
qt6Dir = libDir / "PyQt6" / "Qt6"
|
|
||||||
binDir = libDir / "PyQt6" / "Qt6" / "bin"
|
|
||||||
plugDir = libDir / "PyQt6" / "Qt6" / "plugins"
|
|
||||||
qmDir = libDir / "PyQt6" / "Qt6" / "translations"
|
|
||||||
dictDir = libDir / "enchant" / "data" / "mingw64" / "share" / "enchant" / "hunspell"
|
|
||||||
|
|
||||||
for item in dictDir.iterdir():
|
|
||||||
if not item.name.startswith(("en_GB", "en_US")):
|
|
||||||
unlinkIfFound(item)
|
|
||||||
|
|
||||||
for item in qmDir.iterdir():
|
|
||||||
if not item.name.startswith("qtbase"):
|
|
||||||
unlinkIfFound(item)
|
|
||||||
|
|
||||||
bulkDel = ("QtQml", "Qt6Qml", "QtQuick", "Qt6Quick")
|
|
||||||
unlinkIfPrefix(pyQt6Dir, bulkDel)
|
|
||||||
unlinkIfPrefix(bindDir, bulkDel)
|
|
||||||
unlinkIfPrefix(binDir, bulkDel)
|
|
||||||
|
|
||||||
delQt6 = [
|
|
||||||
"Qt6Bluetooth", "Qt6DBus", "Qt6Designer", "Qt6Help", "Qt6Multimedia",
|
|
||||||
"Qt6MultimediaWidgets", "Qt6Network", "Qt6Nfc", "Qt6OpenGL", "Qt6Positioning",
|
|
||||||
"Qt6PositioningQuick", "Qt6Sensors", "Qt6SerialPort", "Qt6Sql", "Qt6Test",
|
|
||||||
"Qt6TextToSpeech", "Qt6WebChannel", "Qt6WebSockets", "Qt6Xml",
|
|
||||||
]
|
|
||||||
for item in delQt6:
|
|
||||||
qtItem = item.replace("Qt6", "Qt")
|
|
||||||
unlinkIfFound(binDir / f"{item}.dll")
|
|
||||||
unlinkIfFound(pyQt6Dir / f"{qtItem}.pyd")
|
|
||||||
unlinkIfFound(pyQt6Dir / f"{qtItem}.pyi")
|
|
||||||
deleteFolder(bindDir / qtItem)
|
|
||||||
|
|
||||||
delList = [
|
|
||||||
binDir / "opengl32sw.dll",
|
|
||||||
qt6Dir / "qml",
|
|
||||||
plugDir / "renderers",
|
|
||||||
plugDir / "sensors",
|
|
||||||
plugDir / "sqldrivers",
|
|
||||||
plugDir / "texttospeech",
|
|
||||||
plugDir / "webview",
|
|
||||||
]
|
|
||||||
for item in delList:
|
|
||||||
unlinkIfFound(item)
|
|
||||||
deleteFolder(item)
|
|
||||||
|
|
||||||
print("Done")
|
|
||||||
print("")
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -238,12 +152,7 @@ def main(args: argparse.Namespace) -> None:
|
|||||||
writeFile(ROOT_DIR / "setup.iss", issData)
|
writeFile(ROOT_DIR / "setup.iss", issData)
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
try:
|
systemCall(["iscc", "setup.iss"])
|
||||||
subprocess.call(["iscc", "setup.iss"])
|
|
||||||
except Exception as exc:
|
|
||||||
print("Inno Setup failed with error:")
|
|
||||||
print(str(exc))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print("")
|
print("")
|
||||||
print("Done")
|
print("Done")
|
||||||
|
|||||||
+104
-17
@@ -22,6 +22,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -50,11 +51,11 @@ def extractVersion(beQuiet: bool = False) -> tuple[str, str, str]:
|
|||||||
if aLine.startswith("__date__"):
|
if aLine.startswith("__date__"):
|
||||||
relDate = getValue(aLine)
|
relDate = getValue(aLine)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f"Could not read file: {initFile}")
|
print(f"Could not read file: {initFile}", flush=True)
|
||||||
print(str(exc))
|
print(str(exc), flush=True)
|
||||||
|
|
||||||
if not beQuiet:
|
if not beQuiet:
|
||||||
print(f"novelWriter version: {numVers} ({hexVers}) at {relDate}")
|
print(f"novelWriter version: {numVers} ({hexVers}) at {relDate}", flush=True)
|
||||||
|
|
||||||
return numVers, hexVers, relDate
|
return numVers, hexVers, relDate
|
||||||
|
|
||||||
@@ -77,16 +78,16 @@ def copySourceCode(dst: Path) -> None:
|
|||||||
for item in src.glob("**/*"):
|
for item in src.glob("**/*"):
|
||||||
relSrc = item.relative_to(ROOT_DIR)
|
relSrc = item.relative_to(ROOT_DIR)
|
||||||
if item.suffix in (".pyc", ".pyo"):
|
if item.suffix in (".pyc", ".pyo"):
|
||||||
print(f"Ignore: {relSrc}")
|
print("Ignored:", relSrc, flush=True)
|
||||||
continue
|
continue
|
||||||
if item.parent.is_dir() and item.parent.name != "__pycache__":
|
if item.parent.is_dir() and item.parent.name != "__pycache__":
|
||||||
dstDir = dst / relSrc.parent
|
dstDir = dst / relSrc.parent
|
||||||
if not dstDir.exists():
|
if not dstDir.exists():
|
||||||
dstDir.mkdir(parents=True)
|
dstDir.mkdir(parents=True)
|
||||||
print(f"Folder: {dstDir}")
|
print("Created:", dstDir.relative_to(ROOT_DIR), flush=True)
|
||||||
if item.is_file():
|
if item.is_file():
|
||||||
shutil.copyfile(item, dst / relSrc)
|
shutil.copyfile(item, dst / relSrc)
|
||||||
print(f"Copied: {dst / relSrc}")
|
print("Copied:", relSrc, flush=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -95,26 +96,23 @@ def copyPackageFiles(dst: Path, setupPy: bool = False) -> None:
|
|||||||
copyFiles = ["LICENSE.md", "CREDITS.md", "pyproject.toml"]
|
copyFiles = ["LICENSE.md", "CREDITS.md", "pyproject.toml"]
|
||||||
for copyFile in copyFiles:
|
for copyFile in copyFiles:
|
||||||
shutil.copyfile(copyFile, dst / copyFile)
|
shutil.copyfile(copyFile, dst / copyFile)
|
||||||
print(f"Copied: {copyFile}")
|
print("Copied:", copyFile, flush=True)
|
||||||
|
|
||||||
writeFile(dst / "MANIFEST.in", (
|
writeFile(dst / "MANIFEST.in", (
|
||||||
"include LICENSE.md\n"
|
"include LICENSE.md\n"
|
||||||
"include CREDITS.md\n"
|
"include CREDITS.md\n"
|
||||||
"recursive-include novelwriter/assets *\n"
|
"recursive-include novelwriter/assets *\n"
|
||||||
))
|
))
|
||||||
print("Wrote: MANIFEST.in")
|
|
||||||
|
|
||||||
if setupPy:
|
if setupPy:
|
||||||
writeFile(dst / "setup.py", (
|
writeFile(dst / "setup.py", (
|
||||||
"import setuptools\n"
|
"import setuptools\n"
|
||||||
"setuptools.setup()\n"
|
"setuptools.setup()\n"
|
||||||
))
|
))
|
||||||
print("Wrote: setup.py")
|
|
||||||
|
|
||||||
text = readFile(ROOT_DIR / "pyproject.toml")
|
text = readFile(ROOT_DIR / "pyproject.toml")
|
||||||
text = text.replace("setup/description_pypi.md", "data/description_short.txt")
|
text = text.replace("setup/description_pypi.md", "data/description_short.txt")
|
||||||
writeFile(dst / "pyproject.toml", text)
|
writeFile(dst / "pyproject.toml", text)
|
||||||
print("Wrote: pyproject.toml")
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -139,10 +137,10 @@ def makeCheckSum(sumFile: str, cwd: Path | None = None) -> str:
|
|||||||
shaFile = cwd / f"{sumFile}.sha256"
|
shaFile = cwd / f"{sumFile}.sha256"
|
||||||
with open(shaFile, mode="w", encoding="utf-8") as fOut:
|
with open(shaFile, mode="w", encoding="utf-8") as fOut:
|
||||||
subprocess.call(["shasum", "-a", "256", sumFile], stdout=fOut, cwd=cwd)
|
subprocess.call(["shasum", "-a", "256", sumFile], stdout=fOut, cwd=cwd)
|
||||||
print(f"SHA256 Sum: {shaFile}")
|
print(f"SHA256 Sum: {shaFile}", flush=True)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print("Could not generate sha256 file")
|
print("Could not generate sha256 file", flush=True)
|
||||||
print(str(exc))
|
print(str(exc), flush=True)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
return str(shaFile)
|
return str(shaFile)
|
||||||
@@ -156,17 +154,17 @@ def checkAssetsExist() -> bool:
|
|||||||
|
|
||||||
sampleZip = ROOT_DIR / "novelwriter" / "assets" / "sample.zip"
|
sampleZip = ROOT_DIR / "novelwriter" / "assets" / "sample.zip"
|
||||||
if sampleZip.is_file():
|
if sampleZip.is_file():
|
||||||
print(f"Found: {sampleZip}")
|
print(f"Found: {sampleZip}", flush=True)
|
||||||
hasSample = True
|
hasSample = True
|
||||||
|
|
||||||
pdfManual = ROOT_DIR / "novelwriter" / "assets" / "manual.pdf"
|
pdfManual = ROOT_DIR / "novelwriter" / "assets" / "manual.pdf"
|
||||||
if pdfManual.is_file():
|
if pdfManual.is_file():
|
||||||
print(f"Found: {pdfManual}")
|
print(f"Found: {pdfManual}", flush=True)
|
||||||
hasManual = True
|
hasManual = True
|
||||||
|
|
||||||
i18nAssets = ROOT_DIR / "novelwriter" / "assets" / "i18n"
|
i18nAssets = ROOT_DIR / "novelwriter" / "assets" / "i18n"
|
||||||
if len(list(i18nAssets.glob("*.qm"))) > 0:
|
if len(list(i18nAssets.glob("*.qm"))) > 0:
|
||||||
print(f"Found: {i18nAssets}/*.qm")
|
print(f"Found: {i18nAssets}/*.qm", flush=True)
|
||||||
hasQmData = True
|
hasQmData = True
|
||||||
|
|
||||||
return hasSample and hasManual and hasQmData
|
return hasSample and hasManual and hasQmData
|
||||||
@@ -188,4 +186,93 @@ def readFile(file: Path) -> str:
|
|||||||
|
|
||||||
def writeFile(file: Path, text: str) -> int:
|
def writeFile(file: Path, text: str) -> int:
|
||||||
"""Write string to file."""
|
"""Write string to file."""
|
||||||
return file.write_text(text, encoding="utf-8")
|
result = file.write_text(text, encoding="utf-8")
|
||||||
|
print("Wrote:", file.relative_to(ROOT_DIR), flush=True)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def freshFolder(path: Path) -> None:
|
||||||
|
"""Make sure a folder exists and is empty."""
|
||||||
|
if path.exists():
|
||||||
|
print("Removing:", str(path), flush=True)
|
||||||
|
shutil.rmtree(path)
|
||||||
|
path.mkdir()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def systemCall(cmd: list, cwd: Path | str | None = None, env: dict | None = None) -> int:
|
||||||
|
"""Make a system call using subprocess."""
|
||||||
|
if isinstance(cwd, Path):
|
||||||
|
cwd = str(cwd)
|
||||||
|
try:
|
||||||
|
code = subprocess.call([str(c) for c in cmd], cwd=cwd, env=env)
|
||||||
|
except Exception as exc:
|
||||||
|
print("ERROR:", str(exc), flush=True)
|
||||||
|
sys.exit(1)
|
||||||
|
return code
|
||||||
|
|
||||||
|
|
||||||
|
def removeRedundantQt(qtBase: Path) -> None:
|
||||||
|
"""Delete Qt files that are not needed"""
|
||||||
|
|
||||||
|
def unlinkIfFound(file: Path) -> None:
|
||||||
|
if file.is_file():
|
||||||
|
file.unlink()
|
||||||
|
print("Deleted:", file.relative_to(ROOT_DIR), flush=True)
|
||||||
|
|
||||||
|
def deleteFolder(folder: Path) -> None:
|
||||||
|
if folder.is_dir():
|
||||||
|
shutil.rmtree(folder)
|
||||||
|
print("Deleted:", folder.relative_to(ROOT_DIR), flush=True)
|
||||||
|
|
||||||
|
def unlinkIfPrefix(folder: Path, prefix: tuple[str, ...]) -> None:
|
||||||
|
if folder.is_dir():
|
||||||
|
for item in folder.iterdir():
|
||||||
|
if item.name.startswith(prefix):
|
||||||
|
if item.is_file():
|
||||||
|
unlinkIfFound(item)
|
||||||
|
elif item.is_dir():
|
||||||
|
deleteFolder(item)
|
||||||
|
|
||||||
|
print("Deleting redundant files ...")
|
||||||
|
|
||||||
|
pyQt6Dir = qtBase / "PyQt6"
|
||||||
|
bindDir = qtBase / "PyQt6" / "bindings"
|
||||||
|
qt6Dir = qtBase / "PyQt6" / "Qt6"
|
||||||
|
binDir = qtBase / "PyQt6" / "Qt6" / "bin"
|
||||||
|
libDir = qtBase / "PyQt6" / "Qt6" / "lib"
|
||||||
|
plugDir = qtBase / "PyQt6" / "Qt6" / "plugins"
|
||||||
|
qmDir = qtBase / "PyQt6" / "Qt6" / "translations"
|
||||||
|
dictDir = qtBase / "enchant" / "data" / "mingw64" / "share" / "enchant" / "hunspell"
|
||||||
|
|
||||||
|
# Prune Dictionaries
|
||||||
|
if dictDir.exists():
|
||||||
|
for item in dictDir.iterdir():
|
||||||
|
if not item.name.startswith(("en_GB", "en_US")):
|
||||||
|
unlinkIfFound(item)
|
||||||
|
|
||||||
|
# Prune Translations
|
||||||
|
for item in qmDir.iterdir():
|
||||||
|
if not item.name.startswith("qtbase"):
|
||||||
|
unlinkIfFound(item)
|
||||||
|
|
||||||
|
# Delete Modules
|
||||||
|
modules = [
|
||||||
|
"Qt6Qml", "Qt6Quick", "Qt6Bluetooth", "Qt6Nfc",
|
||||||
|
"Qt6Sensors", "Qt6SerialPort", "Qt6Test",
|
||||||
|
]
|
||||||
|
modules.extend([x.replace("Qt6", "Qt") for x in modules])
|
||||||
|
modules.extend([f"lib{x}" for x in modules])
|
||||||
|
modules = tuple(modules)
|
||||||
|
|
||||||
|
unlinkIfPrefix(pyQt6Dir, modules)
|
||||||
|
unlinkIfPrefix(bindDir, modules)
|
||||||
|
unlinkIfPrefix(binDir, modules)
|
||||||
|
unlinkIfPrefix(libDir, modules)
|
||||||
|
|
||||||
|
# Other Files
|
||||||
|
deleteFolder(qt6Dir / "qml")
|
||||||
|
deleteFolder(plugDir / "qmlls")
|
||||||
|
deleteFolder(plugDir / "qmllint")
|
||||||
|
|
||||||
|
return
|
||||||
|
|||||||
+3
-3
@@ -25,7 +25,7 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from utils.common import ROOT_DIR
|
from utils.common import ROOT_DIR, systemCall
|
||||||
|
|
||||||
|
|
||||||
def updateDocsTranslationSources(args: argparse.Namespace) -> None:
|
def updateDocsTranslationSources(args: argparse.Namespace) -> None:
|
||||||
@@ -40,7 +40,7 @@ def updateDocsTranslationSources(args: argparse.Namespace) -> None:
|
|||||||
locsDir.mkdir(exist_ok=True)
|
locsDir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
print("Generating POT Files")
|
print("Generating POT Files")
|
||||||
subprocess.call(["make", "gettext"], cwd=docsDir)
|
systemCall(["make", "gettext"], cwd=docsDir)
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
lang = args.lang
|
lang = args.lang
|
||||||
@@ -55,7 +55,7 @@ def updateDocsTranslationSources(args: argparse.Namespace) -> None:
|
|||||||
print("")
|
print("")
|
||||||
|
|
||||||
for code in update:
|
for code in update:
|
||||||
subprocess.call(["sphinx-intl", "update", "-p", "build/gettext", "-l", code], cwd=docsDir)
|
systemCall(["sphinx-intl", "update", "-p", "build/gettext", "-l", code], cwd=docsDir)
|
||||||
print("")
|
print("")
|
||||||
|
|
||||||
print("Done")
|
print("Done")
|
||||||
|
|||||||
Reference in New Issue
Block a user