diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index abd8b319..1ddeaed1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,6 +12,8 @@ just make a pull request directly. * Bugfixes for new or existing bugs. Please also report new bugs in the issue tracker even if you also provide a fix. It makes it easier to keep track of what has been fixed and when. * Translations made via the [Crowdin project page](https://crowdin.com/project/novelwriter). +* Translations of the documentation. These need to use Sphinx i18n tooling. Please start a + discussion before beginning such work as it requires some coordination. * Improvements to the documentation. Particularly if the documentation is unclear. Please don't make any larger changes to the documentation without discussing them with the maintainer first. * Adaptations, installation or packaging features targeting specific operating systems. @@ -21,6 +23,34 @@ just make a pull request directly. * Make a pull request that restructures or reformats existing code. If you think some part of the code could be improved, please make an issue thread or start a discussion. The same applies to any text document in the repository. +* Make pull requests with AI generated code. This is not a project suitable for vibe coding. + Outright slop will result in the account being blocked. + +This project uses [uv](https://docs.astral.sh/uv/) as its main developer tool. In order to run +novelWriter directly from checked out source, simply call from the root folder: + +```bash +uv run novelwriter +``` + +Many tasks like building assets from source are handled by the `pkgutils.py` helper tool. + +```bash +uv run pkgutils.py --help +``` + +The translation files needed at runtime can be built with: + +```bash +uv run pkgutils.py qtlrelease +``` + +Material design icons are included with the source. Optional icon themes can be built with: + +```bash +uv run pkgutils.py icons optional +``` + ## Picking the Correct Branch for a Pull Request @@ -33,6 +63,11 @@ New features are only accepted on full releases, so a feature pull request must `main` branch. However, if the `main` branch is very close to a new full release, pull requests may not be merged until the release is completed. +This project uses GitHub milestones to plan releases, and only pull requests included in the +current release cycle will be merged to `main`. Milestone tickets are not set in stone and are +often moved between them. + + ## Pull Request Check List Make sure the pull request follows these rules: @@ -41,32 +76,45 @@ Make sure the pull request follows these rules: own fork from the current `main` branch. Do not make pull requests from your copy of the `main` branch. * Please provide a description of the changes in the pull request under the summary section of the - pull request template, and reference any related issues by providing the issue number. + pull request template, and reference any related issues by providing the issue number. Do not + post links to issue numbers as that breaks the integration. Stating the issue number is enough. * Do not change the version number. * Do not submit files that were not actively changed but have otherwise been modified. This is - mostly an issue with translation files. The language tool may update all files in the `i18n` - folder. + particularly an issue with autoformatting. + ## General Rules These are the guidelines for the project. The source code of novelWriter broadly follows the [PEP8](https://www.python.org/dev/peps/pep-0008) style guide, but with a few exceptions. +The project uses [ruff](https://docs.astral.sh/ruff/) for linting, but the auto-formatter should +not be used at this point. It also uses [isort](https://pycqa.github.io/isort) for import sorting. +The latter can be auto-formatted and the settings are defined in ``pyproject.toml`. + + ### Tests * New code must not break any existing tests. * New code must come with tests that cover the code in full. If the code has branches that only - runs on some OSes, they must be covered when test are run on that OS. The test suite runs on - Linux, Windows and MacOS. + runs on some OSes, they only need to be covered when test are run on that OS. The test suite runs + on Linux, Windows and MacOS. + +A helper script is provided for running tests. It simplifies coverage reporting and a few other +things. Run the following to see all details: + +```bash +uv run run_tests.py --help +``` + ### Code Formatting -* Do not run automatic formatting tools like `black` or `ruff` on the code. Auto-formatting using - `ruff` is planned, but there are a couple of features missing in it, so it is currently only used - for linting. Auto-formatting with `isort` is configured in `pyproject.toml` and can be used. -* The pull request code *must* pass the `ruff` linting rules specified in `pyproject.toml`. +* The pull request code *must* pass the `ruff` and `isort` linting rules specified in + `pyproject.toml`. * In general, do not make large scale formatting changes to the code. + ### Type Annotations * All functions and parameters must be type annotated, and so must variables and attributes if the @@ -77,6 +125,7 @@ These are the guidelines for the project. The source code of novelWriter broadly * Do not use deprecated capitalised annotations like `Dict`, `List`, `Tuple`, etc. * Type annotated code must be runnable on all supported Python versions. + ### Internationalisation * All comments and docstrings in the code must be in English. @@ -84,6 +133,7 @@ These are the guidelines for the project. The source code of novelWriter broadly spelling of this text *must* be UK English. US English spelling is not allowed for these strings. * Commit descriptions and pull requests must also be in English. + ### Line Length * Source code lines can extend to the upper limit of 99 characters. Generally, if a code statement @@ -92,6 +142,7 @@ These are the guidelines for the project. The source code of novelWriter broadly * For text files, the text should be wrapped at 99 character. The exception is Markdown image tags and URLs which can run past that limit. + ### Spaces, Indentation and Alignment * Only indentation by multiples of 4 spaces is allowed. @@ -101,9 +152,11 @@ These are the guidelines for the project. The source code of novelWriter broadly rule is relaxed a bit here. Alignment is allowed when populating large dictionaries or setting many class attributes. It does improve readability in such cases, but should not be overused. + ### General Code Rules * Use f-string style for string formatting as the first choice, and `.format` functions if there is a good reason for it. Do not use `%` style formatting except for logging output. For logging, `%` must be used (it's a limitation in the logging library unfortunately). -* Functions should be on camelCase form for consistency with the Qt library code. +* Functions should be on camelCase form for consistency with the Qt library code. This also goes + for variable names for the sake of internal consistency. diff --git a/README.md b/README.md index bb2cd3e9..fd83fdc1 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ documentation. * PyPi Project: [pypi.org/project/novelWriter](https://pypi.org/project/novelWriter) * Social Media: [fosstodon.org/@novelwriter](https://fosstodon.org/@novelwriter) + ## Sponsors
+
+## Working With the Source
+
+This project uses [uv](https://docs.astral.sh/uv/) as its main developer tool. That means the
+`pyproject.toml` file handles almost everything aside from a few OS-specific packaging tasks.
+
+In order to run novelWriter directly from checked out source, simply call from the root folder:
+
+```bash
+uv run novelwriter
+```
+
## Project Contributions
Please don't make feature pull requests without first having discussed them with the maintainer.
You can make a feature request in the [issues tracker](https://github.com/vkbo/novelWriter/issues),
or if the idea isn't fully formed, start a [discussion](https://github.com/vkbo/novelWriter/discussions).
-Please also don't make pull requests to reformat or rewrite existing code unless there is a very good reason for doing so.
+Please also don't make pull requests to reformat or rewrite existing code unless there is a very
+good reason for doing so.
Fixes and patches are welcome. Contributions related to packaging and installing novelWriter will
also be appreciated, but please make an issue or a discussion topic first. Before contributing any
@@ -66,6 +81,7 @@ Project credits are available in [CREDITS.md](https://github.com/vkbo/novelWrite
the `release` branch. So if you're submitting a fix to a current release, **including changes to
documentation**, they must be made to the `release` branch.
+
### Translations
New translations are always welcome. This project uses Crowdin to maintain translations, and you
@@ -73,6 +89,7 @@ can contribute translations at the [Crowdin project page](https://crowdin.com/pr
If you have any questions, feel free to post them to the
[Translations of novelWriter](https://github.com/vkbo/novelWriter/issues/93) issue thread.
+
## Licence
This is Open Source software, and novelWriter is licenced under GPLv3. See the
diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py
index 080b3b21..a8898fa0 100644
--- a/novelwriter/dialogs/about.py
+++ b/novelwriter/dialogs/about.py
@@ -37,7 +37,7 @@ from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.versioninfo import VersionInfoWidget
-from novelwriter.types import QtAlignRightTop, QtHexArgb, QtRoleReject
+from novelwriter.types import QtAlignRightTop, QtHexArgb, QtRoleDestruct
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
@@ -87,7 +87,7 @@ class GuiAbout(NDialog):
self.btnClose.clicked.connect(self.reject)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnClose, QtRoleReject)
+ self.btnBox.addButton(self.btnClose, QtRoleDestruct)
# Assemble
self.innerBox = QVBoxLayout()
diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py
index 1a05e187..a2f60b91 100644
--- a/novelwriter/dialogs/wordlist.py
+++ b/novelwriter/dialogs/wordlist.py
@@ -40,7 +40,7 @@ from novelwriter.core.spellcheck import UserDictionary
from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import NColorLabel
from novelwriter.extensions.modified import NDialog, NIconToolButton
-from novelwriter.types import QtRoleAccept, QtRoleReject
+from novelwriter.types import QtRoleAccept, QtRoleDestruct
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
@@ -119,7 +119,7 @@ class GuiWordList(NDialog):
self.btnBox = QDialogButtonBox(self)
self.btnBox.addButton(self.btnSave, QtRoleAccept)
- self.btnBox.addButton(self.btnClose, QtRoleReject)
+ self.btnBox.addButton(self.btnClose, QtRoleDestruct)
# Assemble
self.outerBox = QVBoxLayout()
diff --git a/novelwriter/tools/dictionaries.py b/novelwriter/tools/dictionaries.py
index cf90d65a..bd19b78d 100644
--- a/novelwriter/tools/dictionaries.py
+++ b/novelwriter/tools/dictionaries.py
@@ -40,7 +40,7 @@ from novelwriter.common import formatFileFilter, formatInt, getFileSize, openExt
from novelwriter.enum import nwStandardButton
from novelwriter.error import formatException
from novelwriter.extensions.modified import NIconToolButton, NNonBlockingDialog
-from novelwriter.types import QtHexArgb, QtRoleReject
+from novelwriter.types import QtHexArgb, QtRoleDestruct
logger = logging.getLogger(__name__)
@@ -115,7 +115,7 @@ class GuiDictionaries(NNonBlockingDialog):
self.btnClose.clicked.connect(self.reject)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnClose, QtRoleReject)
+ self.btnBox.addButton(self.btnClose, QtRoleDestruct)
# Assemble
self.outerBox = QVBoxLayout()
diff --git a/novelwriter/tools/lipsum.py b/novelwriter/tools/lipsum.py
index a970fe93..3cd2fea7 100644
--- a/novelwriter/tools/lipsum.py
+++ b/novelwriter/tools/lipsum.py
@@ -37,7 +37,7 @@ from novelwriter.common import readTextFile
from novelwriter.enum import nwStandardButton
from novelwriter.extensions.modified import NDialog
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAlignLeft, QtAlignRight, QtRoleApply, QtRoleReject
+from novelwriter.types import QtAlignLeft, QtAlignRight, QtRoleApply, QtRoleDestruct
logger = logging.getLogger(__name__)
@@ -102,7 +102,7 @@ class GuiLipsum(NDialog):
self.btnBox = QDialogButtonBox(self)
self.btnBox.addButton(self.btnInsert, QtRoleApply)
- self.btnBox.addButton(self.btnClose, QtRoleReject)
+ self.btnBox.addButton(self.btnClose, QtRoleDestruct)
# Assemble
self.outerBox = QVBoxLayout()
diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py
index c77898d7..ffbef09d 100644
--- a/novelwriter/tools/manusbuild.py
+++ b/novelwriter/tools/manusbuild.py
@@ -43,7 +43,7 @@ from novelwriter.core.item import NWItem
from novelwriter.enum import nwBuildFmt, nwStandardButton
from novelwriter.extensions.modified import NDialog, NIconToolButton, NPushButton
from novelwriter.extensions.progressbars import NProgressSimple
-from novelwriter.types import QtAlignCenter, QtRoleAction, QtRoleReject, QtUserRole
+from novelwriter.types import QtAlignCenter, QtRoleAction, QtRoleDestruct, QtRoleReject, QtUserRole
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
@@ -190,7 +190,7 @@ class GuiManuscriptBuild(NDialog):
self.btnBox = QDialogButtonBox(self)
self.btnBox.addButton(self.btnOpen, QtRoleAction)
self.btnBox.addButton(self.btnBuild, QtRoleAction)
- self.btnBox.addButton(self.btnClose, QtRoleReject)
+ self.btnBox.addButton(self.btnClose, QtRoleDestruct)
# Assemble GUI
# ============
diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py
index dd5bc50c..7f00f6ff 100644
--- a/novelwriter/tools/manussettings.py
+++ b/novelwriter/tools/manussettings.py
@@ -52,7 +52,7 @@ from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.switchbox import NSwitchBox
from novelwriter.types import (
QtAlignCenter, QtAlignLeft, QtHeaderFixed, QtHeaderStretch, QtRoleAccept,
- QtRoleApply, QtRoleReject, QtUserRole
+ QtRoleApply, QtRoleDestruct, QtRoleReject, QtUserRole
)
if TYPE_CHECKING:
@@ -132,7 +132,7 @@ class GuiBuildSettings(NToolDialog):
self.btnBox = QDialogButtonBox(self)
self.btnBox.addButton(self.btnApply, QtRoleApply)
self.btnBox.addButton(self.btnSave, QtRoleAccept)
- self.btnBox.addButton(self.btnClose, QtRoleReject)
+ self.btnBox.addButton(self.btnClose, QtRoleDestruct)
self.btnBox.clicked.connect(self._dialogButtonClicked)
# Assemble
diff --git a/novelwriter/tools/noveldetails.py b/novelwriter/tools/noveldetails.py
index c0314e9b..24b7a9d4 100644
--- a/novelwriter/tools/noveldetails.py
+++ b/novelwriter/tools/noveldetails.py
@@ -44,7 +44,7 @@ from novelwriter.extensions.modified import NNonBlockingDialog
from novelwriter.extensions.novelselector import NovelSelector
from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
-from novelwriter.types import QtAlignRight, QtDecoration, QtRoleReject
+from novelwriter.types import QtAlignRight, QtDecoration, QtRoleDestruct
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
@@ -107,7 +107,7 @@ class GuiNovelDetails(NNonBlockingDialog):
self.btnClose.clicked.connect(self.reject)
self.btnBox = QDialogButtonBox(self)
- self.btnBox.addButton(self.btnClose, QtRoleReject)
+ self.btnBox.addButton(self.btnClose, QtRoleDestruct)
# Assemble
self.topBox = QHBoxLayout()
diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py
index dcf3ce40..fea44864 100644
--- a/novelwriter/tools/writingstats.py
+++ b/novelwriter/tools/writingstats.py
@@ -45,7 +45,7 @@ from novelwriter.extensions.modified import NPushButton, NToolDialog
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import (
QtAlignLeftMiddle, QtAlignRight, QtAlignRightMiddle, QtDecoration,
- QtRoleAction, QtRoleReject
+ QtRoleAction, QtRoleDestruct
)
if TYPE_CHECKING:
@@ -298,7 +298,7 @@ class GuiWritingStats(NToolDialog):
self.btnBox = QDialogButtonBox(self)
self.btnBox.addButton(self.btnSave, QtRoleAction)
- self.btnBox.addButton(self.btnClose, QtRoleReject)
+ self.btnBox.addButton(self.btnClose, QtRoleDestruct)
# Assemble
self.outerBox = QGridLayout()
diff --git a/novelwriter/types.py b/novelwriter/types.py
index 52a18351..658e4ed0 100644
--- a/novelwriter/types.py
+++ b/novelwriter/types.py
@@ -96,6 +96,7 @@ QtRejected = QDialog.DialogCode.Rejected
QtRoleAccept = QDialogButtonBox.ButtonRole.AcceptRole
QtRoleAction = QDialogButtonBox.ButtonRole.ActionRole
QtRoleApply = QDialogButtonBox.ButtonRole.ApplyRole
+QtRoleDestruct = QDialogButtonBox.ButtonRole.DestructiveRole
QtRoleReject = QDialogButtonBox.ButtonRole.RejectRole
QtRoleReset = QDialogButtonBox.ButtonRole.ResetRole