From 3b2878e82963c5cb02c20b52e6fdf8fadf040961 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Sun, 18 Dec 2022 22:23:36 +0100 Subject: [PATCH 01/44] Fix crash handler failing on Windows --- yokadi/ycli/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yokadi/ycli/main.py b/yokadi/ycli/main.py index d73276ef..1369c555 100755 --- a/yokadi/ycli/main.py +++ b/yokadi/ycli/main.py @@ -10,6 +10,7 @@ import locale import os +import platform import sys try: @@ -135,7 +136,7 @@ def onecmd(self, line): print("--") print("Python: %s" % sys.version.replace("\n", " ")) print("SQL Alchemy: %s" % sqlalchemy.__version__) - print("OS: %s (%s)" % os.uname()[0:3:2]) + print("OS: %s" % platform.system()) print("Yokadi: %s" % yokadi.__version__) print(cut) print() From 69fe108fe33659e22022a9f007bb28f281a9168d Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Sun, 18 Dec 2022 23:04:54 +0100 Subject: [PATCH 02/44] Use colorama to fix Windows console --- requirements.txt | 1 + setup.py | 1 + yokadi/ycli/main.py | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/requirements.txt b/requirements.txt index 7a09286d..15eddab0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ sqlalchemy>=0.9, <0.9.99 python-dateutil==2.2 +colorama==0.4.6 diff --git a/setup.py b/setup.py index a2e54a3f..1cd2dbe0 100755 --- a/setup.py +++ b/setup.py @@ -79,6 +79,7 @@ def createFileList(sourceDir, *patterns): install_requires=[ "sqlalchemy", "python-dateutil", + "colorama", ], scripts=scripts, data_files=data_files diff --git a/yokadi/ycli/main.py b/yokadi/ycli/main.py index 1369c555..2f4ed6b7 100755 --- a/yokadi/ycli/main.py +++ b/yokadi/ycli/main.py @@ -36,6 +36,8 @@ print("Or use 'pip install sqlalchemy'") sys.exit(1) +from colorama import just_fix_windows_console + import yokadi from yokadi.core import db @@ -208,6 +210,8 @@ def createArgumentParser(): def main(): locale.setlocale(locale.LC_ALL, os.environ.get("LANG", "C")) + just_fix_windows_console() + parser = createArgumentParser() args = parser.parse_args() dataDir, dbPath = commonargs.processArgs(args) From 60f6c985a71cec76af44cab44555106d6bf247e4 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Sun, 18 Dec 2022 23:25:35 +0100 Subject: [PATCH 03/44] Use pyreadline3 --- requirements.txt | 1 + setup.py | 1 + yokadi/ycli/main.py | 9 +-------- yokadi/ycli/tui.py | 19 ++----------------- 4 files changed, 5 insertions(+), 25 deletions(-) diff --git a/requirements.txt b/requirements.txt index 15eddab0..be3e96e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ sqlalchemy>=0.9, <0.9.99 python-dateutil==2.2 colorama==0.4.6 +pyreadline3==3.4.1; platform_system == 'Windows' diff --git a/setup.py b/setup.py index 1cd2dbe0..99e8b076 100755 --- a/setup.py +++ b/setup.py @@ -80,6 +80,7 @@ def createFileList(sourceDir, *patterns): "sqlalchemy", "python-dateutil", "colorama", + "pyreadline3;platform_system=='Windows'", ], scripts=scripts, data_files=data_files diff --git a/yokadi/ycli/main.py b/yokadi/ycli/main.py index 2f4ed6b7..3e4aa9c9 100755 --- a/yokadi/ycli/main.py +++ b/yokadi/ycli/main.py @@ -13,14 +13,7 @@ import platform import sys -try: - import readline -except ImportError: - print("You don't have a working readline library.") - print("Windows users must install Pyreadline.") - print("Get it on https://launchpad.net/pyreadline/+download") - print("Or use 'pip install pyreadline'") - sys.exit(1) +import readline readline.parse_and_bind("set show-all-if-ambiguous on") diff --git a/yokadi/ycli/tui.py b/yokadi/ycli/tui.py index 4718d678..c12699e7 100644 --- a/yokadi/ycli/tui.py +++ b/yokadi/ycli/tui.py @@ -33,23 +33,8 @@ _answers = [] - -class IOStream: - def __init__(self, original_flow): - self.__original_flow = original_flow - if sys.platform == 'win32': - import pyreadline - self.__console = pyreadline.GetOutputFile() - - def write(self, text): - if sys.platform == 'win32': - self.__console.write_color(text) - else: - self.__original_flow.write(text) - - -stdout = IOStream(sys.stdout) -stderr = IOStream(sys.stderr) +stdout = sys.stdout +stderr = sys.stderr isInteractive = sys.stdin.isatty() From cbefad7432edb592d0cb299746be40ec2897e324 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Sun, 18 Dec 2022 22:46:12 +0100 Subject: [PATCH 04/44] Update to latest sqlalchemy --- requirements.txt | 2 +- setup.py | 2 +- yokadi/core/db.py | 7 ++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index be3e96e5..352d8e01 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -sqlalchemy>=0.9, <0.9.99 +sqlalchemy==1.4.45 python-dateutil==2.2 colorama==0.4.6 pyreadline3==3.4.1; platform_system == 'Windows' diff --git a/setup.py b/setup.py index 99e8b076..ead899c5 100755 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def createFileList(sourceDir, *patterns): # distutils does not support install_requires, but pip needs it to be # able to automatically install dependencies install_requires=[ - "sqlalchemy", + "sqlalchemy ~= 1.4.45", "python-dateutil", "colorama", "pyreadline3;platform_system=='Windows'", diff --git a/yokadi/core/db.py b/yokadi/core/db.py index c97b9dbc..72aa7c70 100644 --- a/yokadi/core/db.py +++ b/yokadi/core/db.py @@ -319,13 +319,10 @@ def __init__(self, dbFileName, createIfNeeded=True, memoryDatabase=False, update dbFileName = os.path.abspath(dbFileName) - if sys.platform == 'win32': - connectionString = 'sqlite://' + dbFileName[0] + '|' + dbFileName[2:] - else: - connectionString = 'sqlite:///' + dbFileName - if memoryDatabase: connectionString = "sqlite:///:memory:" + else: + connectionString = 'sqlite:///' + dbFileName echo = os.environ.get("YOKADI_SQL_DEBUG", "0") != "0" self.engine = create_engine(connectionString, echo=echo) From 51c3c26d0241e159e4f4f7ad69058ba29feaa9f2 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Sun, 18 Dec 2022 23:47:48 +0100 Subject: [PATCH 05/44] Be more precise in install_requires --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index ead899c5..0e14f3e5 100755 --- a/setup.py +++ b/setup.py @@ -78,9 +78,9 @@ def createFileList(sourceDir, *patterns): # able to automatically install dependencies install_requires=[ "sqlalchemy ~= 1.4.45", - "python-dateutil", - "colorama", - "pyreadline3;platform_system=='Windows'", + "python-dateutil ~= 2.2.0", + "colorama ~= 0.4.6", + "pyreadline3 ~= 3.4.1 ; platform_system == 'Windows'", ], scripts=scripts, data_files=data_files From bedaa28c02f27591fe8c25bda1dd6b4a8ee2e5c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20G=C3=A2teau?= Date: Sun, 18 Dec 2022 23:57:04 +0100 Subject: [PATCH 06/44] Switch to GitHub action --- .github/workflows/main.yml | 49 ++++++++++++++++++++++++++++++++++++++ .travis.yml | 20 ---------------- 2 files changed, 49 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..4a74ef1d --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,49 @@ +name: main + +on: + workflow_dispatch: + push: + branches: + - master + - dev + - sync + pull_request: + branches: + - master + - dev + - sync + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - "3.8" + - "3.9" + - "3.10" + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt -r extra-requirements.txt + pip install coverage flake8 + + - name: Lint + # Make sure what's already flake8-happy remains flake8-happy + # Exclude w32_postinst.py because it uses install-specific builtin functions + run: | + flake8 --exclude build,w32_postinst.py + + - name: Tests + run: | + coverage run --source=yokadi --omit="yokadi/tests/*" yokadi/tests/tests.py diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8ca3e259..00000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: python - -python: - - "3.4" - - "3.5" - -install: - - pip install -r requirements.txt - - pip install -r extra-requirements.txt - - pip install coverage coveralls flake8 -script: - # Make sure what's already flake8-happy remains flake8-happy - # Exclude w32_postinst.py because it uses install-specific builtin functions - - flake8 --exclude build,w32_postinst.py - - coverage run --source=yokadi --omit="yokadi/tests/*" yokadi/tests/tests.py -after_success: - coveralls -notifications: - email: false - irc: "chat.freenode.net#yokadi" From 0c0eb61469dca96836a347192355522ae623139f Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 19 Dec 2022 00:03:27 +0100 Subject: [PATCH 07/44] Fix flake8 issues --- yokadi/core/db.py | 1 - yokadi/ycli/confcmd.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/yokadi/core/db.py b/yokadi/core/db.py index 72aa7c70..b1e197d1 100644 --- a/yokadi/core/db.py +++ b/yokadi/core/db.py @@ -8,7 +8,6 @@ import json import os -import sys from datetime import datetime from uuid import uuid1 diff --git a/yokadi/ycli/confcmd.py b/yokadi/ycli/confcmd.py index 1a17501f..5331c9f8 100644 --- a/yokadi/ycli/confcmd.py +++ b/yokadi/ycli/confcmd.py @@ -68,7 +68,7 @@ def checkParameterValue(self, name, value): if name in ("ALARM_DELAY", "ALARM_SUSPEND", "PURGE_DELAY"): try: value = int(value) - assert(value >= 0) + assert value >= 0 return True except (ValueError, AssertionError): return False From cdd11a7b1f8bbc2e74b232e4417e551ed44a091c Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 19 Dec 2022 00:07:14 +0100 Subject: [PATCH 08/44] Disable icalendar tests for now Importing icalendar fails like this: ``` Traceback (most recent call last): File "yokadi/tests/tests.py", line 18, in import icalendar # noqa: F401 File "/opt/hostedtoolcache/Python/3.8.15/x64/lib/python3.8/site-packages/icalendar/__init__.py", line 1, in from icalendar.cal import ( File "/opt/hostedtoolcache/Python/3.8.15/x64/lib/python3.8/site-packages/icalendar/cal.py", line 9, in from icalendar.parser import Contentline File "/opt/hostedtoolcache/Python/3.8.15/x64/lib/python3.8/site-packages/icalendar/parser.py", line 371, in from icalendar.prop import vText File "/opt/hostedtoolcache/Python/3.8.15/x64/lib/python3.8/site-packages/icalendar/prop.py", line 151, in class vBoolean(int): File "/opt/hostedtoolcache/Python/3.8.15/x64/lib/python3.8/site-packages/icalendar/prop.py", line 154, in vBoolean BOOL_MAP = CaselessDict(true=True, false=False) File "/opt/hostedtoolcache/Python/3.8.15/x64/lib/python3.8/site-packages/icalendar/caselessdict.py", line 32, in __init__ for key, value in self.items(): RuntimeError: dictionary keys changed during iteration ``` --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4a74ef1d..f46ab34c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,7 +35,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements.txt -r extra-requirements.txt + pip install -r requirements.txt pip install coverage flake8 - name: Lint From 026783a57d984d772e0382d4dff9d9f3dd838c05 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 19 Dec 2022 09:25:18 +0100 Subject: [PATCH 09/44] Fix tests failing because of issues with checking "interactivity" --- yokadi/tests/tuitestcase.py | 3 +++ yokadi/ycli/tui.py | 13 ++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/yokadi/tests/tuitestcase.py b/yokadi/tests/tuitestcase.py index 3a42c459..cd12224c 100644 --- a/yokadi/tests/tuitestcase.py +++ b/yokadi/tests/tuitestcase.py @@ -16,6 +16,9 @@ def setUp(self): tui.clearInputAnswers() def testEditEmptyText(self): + # Add an unused input answer to pass the isInteractive() check in editText() + tui.addInputAnswers("") + os.environ["EDITOR"] = "/bin/true" out = tui.editText(None) self.assertEqual(out, "") diff --git a/yokadi/ycli/tui.py b/yokadi/ycli/tui.py index c12699e7..83a29173 100644 --- a/yokadi/ycli/tui.py +++ b/yokadi/ycli/tui.py @@ -37,11 +37,18 @@ stderr = sys.stderr -isInteractive = sys.stdin.isatty() +_isInteractive = sys.stdin.isatty() + + +def isInteractive(): + if _answers: + # We are in a test, interaction is being simulated + return True + return _isInteractive def _checkIsInteractive(): - if not isInteractive: + if not isInteractive(): raise YokadiException("This command cannot be used in non-interactive mode") @@ -196,7 +203,7 @@ def enterInt(default=None, prompt="Enter a number"): def confirm(prompt): - if not isInteractive: + if not isInteractive(): return True while True: answer = editLine("", prompt=prompt + " (y/n)? ") From 5a6249628e7ad31dd5562af9152f0569521428fd Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 19 Dec 2022 10:07:35 +0100 Subject: [PATCH 10/44] Use pytest to run tests --- .github/workflows/main.yml | 4 ++-- requirements-dev.txt | 3 +++ scripts/coverage | 15 ++++----------- setup.py | 2 +- 4 files changed, 10 insertions(+), 14 deletions(-) create mode 100644 requirements-dev.txt diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f46ab34c..72a336a0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,7 +36,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install coverage flake8 + pip install -r requirements-dev.txt - name: Lint # Make sure what's already flake8-happy remains flake8-happy @@ -46,4 +46,4 @@ jobs: - name: Tests run: | - coverage run --source=yokadi --omit="yokadi/tests/*" yokadi/tests/tests.py + scripts/coverage diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..9a616b9c --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,3 @@ +coverage==7.0.0 +flake8==6.0.0 +pytest==7.2.0 diff --git a/scripts/coverage b/scripts/coverage index 2b3ee178..9cbfc7f1 100755 --- a/scripts/coverage +++ b/scripts/coverage @@ -1,14 +1,7 @@ -#!/bin/sh -set -e +#!/usr/bin/env bash +set -euo pipefail cd $(dirname $0)/.. -COVERAGE_BIN=${COVERAGE_BIN=python3-coverage} - -if ! which $COVERAGE_BIN > /dev/null ; then - echo "Could not find $COVERAGE_BIN make sure Coverage is installed" - exit 1 -fi - -$COVERAGE_BIN run --source=yokadi --omit="yokadi/tests/*" yokadi/tests/tests.py -$COVERAGE_BIN html +coverage run --source=yokadi --omit="yokadi/tests/*" -m pytest yokadi/tests/tests.py +coverage report diff --git a/setup.py b/setup.py index 0e14f3e5..ad0e3614 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ from os.path import isdir, dirname, join sys.path.insert(0, dirname(__file__)) -import yokadi +import yokadi # noqa: E402 def createFileList(sourceDir, *patterns): From ecbaca82de2a8f739242d660b4123946e70c19d6 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 19 Dec 2022 10:07:46 +0100 Subject: [PATCH 11/44] Make flake8 pass, move invocation to a separate script Keep CI-specific code light. --- .github/workflows/main.yml | 4 +--- scripts/lint | 7 +++++++ setup.cfg | 3 +++ yokadi/tests/tests.py | 41 +++++++++++++++++++------------------- yokadi/ycli/main.py | 13 +++--------- 5 files changed, 34 insertions(+), 34 deletions(-) create mode 100755 scripts/lint create mode 100644 setup.cfg diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 72a336a0..092b57d3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,10 +39,8 @@ jobs: pip install -r requirements-dev.txt - name: Lint - # Make sure what's already flake8-happy remains flake8-happy - # Exclude w32_postinst.py because it uses install-specific builtin functions run: | - flake8 --exclude build,w32_postinst.py + scripts/lint - name: Tests run: | diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 00000000..fab6b34a --- /dev/null +++ b/scripts/lint @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd $(dirname $0)/.. + +echo "flake8" +flake8 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..2154d167 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[flake8] +exclude = build,w32_postinst.py +max-line-length = 120 diff --git a/yokadi/tests/tests.py b/yokadi/tests/tests.py index dd311e4b..65676ba4 100755 --- a/yokadi/tests/tests.py +++ b/yokadi/tests/tests.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: UTF-8 -*- """ Yokadi unit tests @@ -21,27 +20,27 @@ hasIcalendar = False print("icalendar is not installed, some tests won't be run") -from parseutilstestcase import ParseUtilsTestCase # noqa: F401 -from yokadioptionparsertestcase import YokadiOptionParserTestCase # noqa: F401 -from ydateutilstestcase import YDateUtilsTestCase # noqa: F401 -from dbutilstestcase import DbUtilsTestCase # noqa: F401 -from projecttestcase import ProjectTestCase # noqa: F401 -from completerstestcase import CompletersTestCase # noqa: F401 -from tasktestcase import TaskTestCase # noqa: F401 -from bugtestcase import BugTestCase # noqa: F401 -from aliastestcase import AliasTestCase # noqa: F401 -from textlistrenderertestcase import TextListRendererTestCase # noqa: F401 +from parseutilstestcase import ParseUtilsTestCase # noqa: F401, E402 +from yokadioptionparsertestcase import YokadiOptionParserTestCase # noqa: F401, E402 +from ydateutilstestcase import YDateUtilsTestCase # noqa: F401, E402 +from dbutilstestcase import DbUtilsTestCase # noqa: F401, E402 +from projecttestcase import ProjectTestCase # noqa: F401, E402 +from completerstestcase import CompletersTestCase # noqa: F401, E402 +from tasktestcase import TaskTestCase # noqa: F401, E402 +from bugtestcase import BugTestCase # noqa: F401, E402 +from aliastestcase import AliasTestCase # noqa: F401, E402 +from textlistrenderertestcase import TextListRendererTestCase # noqa: F401, E402 if hasIcalendar: - from icaltestcase import IcalTestCase # noqa: F401 -from keywordtestcase import KeywordTestCase # noqa: F401 -from tuitestcase import TuiTestCase # noqa: F401 -from helptestcase import HelpTestCase # noqa: F401 -from conftestcase import ConfTestCase # noqa: F401 -from massedittestcase import MassEditTestCase # noqa: F401 -from basepathstestcase import BasePathsUnixTestCase, BasePathsWindowsTestCase # noqa: F401 -from keywordfiltertestcase import KeywordFilterTestCase # noqa: F401 -from recurrenceruletestcase import RecurrenceRuleTestCase # noqa: F401 -from argstestcase import ArgsTestCase # noqa: F401 + from icaltestcase import IcalTestCase # noqa: F401, E402 +from keywordtestcase import KeywordTestCase # noqa: F401, E402 +from tuitestcase import TuiTestCase # noqa: F401, E402 +from helptestcase import HelpTestCase # noqa: F401, E402 +from conftestcase import ConfTestCase # noqa: F401, E402 +from massedittestcase import MassEditTestCase # noqa: F401, E402 +from basepathstestcase import BasePathsUnixTestCase, BasePathsWindowsTestCase # noqa: F401, E402 +from keywordfiltertestcase import KeywordFilterTestCase # noqa: F401, E402 +from recurrenceruletestcase import RecurrenceRuleTestCase # noqa: F401, E402 +from argstestcase import ArgsTestCase # noqa: F401, E402 def main(): diff --git a/yokadi/ycli/main.py b/yokadi/ycli/main.py index 3e4aa9c9..5657726e 100755 --- a/yokadi/ycli/main.py +++ b/yokadi/ycli/main.py @@ -15,20 +15,11 @@ import readline -readline.parse_and_bind("set show-all-if-ambiguous on") - import traceback from cmd import Cmd from argparse import ArgumentParser -try: - import sqlalchemy -except ImportError: - print("You must install SQL Alchemy to use Yokadi") - print("Get it on http://www.sqlalchemy.org/") - print("Or use 'pip install sqlalchemy'") - sys.exit(1) - +import sqlalchemy from colorama import just_fix_windows_console import yokadi @@ -47,6 +38,8 @@ from yokadi.core.yokadiexception import YokadiException, BadUsageException from yokadi.core.yokadioptionparser import YokadiOptionParserNormalExitException +readline.parse_and_bind("set show-all-if-ambiguous on") + # TODO: move YokadiCmd to a separate module in ycli package class YokadiCmd(TaskCmd, ProjectCmd, KeywordCmd, ConfCmd, AliasCmd, Cmd): From a1aa96ad87fca7e33540314cea3c3568922b0405 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 19 Dec 2022 10:11:23 +0100 Subject: [PATCH 12/44] Fix sqlalchemy deprecation warning --- yokadi/core/db.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/yokadi/core/db.py b/yokadi/core/db.py index b1e197d1..deddb241 100644 --- a/yokadi/core/db.py +++ b/yokadi/core/db.py @@ -11,7 +11,7 @@ from datetime import datetime from uuid import uuid1 -from sqlalchemy import create_engine +from sqlalchemy import create_engine, inspect from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.orm import scoped_session, sessionmaker, relationship @@ -348,7 +348,8 @@ def createTables(self): Base.metadata.create_all(self.engine) def getVersion(self): - if not self.engine.has_table("config"): + inspector = inspect(self.engine) + if not inspector.has_table("config"): # There was no Config table in v1 return 1 From 73237113da0f4cba114d97aaf1dc9c224bd97649 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 19 Dec 2022 10:24:37 +0100 Subject: [PATCH 13/44] Update to latest dateutil --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 352d8e01..66d30670 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ sqlalchemy==1.4.45 -python-dateutil==2.2 +python-dateutil==2.8.2 colorama==0.4.6 pyreadline3==3.4.1; platform_system == 'Windows' diff --git a/setup.py b/setup.py index ad0e3614..15c8bcbe 100755 --- a/setup.py +++ b/setup.py @@ -78,7 +78,7 @@ def createFileList(sourceDir, *patterns): # able to automatically install dependencies install_requires=[ "sqlalchemy ~= 1.4.45", - "python-dateutil ~= 2.2.0", + "python-dateutil ~= 2.8.2", "colorama ~= 0.4.6", "pyreadline3 ~= 3.4.1 ; platform_system == 'Windows'", ], From 01f5232b65f18ce07d7717dffd6b614f820feff8 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Wed, 31 Jul 2024 08:43:48 +0200 Subject: [PATCH 14/44] Update GitHub actions versions --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 092b57d3..91e2dfb6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -25,10 +25,10 @@ jobs: - "3.10" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} From 48c9ff18280f165a8cad6ecb3c166a479345c844 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Wed, 31 Jul 2024 08:43:54 +0200 Subject: [PATCH 15/44] Test more Python versions --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 91e2dfb6..0d52b6f1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,6 +23,8 @@ jobs: - "3.8" - "3.9" - "3.10" + - "3.11" + - "3.12" steps: - uses: actions/checkout@v4 From 02d6f74f21cf8c0d97e28611bacf66163af3023b Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Wed, 31 Jul 2024 08:44:03 +0200 Subject: [PATCH 16/44] Make flake8 ignore .venv dir --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 2154d167..ef4aa117 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [flake8] -exclude = build,w32_postinst.py +exclude = build,w32_postinst.py,.venv max-line-length = 120 From 89b40d795c6d9d31c6e6f2e9ede8415f6fa0c0d3 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Fri, 2 Aug 2024 08:53:34 +0200 Subject: [PATCH 17/44] Fix fail to import test suite when icalendar is available Running the test suite would fail with: ``` ___________________________________________________________________________________ ERROR collecting yokadi/tests/tests.py ___________________________________________________________________________________ yokadi/tests/tests.py:17: in import icalendar # noqa: F401 .venv/lib/python3.10/site-packages/icalendar/__init__.py:1: in from icalendar.cal import ( .venv/lib/python3.10/site-packages/icalendar/cal.py:9: in from icalendar.parser import Contentline .venv/lib/python3.10/site-packages/icalendar/parser.py:371: in from icalendar.prop import vText .venv/lib/python3.10/site-packages/icalendar/prop.py:151: in class vBoolean(int): .venv/lib/python3.10/site-packages/icalendar/prop.py:154: in vBoolean BOOL_MAP = CaselessDict(true=True, false=False) .venv/lib/python3.10/site-packages/icalendar/caselessdict.py:32: in __init__ for key, value in self.items(): E RuntimeError: dictionary keys changed during iteration ``` Bump icalendar to 3.7. --- extra-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extra-requirements.txt b/extra-requirements.txt index 31575199..21e627cc 100644 --- a/extra-requirements.txt +++ b/extra-requirements.txt @@ -1,2 +1,2 @@ -icalendar==3.6.1 +icalendar==3.7 setproctitle==1.1.8 From f92f8c33eb7880803ad512ed253d506f698f2d56 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Fri, 2 Aug 2024 08:56:06 +0200 Subject: [PATCH 18/44] Make CI run icalendar tests --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0d52b6f1..53703518 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,6 +38,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt + pip install -r extra-requirements.txt pip install -r requirements-dev.txt - name: Lint From 93c4eeac26ebbe214a3e7223d6f4fa9a095d1a0c Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Fri, 2 Aug 2024 08:58:40 +0200 Subject: [PATCH 19/44] Bump coverage, flake8 and pytest to latest versions --- requirements-dev.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9a616b9c..792d4b87 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,3 @@ -coverage==7.0.0 -flake8==6.0.0 -pytest==7.2.0 +coverage==7.6.0 +flake8==7.1.0 +pytest==8.3.2 From 4e5e94f6380166c916b1b47e5263f22f8bdc854f Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Fri, 2 Aug 2024 19:08:38 +0200 Subject: [PATCH 20/44] Remove usage of Session.merge() Session.merge() is useful to update an instance which is already in the session with a newly created instance, but we never do that. When we need the ID of a newly created instance, call Session.flush() instead. Changing this fixes issues with SQLAlchemy 2.0. --- yokadi/core/db.py | 2 -- yokadi/core/dbutils.py | 3 +-- yokadi/tests/massedittestcase.py | 1 - yokadi/ycli/keywordcmd.py | 1 - yokadi/ycli/massedit.py | 5 +---- yokadi/ycli/projectcmd.py | 2 -- yokadi/ycli/taskcmd.py | 5 ----- yokadi/yical/yical.py | 1 - 8 files changed, 2 insertions(+), 18 deletions(-) diff --git a/yokadi/core/db.py b/yokadi/core/db.py index deddb241..47cf900a 100644 --- a/yokadi/core/db.py +++ b/yokadi/core/db.py @@ -195,8 +195,6 @@ def setStatus(self, status): self.doneDate = datetime.now().replace(second=0, microsecond=0) else: self.doneDate = None - session = getSession() - session.merge(self) def setRecurrenceRule(self, rule): """Set recurrence and update the due date accordingly""" diff --git a/yokadi/core/dbutils.py b/yokadi/core/dbutils.py index f5af6da0..29c4950b 100644 --- a/yokadi/core/dbutils.py +++ b/yokadi/core/dbutils.py @@ -45,7 +45,7 @@ def addTask(projectName, title, keywordDict=None, interactive=True): description="", status="new") session.add(task) task.setKeywordDict(keywordDict) - session.merge(task) + session.flush() return task @@ -197,7 +197,6 @@ def update(self, now=None): now = datetime.now() lock = self._getLock() lock.updateDate = now - self.session.merge(lock) self.session.commit() def release(self): diff --git a/yokadi/tests/massedittestcase.py b/yokadi/tests/massedittestcase.py index f2f62153..94fd6ecd 100644 --- a/yokadi/tests/massedittestcase.py +++ b/yokadi/tests/massedittestcase.py @@ -31,7 +31,6 @@ def testApplyMEditChanges(self): t2 = dbutils.addTask("p1", "Change keywords", {"k1": None, "k2": 1}) t3 = dbutils.addTask("p1", "Done", {}) t3.status = "started" - self.session.merge(t3) t4 = dbutils.addTask("p1", "Deleted", {}) t5 = dbutils.addTask("p1", "Moved", {}) self.session.commit() diff --git a/yokadi/ycli/keywordcmd.py b/yokadi/ycli/keywordcmd.py index d7b1a340..50d25683 100644 --- a/yokadi/ycli/keywordcmd.py +++ b/yokadi/ycli/keywordcmd.py @@ -80,7 +80,6 @@ def do_k_edit(self, line): if len(lst) == 0: # Simple case: newName does not exist, just rename the existing keyword keyword.name = newName - session.merge(keyword) session.commit() print("Keyword %s has been renamed to %s" % (oldName, newName)) return diff --git a/yokadi/ycli/massedit.py b/yokadi/ycli/massedit.py index d4d2b78c..db14ef8f 100644 --- a/yokadi/ycli/massedit.py +++ b/yokadi/ycli/massedit.py @@ -190,14 +190,11 @@ def applyChanges(project, oldList, newList, interactive=True): task = dbutils.getTaskFromId(newEntry.id) else: task = Task(creationDate=datetime.now().replace(second=0, microsecond=0), project=project) + session.add(task) task.title = newEntry.title task.setKeywordDict(newEntry.keywords) task.setStatus(newEntry.status) task.urgency = nbTasks - pos - if newEntry.id: - session.merge(task) - else: - session.add(task) # vi: ts=4 sw=4 et diff --git a/yokadi/ycli/projectcmd.py b/yokadi/ycli/projectcmd.py index 63c7598c..98d8583f 100644 --- a/yokadi/ycli/projectcmd.py +++ b/yokadi/ycli/projectcmd.py @@ -92,7 +92,6 @@ def do_p_set_active(self, line): session = db.getSession() project = getProjectFromName(line) project.active = True - session.merge(project) session.commit() complete_p_set_active = ProjectCompleter(1) @@ -101,7 +100,6 @@ def do_p_set_inactive(self, line): session = db.getSession() project = getProjectFromName(line) project.active = False - session.merge(project) session.commit() complete_p_set_inactive = ProjectCompleter(1) diff --git a/yokadi/ycli/taskcmd.py b/yokadi/ycli/taskcmd.py index 28a4cb70..5a710c6b 100644 --- a/yokadi/ycli/taskcmd.py +++ b/yokadi/ycli/taskcmd.py @@ -167,7 +167,6 @@ def updateDescription(description): except Exception as e: raise YokadiException(e) updateDescription(description) - self.session.merge(task) self.session.commit() complete_t_describe = taskIdCompleter @@ -200,7 +199,6 @@ def do_t_urgency(self, line): urgency = -99 task.urgency = urgency - self.session.merge(task) self.session.commit() complete_t_set_urgency = taskIdCompleter @@ -652,7 +650,6 @@ def do_t_reorder(self, line): for urgency, taskId in enumerate(ids): task = self.session.query(Task).get(taskId) task.urgency = urgency - self.session.merge(task) self.session.commit() complete_t_reorder = ProjectCompleter(1) @@ -905,7 +902,6 @@ def do_t_due(self, line): else: task.dueDate = ydateutils.parseHumaneDateTime(line) print("Due date for task '%s' set to %s" % (task.title, task.dueDate.ctime())) - self.session.merge(task) self.session.commit() complete_t_set_due = dueDateCompleter complete_t_due = dueDateCompleter @@ -931,7 +927,6 @@ def do_t_add_keywords(self, line): kwDict = task.getKeywordDict() kwDict.update(newKwDict) task.setKeywordDict(kwDict) - self.session.merge(task) self.session.commit() def do_t_recurs(self, line): diff --git a/yokadi/yical/yical.py b/yokadi/yical/yical.py index f8fdfe8a..223a0c93 100644 --- a/yokadi/yical/yical.py +++ b/yokadi/yical/yical.py @@ -190,7 +190,6 @@ def _processVTodo(self, vTodo): task = dbutils.getTaskFromId(id) print("Task found in yokadi db: %s" % task.title) updateTaskFromVTodo(task, vTodo) - session.merge(task) session.commit() else: raise YokadiException("Task %s does exist in yokadi db " % id) From 1df3235799fdec8dc226c3a9b842d607cb44f511 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Fri, 2 Aug 2024 19:54:23 +0200 Subject: [PATCH 21/44] Add tests for t_reorder It makes DB calls, so better had tests before to help porting to SQLAlchemy 2.0. --- yokadi/tests/tasktestcase.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/yokadi/tests/tasktestcase.py b/yokadi/tests/tasktestcase.py index c0343cb7..8f1beb25 100644 --- a/yokadi/tests/tasktestcase.py +++ b/yokadi/tests/tasktestcase.py @@ -6,6 +6,7 @@ @license: GPL v3 or later """ import unittest +from unittest.mock import patch import testutils @@ -368,4 +369,18 @@ def testToTask(self): task = self.session.query(Task).get(1) self.assertFalse(task.isNote(self.session)) + @patch("yokadi.ycli.tui.editText") + def testReorder(self, editTextMock): + t1, t2, t3 = [dbutils.addTask("x", f"t{x}", interactive=False) for x in range(1, 4)] + + # Simulate moving t3 from 3rd line to the 1st + editTextMock.return_value = "3,t3\n1,t1\n2,t2" + + self.cmd.do_t_reorder("x") + editTextMock.assert_called_with("1,t1\n2,t2\n3,t3") + + self.assertEqual(t3.urgency, 2) + self.assertEqual(t1.urgency, 1) + self.assertEqual(t2.urgency, 0) + # vi: ts=4 sw=4 et From a1cadd31fdf6655d39d9bdf48f2006c3ad07c631 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Fri, 2 Aug 2024 19:55:05 +0200 Subject: [PATCH 22/44] Make coverage script generate HTML report --- scripts/coverage | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/coverage b/scripts/coverage index 9cbfc7f1..62e5b650 100755 --- a/scripts/coverage +++ b/scripts/coverage @@ -5,3 +5,4 @@ cd $(dirname $0)/.. coverage run --source=yokadi --omit="yokadi/tests/*" -m pytest yokadi/tests/tests.py coverage report +coverage html From 4a06f8e12ccb52da3afa5a64e31c9836399ef776 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 12 Aug 2024 08:37:53 +0200 Subject: [PATCH 23/44] Add test for yical.generateCal --- yokadi/tests/icaltestcase.py | 37 ++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/yokadi/tests/icaltestcase.py b/yokadi/tests/icaltestcase.py index d8d21944..f8e9fdba 100644 --- a/yokadi/tests/icaltestcase.py +++ b/yokadi/tests/icaltestcase.py @@ -7,8 +7,10 @@ import unittest +from datetime import datetime from yokadi.ycli import tui +from yokadi.ycli.projectcmd import getProjectFromName from yokadi.yical import yical from yokadi.core import dbutils from yokadi.core import db @@ -113,8 +115,35 @@ def testKeywordMapping(self): def testTaskDoneMapping(self): tui.addInputAnswers("y") t1 = dbutils.addTask("x", "t1", {}) - yical.createVTodoFromTask(t1) + v1 = yical.createVTodoFromTask(t1) + + v1["completed"] = datetime.now() + yical.updateTaskFromVTodo(t1, v1) + self.assertEqual(t1.status, "done") + + def testGenerateCal(self): + # Add an inactive project + t1 = dbutils.addTask("p1", "t1", interactive=False) + project = getProjectFromName("p1") + project.active = False + + # And an active project with 3 tasks, one of them is done + t2new = dbutils.addTask("p2", "t2new", interactive=False) + + t2started = dbutils.addTask("p2", "t2started", interactive=False) + t2started.setStatus("started") + + t2done = dbutils.addTask("p2", "t2done", interactive=False) + t2done.setStatus("done") + + self.session.commit() + + # Generate the calendar + cal = yical.generateCal() + + # It should contain only "p2", "t1" and "t2new" and "t2started" + # I am not sure that it should contain "t1" (since its project is not active), but that's the current behavior + summaries = sorted(str(x["SUMMARY"]) for x in cal.subcomponents) + expected = sorted(["p2", f"t1 ({t1.id})", f"t2new ({t2new.id})", f"t2started ({t2started.id})"]) - # v1["completed"] = datetime.datetime.now() - # yical.updateTaskFromVTodo(t1, v1) - # self.assertEqual(t1.status, "done") + self.assertEqual(summaries, expected) From 56c6a9f957b7ee47a0db39732a0860296ffdb2f5 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 12 Aug 2024 08:40:24 +0200 Subject: [PATCH 24/44] TaskTestCase: Fix test getting covered by another one --- yokadi/tests/tasktestcase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yokadi/tests/tasktestcase.py b/yokadi/tests/tasktestcase.py index 8f1beb25..58f5bbeb 100644 --- a/yokadi/tests/tasktestcase.py +++ b/yokadi/tests/tasktestcase.py @@ -331,7 +331,7 @@ def testTApply(self): else: self.assertNotEqual(kwDict, dict(lala=None, toto=None)) - def testReorder(self): + def testReorderFailsOnInvalidInputs(self): self.assertRaises(BadUsageException, self.cmd.do_t_reorder, "unknown_project") self.assertRaises(BadUsageException, self.cmd.do_t_reorder, "too much args") From 08dfe0e2023f9ce42f383258dd5adb96656d6567 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 12 Aug 2024 21:57:59 +0200 Subject: [PATCH 25/44] Add tests for IcalHttpRequestHandler --- yokadi/tests/icaltestcase.py | 51 +++++++++++++++++++++++++++++++++++- yokadi/yical/yical.py | 24 ++++++++++------- 2 files changed, 64 insertions(+), 11 deletions(-) diff --git a/yokadi/tests/icaltestcase.py b/yokadi/tests/icaltestcase.py index f8e9fdba..715c7fc7 100644 --- a/yokadi/tests/icaltestcase.py +++ b/yokadi/tests/icaltestcase.py @@ -7,7 +7,9 @@ import unittest -from datetime import datetime +from datetime import datetime, timedelta + +import icalendar from yokadi.ycli import tui from yokadi.ycli.projectcmd import getProjectFromName @@ -147,3 +149,50 @@ def testGenerateCal(self): expected = sorted(["p2", f"t1 ({t1.id})", f"t2new ({t2new.id})", f"t2started ({t2started.id})"]) self.assertEqual(summaries, expected) + + def testHandlerProcessVTodoModifyTask(self): + # Create a task + task = dbutils.addTask("p1", "t1", interactive=False) + self.session.commit() + + # Create a vTodo to modify the task + modified = datetime.now() + created = modified + timedelta(hours=-1) + vTodo = icalendar.Todo() + vTodo["UID"] = yical.TASK_UID % str(task.id) + vTodo.add("CREATED", created) + vTodo.add("LAST-MODIFIED", modified) + vTodo.add("summary", "new title") + + # Process the vTodo + newTaskDict = {} + yical.IcalHttpRequestHandler.processVTodo(newTaskDict, vTodo) + + # The task title must have changed + task = dbutils.getTaskFromId(task.id) + self.assertEqual(task.title, "new title") + + # newTaskDict must not have changed + self.assertEqual(newTaskDict, {}) + + def testHandlerProcessVTodoCreateTask(self): + # Create a vTodo to add a new task + modified = datetime.now() + created = modified + timedelta(hours=-1) + vTodo = icalendar.Todo() + vTodo["UID"] = "zogzog" + vTodo.add("summary", "new task") + + # Process the vTodo + newTaskDict = {} + yical.IcalHttpRequestHandler.processVTodo(newTaskDict, vTodo) + + # The task should be in newTaskDict + newTaskList = list(newTaskDict.items()) + self.assertEqual(len(newTaskList), 1) + + (uid, taskId) = newTaskList[0] + + # And the task can be retried + task = dbutils.getTaskFromId(taskId) + self.assertEqual(task.title, "new task") diff --git a/yokadi/yical/yical.py b/yokadi/yical/yical.py index 223a0c93..53b62b9c 100644 --- a/yokadi/yical/yical.py +++ b/yokadi/yical/yical.py @@ -163,7 +163,7 @@ def do_PUT(self): for vTodo in cal.walk(): if "UID" in vTodo: try: - self._processVTodo(vTodo) + IcalHttpRequestHandler.processVTodo(self.newTask, vTodo) except YokadiException as e: self.send_response(503, e) @@ -171,20 +171,24 @@ def do_PUT(self): self.send_response(200) self.end_headers() - def _processVTodo(self, vTodo): + # This is static method to make it easier to test + @staticmethod + def processVTodo(newTaskDict, vTodo): session = db.getSession() - if vTodo["UID"] in self.newTask: + uid = vTodo["UID"] + if uid in newTaskDict: # This is a recent new task but remote ical calendar tool is not # aware of new Yokadi UID. Update it here to avoid duplicate new tasks print("update UID to avoid duplicate task") - vTodo["UID"] = TASK_UID % self.newTask[vTodo["UID"]] + uid = TASK_UID % newTaskDict[uid] + vTodo["UID"] = uid - if vTodo["UID"].startswith(UID_PREFIX): + if uid.startswith(UID_PREFIX): # This is a yokadi Task. if vTodo["LAST-MODIFIED"].dt > vTodo["CREATED"].dt: # Task has been modified - print("Modified task: %s" % vTodo["UID"]) - result = TASK_RE.match(vTodo["UID"]) + print("Modified task: %s" % uid) + result = TASK_RE.match(uid) if result: id = result.group(1) task = dbutils.getTaskFromId(id) @@ -192,10 +196,10 @@ def _processVTodo(self, vTodo): updateTaskFromVTodo(task, vTodo) session.commit() else: - raise YokadiException("Task %s does exist in yokadi db " % id) + raise YokadiException("Task %s does exist in yokadi db " % uid) else: # This is a new task - print("New task %s (%s)" % (vTodo["summary"], vTodo["UID"])) + print("New task %s (%s)" % (vTodo["summary"], uid)) keywordDict = {} task = dbutils.addTask(INBOX_PROJECT, vTodo["summary"], keywordDict, interactive=False) @@ -205,7 +209,7 @@ def _processVTodo(self, vTodo): # if user update it right after creation without reloading the # yokadi UID # TODO: add purge for old UID - self.newTask[vTodo["UID"]] = task.id + newTaskDict[uid] = task.id class YokadiIcalServer(Thread): From 57ed26575a86e3dd54b980617859b812746f9186 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Tue, 13 Aug 2024 09:07:42 +0200 Subject: [PATCH 26/44] typo-- --- yokadi/tests/icaltestcase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yokadi/tests/icaltestcase.py b/yokadi/tests/icaltestcase.py index 715c7fc7..ff54db5e 100644 --- a/yokadi/tests/icaltestcase.py +++ b/yokadi/tests/icaltestcase.py @@ -193,6 +193,6 @@ def testHandlerProcessVTodoCreateTask(self): (uid, taskId) = newTaskList[0] - # And the task can be retried + # And the task can be retrieved task = dbutils.getTaskFromId(taskId) self.assertEqual(task.title, "new task") From 0b058f667b8c869a236457067c7095d35eeca731 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Tue, 13 Aug 2024 09:07:49 +0200 Subject: [PATCH 27/44] Add extra check in ical test --- yokadi/tests/icaltestcase.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/yokadi/tests/icaltestcase.py b/yokadi/tests/icaltestcase.py index ff54db5e..0ad9cf24 100644 --- a/yokadi/tests/icaltestcase.py +++ b/yokadi/tests/icaltestcase.py @@ -196,3 +196,6 @@ def testHandlerProcessVTodoCreateTask(self): # And the task can be retrieved task = dbutils.getTaskFromId(taskId) self.assertEqual(task.title, "new task") + + # And there is only one task + self.assertEqual(self.session.query(db.Task).count(), 1) From 011f413e300cd02debe7f532cc5a707e49da6e9c Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Fri, 16 Aug 2024 09:09:01 +0200 Subject: [PATCH 28/44] update9to10: make unpickling recurrence rules more robust --- yokadi/update/update9to10.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/yokadi/update/update9to10.py b/yokadi/update/update9to10.py index 5025d5a1..21703276 100644 --- a/yokadi/update/update9to10.py +++ b/yokadi/update/update9to10.py @@ -35,7 +35,11 @@ def createByweekdayValue(rule): def createJsonStringFromRule(pickledRule): - rule = pickle.loads(pickledRule) + try: + rule = pickle.loads(pickledRule) + except UnicodeDecodeError: + # Some rules fails to unpickle if we don't set encoding to latin1 + rule = pickle.loads(pickledRule, encoding="latin1") dct = {} dct["freq"] = rule._freq dct["bymonth"] = tuplify(rule._bymonth) From 8fbf062324473001ec2b25293e403ede855e3621 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Fri, 16 Aug 2024 09:24:09 +0200 Subject: [PATCH 29/44] Add test for setVersion(), fix deprecated code --- yokadi/core/db.py | 9 ++++++--- yokadi/tests/dbtestcase.py | 20 ++++++++++++++++++++ yokadi/tests/tests.py | 1 + 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 yokadi/tests/dbtestcase.py diff --git a/yokadi/core/db.py b/yokadi/core/db.py index 47cf900a..6537f0dd 100644 --- a/yokadi/core/db.py +++ b/yokadi/core/db.py @@ -346,8 +346,7 @@ def createTables(self): Base.metadata.create_all(self.engine) def getVersion(self): - inspector = inspect(self.engine) - if not inspector.has_table("config"): + if not self._hasConfigTable(): # There was no Config table in v1 return 1 @@ -357,12 +356,16 @@ def getVersion(self): raise YokadiException("Configuration key '%s' does not exist. This should not happen!" % DB_VERSION_KEY) def setVersion(self, version): - assert self.engine.has_table("config") + assert self._hasConfigTable() instance = self.session.query(Config).filter_by(name=DB_VERSION_KEY).one() instance.value = str(version) self.session.add(instance) self.session.commit() + def _hasConfigTable(self): + inspector = inspect(self.engine) + return inspector.has_table("config") + def checkVersion(self): """Check version and exit if it is not suitable""" version = self.getVersion() diff --git a/yokadi/tests/dbtestcase.py b/yokadi/tests/dbtestcase.py new file mode 100644 index 00000000..fc898ca0 --- /dev/null +++ b/yokadi/tests/dbtestcase.py @@ -0,0 +1,20 @@ +""" +@author: Aurélien Gâteau +@license: GPL v3 or later +""" + +import unittest + +from yokadi.core import db + + +class DbTestCase(unittest.TestCase): + def setUp(self): + db.connectDatabase("", memoryDatabase=True) + self.session = db.getSession() + + def testSetVersion(self): + newVersion = db.DB_VERSION + 1 + db._database.setVersion(newVersion) + version = db._database.getVersion() + self.assertEqual(version, newVersion) diff --git a/yokadi/tests/tests.py b/yokadi/tests/tests.py index 65676ba4..817070e1 100755 --- a/yokadi/tests/tests.py +++ b/yokadi/tests/tests.py @@ -41,6 +41,7 @@ from keywordfiltertestcase import KeywordFilterTestCase # noqa: F401, E402 from recurrenceruletestcase import RecurrenceRuleTestCase # noqa: F401, E402 from argstestcase import ArgsTestCase # noqa: F401, E402 +from dbtestcase import DbTestCase # noqa: F401, E402 def main(): From a2ffc2d6ebd0c14b1dda610f54c31ebfc6a1abef Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Wed, 31 Jul 2024 20:58:03 +0200 Subject: [PATCH 30/44] Fix all SQLAlchemy 2.0 warnings When running tests like this: ``` SQLALCHEMY_WARN_20=1 \ python -W error::DeprecationWarning -m pytest yokadi/tests/tests.py ``` They pass without failing. --- yokadi/core/db.py | 12 +++++------ yokadi/tests/bugtestcase.py | 2 +- yokadi/tests/tasktestcase.py | 40 ++++++++++++++++++------------------ yokadi/ycli/taskcmd.py | 2 +- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/yokadi/core/db.py b/yokadi/core/db.py index 6537f0dd..8db6ad35 100644 --- a/yokadi/core/db.py +++ b/yokadi/core/db.py @@ -12,9 +12,8 @@ from uuid import uuid1 from sqlalchemy import create_engine, inspect -from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.associationproxy import association_proxy -from sqlalchemy.orm import scoped_session, sessionmaker, relationship +from sqlalchemy.orm import scoped_session, sessionmaker, relationship, declarative_base from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.exc import IntegrityError from sqlalchemy import Column, Integer, Boolean, Unicode, DateTime, Enum, ForeignKey, UniqueConstraint @@ -54,7 +53,7 @@ class Project(Base): uuid = Column(Unicode, unique=True, default=uuidGenerator, nullable=False) name = Column(Unicode, unique=True) active = Column(Boolean, default=True) - tasks = relationship("Task", cascade="all", backref="project") + tasks = relationship("Task", cascade="all", backref="project", cascade_backrefs=False) def __repr__(self): return self.name @@ -83,7 +82,7 @@ class Keyword(Base): id = Column(Integer, primary_key=True) name = Column(Unicode, unique=True) tasks = association_proxy("taskKeywords", "task") - taskKeywords = relationship("TaskKeyword", cascade="all", backref="keyword") + taskKeywords = relationship("TaskKeyword", cascade="all", backref="keyword", cascade_backrefs=False) def __repr__(self): return self.name @@ -91,6 +90,7 @@ def __repr__(self): class TaskKeyword(Base): __tablename__ = "task_keyword" + __mapper_args__ = {"confirm_deleted_rows": False} id = Column(Integer, primary_key=True) taskId = Column("task_id", Integer, ForeignKey("task.id"), nullable=False) keywordId = Column("keyword_id", Integer, ForeignKey("keyword.id"), nullable=False) @@ -138,8 +138,8 @@ class Task(Base): status = Column(Enum("new", "started", "done"), default="new") recurrence = Column(RecurrenceRuleColumnType, nullable=False, default=RecurrenceRule()) projectId = Column("project_id", Integer, ForeignKey("project.id"), nullable=False) - taskKeywords = relationship("TaskKeyword", cascade="all", backref="task") - lock = relationship("TaskLock", cascade="all", backref="task") + taskKeywords = relationship("TaskKeyword", cascade="all", backref="task", cascade_backrefs=False) + lock = relationship("TaskLock", cascade="all", backref="task", cascade_backrefs=False) def setKeywordDict(self, dct): """ diff --git a/yokadi/tests/bugtestcase.py b/yokadi/tests/bugtestcase.py index 16c987a7..fe560d92 100644 --- a/yokadi/tests/bugtestcase.py +++ b/yokadi/tests/bugtestcase.py @@ -34,7 +34,7 @@ def testAdd(self): expected = ["t1"] self.assertEqual(result, expected) - kwDict = self.session.query(Task).get(1).getKeywordDict() + kwDict = self.session.get(Task, 1).getKeywordDict() self.assertEqual(kwDict, dict(_severity=2, _likelihood=4, _bug=123)) for bad_input in ("", # No project diff --git a/yokadi/tests/tasktestcase.py b/yokadi/tests/tasktestcase.py index 58f5bbeb..0672f6fb 100644 --- a/yokadi/tests/tasktestcase.py +++ b/yokadi/tests/tasktestcase.py @@ -41,7 +41,7 @@ def testAdd(self): expected = ["t1", "t2"] self.assertEqual(result, expected) - kwDict = self.session.query(Task).get(2).getKeywordDict() + kwDict = self.session.get(Task, 2).getKeywordDict() self.assertEqual(kwDict, dict(kw1=None, kw2=12)) for bad_input in ("", # No project @@ -55,7 +55,7 @@ def testEdit(self): tui.addInputAnswers("newtxt") self.cmd.do_t_edit("1") - task = self.session.query(Task).get(1) + task = self.session.get(Task, 1) self.assertEqual(task.title, "newtxt") self.assertEqual(task.getKeywordDict(), {"_note": None}) @@ -66,7 +66,7 @@ def testEditAddKeyword(self): tui.addInputAnswers("txt @kw", "y") self.cmd.do_t_edit("1") - task = self.session.query(Task).get(1) + task = self.session.get(Task, 1) self.assertEqual(task.title, "txt") self.assertEqual(task.getKeywordDict(), {"kw": None}) @@ -77,7 +77,7 @@ def testEditRemoveKeyword(self): tui.addInputAnswers("txt") self.cmd.do_t_edit("1") - task = self.session.query(Task).get(1) + task = self.session.get(Task, 1) self.assertEqual(task.title, "txt") self.assertEqual(task.getKeywordDict(), {}) @@ -112,7 +112,7 @@ def testRemove(self): def testMark(self): tui.addInputAnswers("y") self.cmd.do_t_add("x t1") - task = self.session.query(Task).get(1) + task = self.session.get(Task, 1) self.assertEqual(task.status, "new") self.cmd.do_t_mark_started("1") self.assertEqual(task.status, "started") @@ -124,7 +124,7 @@ def testMark(self): def testAddKeywords(self): tui.addInputAnswers("y") self.cmd.do_t_add("x t1") - task = self.session.query(Task).get(1) + task = self.session.get(Task, 1) tui.addInputAnswers("y", "y") self.cmd.do_t_add_keywords("1 @kw1 @kw2=12") @@ -142,17 +142,17 @@ def testSetProject(self): self.cmd.do_t_add("x t1") tui.addInputAnswers("y") self.cmd.do_t_project("1 y") - task1 = self.session.query(Task).get(1) + task1 = self.session.get(Task, 1) self.assertEqual(task1.project.name, "y") self.cmd.do_t_add("x t2") self.cmd.do_t_project("1 _") - task1 = self.session.query(Task).get(1) + task1 = self.session.get(Task, 1) self.assertEqual(task1.project.name, "x") tui.addInputAnswers("n") self.cmd.do_t_project("1 doesnotexist") - task1 = self.session.query(Task).get(1) + task1 = self.session.get(Task, 1) self.assertEqual(task1.project.name, "x") def testLastTaskId(self): @@ -161,11 +161,11 @@ def testLastTaskId(self): tui.addInputAnswers("y") self.cmd.do_t_add("x t1") - task1 = self.session.query(Task).get(1) + task1 = self.session.get(Task, 1) self.assertEqual(self.cmd.getTaskFromId("_"), task1) self.cmd.do_t_add("x t2") - task2 = self.session.query(Task).get(2) + task2 = self.session.get(Task, 2) self.assertEqual(self.cmd.getTaskFromId("_"), task2) self.cmd.do_t_mark_started("1") @@ -176,15 +176,15 @@ def testLastProjectName(self): self.assertRaises(YokadiException, self.cmd.do_t_add, "_ t1") tui.addInputAnswers("y") self.cmd.do_t_add("x t1") - task1 = self.session.query(Task).get(1) + task1 = self.session.get(Task, 1) self.cmd.do_t_add("_ t2") - task2 = self.session.query(Task).get(2) + task2 = self.session.get(Task, 2) self.assertEqual(task1.project, task2.project) def testRecurs(self): tui.addInputAnswers("y") self.cmd.do_t_add("x t1") - task = self.session.query(Task).get(1) + task = self.session.get(Task, 1) self.cmd.do_t_recurs("1 daily 10:00") self.assertTrue(task.recurrence) @@ -313,7 +313,7 @@ def testTApply(self): ids = [1, 2, 4, 5, 6, 9] self.cmd.do_t_apply("1 2,4-6 9 t_add_keywords @lala") for taskId in range(1, 10): - kwDict = self.session.query(Task).get(taskId).getKeywordDict() + kwDict = self.session.get(Task, taskId).getKeywordDict() if taskId in ids: self.assertEqual(kwDict, dict(lala=None)) else: @@ -325,7 +325,7 @@ def testTApply(self): self.cmd.do_t_list("@lala") self.cmd.do_t_apply("__ t_add_keywords @toto") for taskId in range(1, 10): - kwDict = self.session.query(Task).get(taskId).getKeywordDict() + kwDict = self.session.get(Task, taskId).getKeywordDict() if taskId in ids: self.assertEqual(kwDict, dict(lala=None, toto=None)) else: @@ -348,12 +348,12 @@ def testToNote(self): self.cmd.do_t_add("x t1") self.cmd.do_t_to_note(1) - task = self.session.query(Task).get(1) + task = self.session.get(Task, 1) self.assertTrue(task.isNote(self.session)) # Doing it twice should not fail self.cmd.do_t_to_note(1) - task = self.session.query(Task).get(1) + task = self.session.get(Task, 1) self.assertTrue(task.isNote(self.session)) def testToTask(self): @@ -361,12 +361,12 @@ def testToTask(self): self.cmd.do_n_add("x t1") self.cmd.do_n_to_task(1) - task = self.session.query(Task).get(1) + task = self.session.get(Task, 1) self.assertFalse(task.isNote(self.session)) # Doing it twice should not fail self.cmd.do_n_to_task(1) - task = self.session.query(Task).get(1) + task = self.session.get(Task, 1) self.assertFalse(task.isNote(self.session)) @patch("yokadi.ycli.tui.editText") diff --git a/yokadi/ycli/taskcmd.py b/yokadi/ycli/taskcmd.py index 5a710c6b..26edb61c 100644 --- a/yokadi/ycli/taskcmd.py +++ b/yokadi/ycli/taskcmd.py @@ -648,7 +648,7 @@ def do_t_reorder(self, line): ids.reverse() for urgency, taskId in enumerate(ids): - task = self.session.query(Task).get(taskId) + task = self.session.get(Task, taskId) task.urgency = urgency self.session.commit() complete_t_reorder = ProjectCompleter(1) From c6bf84be85d411495e223ccc55cc9eb622440238 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Fri, 16 Aug 2024 09:20:53 +0200 Subject: [PATCH 31/44] Update SQLAlchemy to 2.0.32 --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 66d30670..64a6d170 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -sqlalchemy==1.4.45 +sqlalchemy==2.0.32 python-dateutil==2.8.2 colorama==0.4.6 pyreadline3==3.4.1; platform_system == 'Windows' diff --git a/setup.py b/setup.py index 15c8bcbe..6c9d2410 100755 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def createFileList(sourceDir, *patterns): # distutils does not support install_requires, but pip needs it to be # able to automatically install dependencies install_requires=[ - "sqlalchemy ~= 1.4.45", + "sqlalchemy ~= 2.0.32", "python-dateutil ~= 2.8.2", "colorama ~= 0.4.6", "pyreadline3 ~= 3.4.1 ; platform_system == 'Windows'", From bab00f9fb2034acbe3c0c57aa1c7e293ac18a96b Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Tue, 3 Sep 2024 08:39:24 +0200 Subject: [PATCH 32/44] make getOrCreateProject() similar to getOrCreateKeyword() --- yokadi/core/dbutils.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/yokadi/core/dbutils.py b/yokadi/core/dbutils.py index 29c4950b..13b13918 100644 --- a/yokadi/core/dbutils.py +++ b/yokadi/core/dbutils.py @@ -100,20 +100,17 @@ def getOrCreateProject(projectName, interactive=True, createIfNeeded=True): @type createIfNeeded: Bool @return: Project instance or None if user cancel creation or createIfNeeded is False""" session = db.getSession() - result = session.query(Project).filter_by(name=projectName).all() - if len(result): - return result[0] - - if not createIfNeeded: - return None - - if interactive and not tui.confirm("Project '%s' does not exist, create it" % projectName): - return None - - project = Project(name=projectName) - session.add(project) - print("Added project '%s'" % projectName) - return project + try: + return session.query(Project).filter_by(name=projectName).one() + except (NoResultFound, MultipleResultsFound): + if not createIfNeeded: + return None + if interactive and not tui.confirm("Project '%s' does not exist, create it" % projectName): + return None + project = Project(name=projectName) + session.add(project) + print("Added project '%s'" % projectName) + return project def createMissingKeywords(lst, interactive=True): From a490e51085b08579b2ee47815fc721eaed1db3c8 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Tue, 3 Sep 2024 08:40:47 +0200 Subject: [PATCH 33/44] testTaskDoneMapping: fix wrong date type being used --- yokadi/tests/icaltestcase.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/yokadi/tests/icaltestcase.py b/yokadi/tests/icaltestcase.py index 0ad9cf24..9c154d1b 100644 --- a/yokadi/tests/icaltestcase.py +++ b/yokadi/tests/icaltestcase.py @@ -119,9 +119,11 @@ def testTaskDoneMapping(self): t1 = dbutils.addTask("x", "t1", {}) v1 = yical.createVTodoFromTask(t1) - v1["completed"] = datetime.now() + completed = datetime.now() + v1.add("COMPLETED", completed) yical.updateTaskFromVTodo(t1, v1) self.assertEqual(t1.status, "done") + self.assertEqual(t1.doneDate, completed) def testGenerateCal(self): # Add an inactive project From 06d74b43dfc3b78bf8765d7f23939a0f340af3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20G=C3=A2teau?= Date: Wed, 4 Sep 2024 16:32:32 +0200 Subject: [PATCH 34/44] Write keywords as colored `@k1 @k2` This is more readable and more compact than the existing `(k1, k2)` format. --- yokadi/core/db.py | 4 ++-- yokadi/ycli/textlistrenderer.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/yokadi/core/db.py b/yokadi/core/db.py index 8db6ad35..cd77931f 100644 --- a/yokadi/core/db.py +++ b/yokadi/core/db.py @@ -172,13 +172,13 @@ def getKeywordsAsString(self): def getUserKeywordsNameAsString(self): """ - Returns all keywords keys as a string like "key1, key2, key3...". + Returns all keywords keys as a string like "@key1 @key2 @key3...". Internal keywords (starting with _) are ignored. """ keywords = [k for k in list(self.getKeywordDict().keys()) if not k.startswith("_")] keywords.sort() if keywords: - return ", ".join(keywords) + return " ".join(f"@{k}" for k in keywords) else: return "" diff --git a/yokadi/ycli/textlistrenderer.py b/yokadi/ycli/textlistrenderer.py index 521a310d..b84a99aa 100644 --- a/yokadi/ycli/textlistrenderer.py +++ b/yokadi/ycli/textlistrenderer.py @@ -85,11 +85,10 @@ def __call__(self, task): # Create title title = task.title if keywords and len(title) < maxWidth: - title += ' (' - colorizer.setColorAt(len(title), C.BOLD) + title += ' ' + colorizer.setColorAt(len(title), C.ORANGE) title += keywords colorizer.setResetAt(len(title)) - title += ')' # Crop title to fit in self.width titleWidth = len(title) From 6253a763c7fc5324a85b9005fa21521725aaf32d Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Thu, 31 Oct 2024 08:43:17 +0100 Subject: [PATCH 35/44] Fix flake8 issue --- yokadi/tests/icaltestcase.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/yokadi/tests/icaltestcase.py b/yokadi/tests/icaltestcase.py index 9c154d1b..0898a6e6 100644 --- a/yokadi/tests/icaltestcase.py +++ b/yokadi/tests/icaltestcase.py @@ -179,8 +179,6 @@ def testHandlerProcessVTodoModifyTask(self): def testHandlerProcessVTodoCreateTask(self): # Create a vTodo to add a new task - modified = datetime.now() - created = modified + timedelta(hours=-1) vTodo = icalendar.Todo() vTodo["UID"] = "zogzog" vTodo.add("summary", "new task") From 335e102b0bb65865b5f8c99f8f11bc167ac49882 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Thu, 31 Oct 2024 08:45:30 +0100 Subject: [PATCH 36/44] Fix tests following colored keyword changes --- yokadi/tests/textlistrenderertestcase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yokadi/tests/textlistrenderertestcase.py b/yokadi/tests/textlistrenderertestcase.py index 05d5c9f8..67c2111e 100644 --- a/yokadi/tests/textlistrenderertestcase.py +++ b/yokadi/tests/textlistrenderertestcase.py @@ -41,7 +41,7 @@ def testTitleFormater(self): TEST_DATA = ( (task, 20, "t1"), - (taskWithKeywords, 20, "t2 (key1, key2)"), + (taskWithKeywords, 20, "t2 @key1 @key2"), (taskWithKeywords, 4, "t2 >"), (longTask, 10, longTask.title[:8] + ">*"), (longTask, len(longTask.title), longTask.title[:-2] + ">*"), @@ -78,7 +78,7 @@ def testFullRendering(self): " Foo \n" \ "ID│Title │U │S│Age │Due date\n" \ "──┼───────────────────┼───┼─┼────────┼────────\n" \ - "2 │t2 (k1, k2) │0 │N│0m │ \n" \ + "2 │t2 @k1 @k2 │0 │N│0m │ \n" \ "3 │A longer task name*│0 │N│0m │ \n" self.assertMultiLineEqual(out, expected) From 5ee3ced46ed4cc6123aa5ab884f95c5ce4e20824 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Thu, 31 Oct 2024 08:57:40 +0100 Subject: [PATCH 37/44] Add pre-commit config --- .pre-commit-config.yaml | 32 ++++++++++++++++++++++++++++++++ README.md | 1 - doc/tips.md | 1 - requirements-dev.txt | 1 + scripts/tests | 6 ++++++ 5 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100755 scripts/tests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..febd5ed2 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,32 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + + - repo: local + hooks: + - id: tests + name: tests + entry: bash -c ". .venv/bin/activate && scripts/tests" + language: system + files: \.py$ + pass_filenames: false + + - repo: local + hooks: + - id: lint + name: lint + entry: bash -c ". .venv/bin/activate && scripts/lint" + language: system + files: \.py$ + pass_filenames: false + + # Check GitHub workflows + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.23.1 + hooks: + - id: check-github-workflows diff --git a/README.md b/README.md index 9caa5d7f..e6b32acc 100644 --- a/README.md +++ b/README.md @@ -295,4 +295,3 @@ Other people contributed to Yokadi: - Jonas Christian Drewsen : quarterly recurrence feature - diff --git a/doc/tips.md b/doc/tips.md index 3c387d49..2009839b 100644 --- a/doc/tips.md +++ b/doc/tips.md @@ -70,4 +70,3 @@ shortcuts such as: - ctrl-w delete last word - diff --git a/requirements-dev.txt b/requirements-dev.txt index 792d4b87..7a0c6bd4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,4 @@ coverage==7.6.0 flake8==7.1.0 pytest==8.3.2 +pytest-xdist==3.6.1 diff --git a/scripts/tests b/scripts/tests new file mode 100755 index 00000000..85623f0c --- /dev/null +++ b/scripts/tests @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd $(dirname $0)/.. + +pytest -n auto yokadi/tests/tests.py From 86b5ad55fde70cb507a511bcdf65cbac5eb79aa8 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Thu, 31 Oct 2024 09:01:52 +0100 Subject: [PATCH 38/44] Add markdown linter, and fix Markdown documents --- .markdownlint.yml | 14 ++++++++++++++ .pre-commit-config.yaml | 5 +++++ doc/bugtracking.md | 12 +++++++----- doc/dev/db-updates.md | 2 +- doc/dev/hacking.md | 2 +- doc/ical.md | 18 +++++++++--------- doc/tips.md | 16 +++++++++------- 7 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 .markdownlint.yml diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 00000000..6a3dc502 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,14 @@ +default: true +ul-indent: + indent: 4 + +fenced-code-language: false + +line_length: false +first-line-h1: false +no-trailing-punctuation: false +no-inline-html: false +commands-show-output: false + +# Changelog files are full of duplicate headers +no-duplicate-header: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index febd5ed2..121b8c8b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,6 +7,11 @@ repos: - id: check-yaml - id: check-added-large-files + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.32.2 + hooks: + - id: markdownlint-fix + - repo: local hooks: - id: tests diff --git a/doc/bugtracking.md b/doc/bugtracking.md index 6b929432..ae8826b1 100644 --- a/doc/bugtracking.md +++ b/doc/bugtracking.md @@ -1,11 +1,13 @@ -# Introduction +# Bugtracking + +## Introduction Yokadi comes with a set of commands tailored to help you track bugs. These commands are `bug_add` and `bug_edit`. They are similar to `t_add` and `t_edit` except they will ask you a few questions to help you decide which bug to fix next. -# Entering a bug +## Entering a bug Enter a new bug like you would enter a new task: @@ -48,7 +50,7 @@ If you edit the task with `t_edit 12` you will only be able to fix the task title. To be asked for severity, likelihood and bug id again, use `bug_edit 12`. -# What's next? +## What's next? Based on the severity and likelihood, Yokadi computes the urgency of the bug. The formula used is: @@ -64,7 +66,7 @@ This is based on the concept of "User Pain", as described by Danc here: Now, when you list your tasks with `t_list`, the most urgent tasks will be listed first, making it easy to fix the most important bugs first. -# Behind the scenes +## Behind the scenes Likelihood, severity and bug are stored as Yokadi keywords (Yokadi keywords can be associated with an integer value). @@ -74,7 +76,7 @@ task urgency field. Yes, this means there is duplication and you may get likelihood/severity and urgency out of sync if you manually adjust urgency with `t_set_urgency`. In practice, I found it was not a problem. -# Tricks +## Tricks Here are a few tricks I came up with while using Yokadi to do bug tracking: diff --git a/doc/dev/db-updates.md b/doc/dev/db-updates.md index 0e5ad5cd..36d94953 100644 --- a/doc/dev/db-updates.md +++ b/doc/dev/db-updates.md @@ -8,7 +8,7 @@ The update process goes like this: - Copy yokadi.db to work.db - for each v between x and x + n - 1: - - run `updateto.update()` + - run `updateto.update()` - Create an empty database in recreated.db - Fill recreated.db with the content of work.db - If we are updating the database in place, rename yokadi.db to yokadi-$date.db diff --git a/doc/dev/hacking.md b/doc/dev/hacking.md index 97e466f2..2d49375a 100644 --- a/doc/dev/hacking.md +++ b/doc/dev/hacking.md @@ -81,7 +81,7 @@ Keep import in blocks, in this order: Keep import blocks sorted. It makes it easier to check if an import line is already there. -# Command docstrings +## Command docstrings All commands are documented either through their parser or using the command docstring. To ensure consistency all usage string should follow the same diff --git a/doc/ical.md b/doc/ical.md index b74abd77..d47a3c88 100644 --- a/doc/ical.md +++ b/doc/ical.md @@ -1,4 +1,6 @@ -# Intro +# Ical support + +## Introduction This document presents how to use Yokadi with a third party calendar/todolist application that supports the ical format (RFC2445). @@ -12,7 +14,7 @@ TCP port 9000: yokadid --icalserver --port=9000 -# Read your Yokadi tasks in a third party tool +## Read your Yokadi tasks in a third party tool If your third party tool supports ical format and is able to read it through HTTP, just set it up to read on localhost:8000 (or whatever port you setup) and @@ -24,30 +26,29 @@ If your calendar/todo tool only supports local files: * make a simple shell script that downloads the ical file and put it on your crontab. You can use wget for that: - wget -O yokadi.ical http://localhost:8000 + wget -O yokadi.ical Each Yokadi task is defined as an ical VTODO object. Yokadi projects are represented as special tasks to which included tasks are related. -# Create and update yokadi tasks from a third party tool +## Create and update yokadi tasks from a third party tool On the same TCP socket, you can write tasks with the PUT HTTP method. Only new and updated tasks will be considered. -# Supported third party ical tool +## Supported third party ical tool Yokadi should support any tool which implements RFC2345. But we are not in a perfect world. The following tools are known to work properly with Yokadi ical server: -- Kontact/KOrganizer (4.4) from the KDE Software Compilation +* Kontact/KOrganizer (4.4) from the KDE Software Compilation If you successfully plugged Yokadi with another calendar/todolist tool, please let us now in order to complete this list. - -# Some security considerations +## Some security considerations By default, the ical server only listens on localhost (loopback). You can bypass this restriction with the --listen switch which makes the ical server @@ -64,5 +65,4 @@ restrict who can access to your Yokadi daemon: You have been warned. That's why listening only to localhost (which is the default) is strongly recommended. - diff --git a/doc/tips.md b/doc/tips.md index 2009839b..13e899a9 100644 --- a/doc/tips.md +++ b/doc/tips.md @@ -1,13 +1,15 @@ -# Intro +# Tips + +## Introduction This document presents practical advices on how to get the best out of Yokadi. -# Completion +## Completion Yokadi supports completion of command names, and in many commands it can complete project names. Do not hesitate to try the `[tab]` key! -# Setting up a project hierarchy +## Setting up a project hierarchy You can set up a project hierarchy by adopting a name convention. For example if you want to track tasks related to a program which is made of many plugins, you @@ -20,7 +22,7 @@ For example to list all `fooplayer` related tasks you can use: t_list fooplayer% -# Using keywords +## Using keywords Keywords are great to group tasks in different ways. For example you can create a keyword named `phone`, and assign it to tasks which you must accomplish on @@ -36,13 +38,13 @@ Or even nicer, directly print your list (from the shell): yokadi "t_list @diy_store --format plain" | lp -# Keep track of your meetings +## Keep track of your meetings To track my meetings, I like to use a `meeting` keyword together with an assigned due date. Yokadi ability to add long descriptions to tasks is also handy to associate address or contact information to a meeting task. -# Keep track of tasks you delegate to people +## Keep track of tasks you delegate to people When you delegate a task to someone, add a keyword with its name to the task. So you can check that people really do what they promise to do even if they @@ -56,7 +58,7 @@ To check all task that Bob should have done: t_list --overdue @bob -# Some useful shortcuts +## Some useful shortcuts Yokadi relies on readline library, so you can use very useful readline shortcuts such as: From 582fc95a7f1d2f42763ebeceadb9b226f9b882db Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 4 Nov 2024 08:32:01 +0100 Subject: [PATCH 39/44] Fix unset release date --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 52444bdf..42cd3660 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -v1.2.0 unreleased +v1.2.0 2019/02/10 - New features: - The new `p_merge` command lets you merge a project into another. - It is now possible to turn a task into a note with `t_to_note` and a note into a task with `n_to_task`. From fbfa27d7f4a57d6270d8d19c184f5548cbb4177a Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 4 Nov 2024 08:38:00 +0100 Subject: [PATCH 40/44] Turn NEWS into CHANGELOG.md --- NEWS => CHANGELOG.md | 42 ++++++++++++++++++++++++++---------------- doc/dev/release.md | 2 +- 2 files changed, 27 insertions(+), 17 deletions(-) rename NEWS => CHANGELOG.md (94%) diff --git a/NEWS b/CHANGELOG.md similarity index 94% rename from NEWS rename to CHANGELOG.md index 42cd3660..5074182c 100644 --- a/NEWS +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ -v1.2.0 2019/02/10 +# Changelog + +## 1.2.0 - 2019-02-10 + - New features: - The new `p_merge` command lets you merge a project into another. - It is now possible to turn a task into a note with `t_to_note` and a note into a task with `n_to_task`. @@ -18,7 +21,7 @@ v1.2.0 2019/02/10 - Similarly, the `YOKADI_DB` environment variable is now deprecated and will be removed in the next version. - Yokadi no longer supports cryptography: encrypted databases will be decrypted at update. -v1.1.1 2016/11/11 +## 1.1.1 - 2016-11-11 - Improvements: - When listing multiple projects, order them alphabetically. @@ -27,7 +30,7 @@ v1.1.1 2016/11/11 - When the user edits a tasks with t_edit and removes a keyword, remove the keyword from the task. - Made recurrence code work with dateutil 2.6.0. -v1.1.0 2016/09/03 +## 1.1.0 - 2016-09-03 - New features & Improvements: - A new command has been added: `t_medit`. `t_medit` lets you edit all tasks of a project in one go. @@ -44,13 +47,13 @@ v1.1.0 2016/09/03 - Fixed `bug_edit` crash. - Fixed negative keyword filter: A task with two keywords k1 and k2 would not be excluded by a filter !k1. -v1.0.2 2016/03/28 +## 1.0.2 - 2016-03-28 - Use a more portable way to get the terminal size. This makes it possible to use Yokadi inside Android terminal emulators like Termux - Sometimes the task lock used to prevent editing the same task description from multiple Yokadi instances were not correctly released - Deleting a keyword from the database caused a crash when a t_list returned tasks which previously contained this keyword -v1.0.1 2015/12/03 +## 1.0.1 - 2015-12-03 - User changes: - Make sure installing via pip installs the required dependencies @@ -58,7 +61,7 @@ v1.0.1 2015/12/03 - Developer changes: - Improved release process -v1.0.0 2015/11/29 +## 1.0.0 - 2015-11-29 - User changes: - Fixed an issue which caused t_list to fail when filtering by keywords on large lists @@ -70,7 +73,8 @@ v1.0.0 2015/11/29 - Yokadi has been ported to Python 3 - The application now uses SQLAlchemy instead of SQLObject to access the SQLite database -v0.14 2014/05/03 +## 0.14 - 2014-05-03 + - Command changes: - t_add, n_add: - Allow creating two tasks with the same title (useful for recurrent tasks, like "buy bread"). @@ -116,19 +120,23 @@ v0.14 2014/05/03 - The scripts in bin/ are now smart enough to run the source tree version instead of the installed version if possible. - We now use Travis for continuous integration. -v0.13 2011/04/09 +## 0.13 - 2011-04-09 + - cryptographic support to encrypt tasks title and description - t_apply now accept id range (x-y) - Special keyword __ can used in t_apply to affect all tasks previously select by t_list -v0.12 2010/07/06 +## 0.12 - 2010-07-06 + - Negative keyword support. Ex.: t_list !@home - Permanent filters on keyword or project. 't_filter @foo' will filter any further call to t_list on @foo keyword. -v0.11.1 2009/11/02 +## 0.11.1 - 2009-11-02 + - yokadi symlink (useful to run yokadi without installing it) was broken -v0.11 2009/11/01 +## 0.11 - 2009-11-01 + - dynamic display width according to user terminal - display keywords in t_list - bugs keywords are prefixed with a '_' to distinguish them from user keywords @@ -138,12 +146,13 @@ v0.11 2009/11/01 - custom aliases can be defined for all commands with a_add - switch from GPL 3 to GPL v3 or newer license -v0.10 2009/07/08 +## 0.10 - 2009-07-08 + - ability to assign keywords to a project - shortened some commands (old ones still available but deprecated): - o t_set_due => t_due - o t_set_project => t_project - o t_set_urgency => t_urgency + - t_set_due => t_due + - t_set_project => t_project + - t_set_urgency => t_urgency - changed keyword syntax: use @foo instead of -k foo - added t_recurs command to define task recursion (weekly, monthly, yearly...) - added full text search with t_list -s foo @@ -152,5 +161,6 @@ v0.10 2009/07/08 - added Windows support - fixed install script to be more friendly to both users and packagers -v0.9 2009/02/07 +## 0.9 - 2009-02-07 + First public release. Fully usable for home and work. diff --git a/doc/dev/release.md b/doc/dev/release.md index 189bc96b..31559954 100644 --- a/doc/dev/release.md +++ b/doc/dev/release.md @@ -15,7 +15,7 @@ Check dev is clean git pull git status -Update `NEWS` file (add changes, check release date) +Update `CHANGELOG.md` file (add changes, check release date) Ensure `yokadi/__init__.py` file contains $version From 2ba1ab11a669f0b53993b484ab1775eb5313e127 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 4 Nov 2024 08:42:31 +0100 Subject: [PATCH 41/44] Create h3 sections in changelog --- CHANGELOG.md | 212 ++++++++++++++++++++++++++++----------------------- 1 file changed, 117 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5074182c..ae46dfda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,50 +2,63 @@ ## 1.2.0 - 2019-02-10 -- New features: - - The new `p_merge` command lets you merge a project into another. - - It is now possible to turn a task into a note with `t_to_note` and a note into a task with `n_to_task`. -- Bug fixes: - - The `k_remove` command no longer ignores unused keywords. - - HTML output has been fixed to no longer output strings wrapped in `b""`. - - `t_list` filtering has been fixed so that `t_list --urgency 0` filters out tasks with a negative urgency, as expected. -- Improvements: - - HTML output has been refreshed: - - It looks more modern now. - - Some fields have been removed (doneDate, creationDate). - - The title, keywords and description fields have been merged. - - An ID field has been added (handy to run a command on a task listed in the output). - - Columns now use human-friendly titles. -- Misc: - - The `--db` option is now deprecated and replaced by the `--datadir` option. `--db` will be removed in the next version. - - Similarly, the `YOKADI_DB` environment variable is now deprecated and will be removed in the next version. - - Yokadi no longer supports cryptography: encrypted databases will be decrypted at update. +### New features + +- The new `p_merge` command lets you merge a project into another. +- It is now possible to turn a task into a note with `t_to_note` and a note into a task with `n_to_task`. + +### Bug fixes + +- The `k_remove` command no longer ignores unused keywords. +- HTML output has been fixed to no longer output strings wrapped in `b""`. +- `t_list` filtering has been fixed so that `t_list --urgency 0` filters out tasks with a negative urgency, as expected. + +### Improvements + +- HTML output has been refreshed: + - It looks more modern now. + - Some fields have been removed (doneDate, creationDate). + - The title, keywords and description fields have been merged. + - An ID field has been added (handy to run a command on a task listed in the output). + - Columns now use human-friendly titles. + +### Misc + +- The `--db` option is now deprecated and replaced by the `--datadir` option. `--db` will be removed in the next version. +- Similarly, the `YOKADI_DB` environment variable is now deprecated and will be removed in the next version. +- Yokadi no longer supports cryptography: encrypted databases will be decrypted at update. ## 1.1.1 - 2016-11-11 -- Improvements: - - When listing multiple projects, order them alphabetically. -- Bug fixes: - - Fixed parse error if the user sets a time of "17m". - - When the user edits a tasks with t_edit and removes a keyword, remove the keyword from the task. - - Made recurrence code work with dateutil 2.6.0. +### Improvements + +- When listing multiple projects, order them alphabetically. + +### Bug fixes + +- Fixed parse error if the user sets a time of "17m". +- When the user edits a tasks with t_edit and removes a keyword, remove the keyword from the task. +- Made recurrence code work with dateutil 2.6.0. ## 1.1.0 - 2016-09-03 -- New features & Improvements: - - A new command has been added: `t_medit`. `t_medit` lets you edit all tasks of a project in one go. - - Aliases can now be modified. The name of the alias can be modified with `a_edit_name` and the command with `a_edit_command`. - - Database format updates are now easier to run: just run `yokadi -u`, no more separate `update.py` command. Updates are also much faster. - - Task lists have been improved: - - Borders look nicer. - - Some bugs in the rendering of the title column have been fixed (wrong width, badly cropped text). - - Yokadi now uses standard paths by default: the database is stored in ~/.local/share/yokadi/yokadi.db and non-essential data is in ~/.cache/yokadi/. - - Reviewed and improved documentation. Moved developer documentation to a separate dir (doc/dev). -- Bug fixes: - - The code handling recurrences has been made more robust. - - Recurrences are now stored in a more future proof way. - - Fixed `bug_edit` crash. - - Fixed negative keyword filter: A task with two keywords k1 and k2 would not be excluded by a filter !k1. +### New features & Improvements + +- A new command has been added: `t_medit`. `t_medit` lets you edit all tasks of a project in one go. +- Aliases can now be modified. The name of the alias can be modified with `a_edit_name` and the command with `a_edit_command`. +- Database format updates are now easier to run: just run `yokadi -u`, no more separate `update.py` command. Updates are also much faster. +- Task lists have been improved: + - Borders look nicer. + - Some bugs in the rendering of the title column have been fixed (wrong width, badly cropped text). +- Yokadi now uses standard paths by default: the database is stored in ~/.local/share/yokadi/yokadi.db and non-essential data is in ~/.cache/yokadi/. +- Reviewed and improved documentation. Moved developer documentation to a separate dir (doc/dev). + +### Bug fixes + +- The code handling recurrences has been made more robust. +- Recurrences are now stored in a more future proof way. +- Fixed `bug_edit` crash. +- Fixed negative keyword filter: A task with two keywords k1 and k2 would not be excluded by a filter !k1. ## 1.0.2 - 2016-03-28 @@ -55,70 +68,79 @@ ## 1.0.1 - 2015-12-03 -- User changes: - - Make sure installing via pip installs the required dependencies +### User changes + +- Make sure installing via pip installs the required dependencies -- Developer changes: - - Improved release process +### Developer changes + +- Improved release process ## 1.0.0 - 2015-11-29 -- User changes: - - Fixed an issue which caused t_list to fail when filtering by keywords on large lists - - Removed the project keywords feature. It was not very useful and made the searching code more complicated - - Fixed ical support: it now works with ical 3.6 or later - - Improved documentation - - Added Keywords field to yokadi.desktop -- Developer changes: - - Yokadi has been ported to Python 3 - - The application now uses SQLAlchemy instead of SQLObject to access the SQLite database +### User changes + +- Fixed an issue which caused t_list to fail when filtering by keywords on large lists +- Removed the project keywords feature. It was not very useful and made the searching code more complicated +- Fixed ical support: it now works with ical 3.6 or later +- Improved documentation +- Added Keywords field to yokadi.desktop + +### Developer changes + +- Yokadi has been ported to Python 3 +- The application now uses SQLAlchemy instead of SQLObject to access the SQLite database ## 0.14 - 2014-05-03 -- Command changes: - - t_add, n_add: - - Allow creating two tasks with the same title (useful for recurrent tasks, like "buy bread"). - - Allow using _ to select last project, making it possible to do multiple t_add on the same project with `t_add _ `. - - Add --describe option to start describing the task right after adding it. - - t_describe, n_describe: - - Safer task description editing: task is updated each time the editor saves, a lock manager now prevents multiple edits. - - Use .md suffix instead of .txt for the temporary filename to allow some smart things with editors that understand Markdown. - - Use project and task name for the temporary filename. Useful when using graphical editors or when your terminal title shows the current running command. - - t_due: - - When called with a time argument which is before current time, set due date to the day after. - - t_show: - - Show the task ID. - - t_list: - - Use month and year for the task age if the task is older than 12 months. - - Add support for arbitrary minimum date for --done. - - Fixed broken help. - - n_list: - - Display creation date instead of age. - - Notes are now grouped by date. - - p_list: - - Show task count per project. - - p_remove: - - Show the number of associated tasks in the prompt. - - p_edit: - - Handle case where user tries to rename a project using the name of an existing project. - -- yokadid: - - Add --restart option and --log option. - - Set process name with setproctitle. - - Configuration keys can now be overridden using environment variables. - -- Misc: - - Date/time commands now support %d/%m/%y date format. - - Replaced xyokadi with a desktop file. - - Updated README to match real output. - -- Developer specific changes: - - Command parser has been ported from optparse to argparse. - - Code is now PEP 8 compliant, with the exception of camelCase usage. - - All imports have been changed to absolute imports (ie `import yokadi.`). - - Code has been reorganized into different sub directories. - - The scripts in bin/ are now smart enough to run the source tree version instead of the installed version if possible. - - We now use Travis for continuous integration. +### Command changes + +- t_add, n_add: + - Allow creating two tasks with the same title (useful for recurrent tasks, like "buy bread"). + - Allow using _ to select last project, making it possible to do multiple t_add on the same project with `t_add _ `. + - Add --describe option to start describing the task right after adding it. +- t_describe, n_describe: + - Safer task description editing: task is updated each time the editor saves, a lock manager now prevents multiple edits. + - Use .md suffix instead of .txt for the temporary filename to allow some smart things with editors that understand Markdown. + - Use project and task name for the temporary filename. Useful when using graphical editors or when your terminal title shows the current running command. +- t_due: + - When called with a time argument which is before current time, set due date to the day after. +- t_show: + - Show the task ID. +- t_list: + - Use month and year for the task age if the task is older than 12 months. + - Add support for arbitrary minimum date for --done. + - Fixed broken help. +- n_list: + - Display creation date instead of age. + - Notes are now grouped by date. +- p_list: + - Show task count per project. +- p_remove: + - Show the number of associated tasks in the prompt. +- p_edit: + - Handle case where user tries to rename a project using the name of an existing project. + +### yokadid + +- Add --restart option and --log option. +- Set process name with setproctitle. +- Configuration keys can now be overridden using environment variables. + +### Misc + +- Date/time commands now support %d/%m/%y date format. +- Replaced xyokadi with a desktop file. +- Updated README to match real output. + +### Developer specific changes + +- Command parser has been ported from optparse to argparse. +- Code is now PEP 8 compliant, with the exception of camelCase usage. +- All imports have been changed to absolute imports (ie `import yokadi.`). +- Code has been reorganized into different sub directories. +- The scripts in bin/ are now smart enough to run the source tree version instead of the installed version if possible. +- We now use Travis for continuous integration. ## 0.13 - 2011-04-09 From 3d67ecc54fd0d676b4c6d20b0dbfa2a6b3ef0291 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 4 Nov 2024 08:45:28 +0100 Subject: [PATCH 42/44] More changelog formatting --- CHANGELOG.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae46dfda..d5c2bd3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -129,7 +129,7 @@ ### Misc -- Date/time commands now support %d/%m/%y date format. +- Date/time commands now support `%d/%m/%y` date format. - Replaced xyokadi with a desktop file. - Updated README to match real output. @@ -144,14 +144,14 @@ ## 0.13 - 2011-04-09 -- cryptographic support to encrypt tasks title and description -- t_apply now accept id range (x-y) -- Special keyword __ can used in t_apply to affect all tasks previously select by t_list +- cryptographic support to encrypt tasks title and description. +- t_apply now accept id range (x-y). +- Special keyword `__` can used in t_apply to affect all tasks previously select by t_list. ## 0.12 - 2010-07-06 -- Negative keyword support. Ex.: t_list !@home -- Permanent filters on keyword or project. 't_filter @foo' will filter any further call to t_list on @foo keyword. +- Negative keyword support. Ex.: `t_list !@home` +- Permanent filters on keyword or project. `t_filter @foo` will filter any further call to t_list on @foo keyword. ## 0.11.1 - 2009-11-02 @@ -161,10 +161,10 @@ - dynamic display width according to user terminal - display keywords in t_list -- bugs keywords are prefixed with a '_' to distinguish them from user keywords +- bugs keywords are prefixed with a `_` to distinguish them from user keywords - YOKADI_DB environment variable can be defined to set default yokadi database path - tasks can be grouped by keyword instead of project -- special character _ can be used to represent last task id +- special character `_` can be used to represent last task id - custom aliases can be defined for all commands with a_add - switch from GPL 3 to GPL v3 or newer license @@ -172,12 +172,12 @@ - ability to assign keywords to a project - shortened some commands (old ones still available but deprecated): - - t_set_due => t_due - - t_set_project => t_project - - t_set_urgency => t_urgency -- changed keyword syntax: use @foo instead of -k foo + - `t_set_due` => `t_due` + - `t_set_project` => `t_project` + - `t_set_urgency` => `t_urgency` +- changed keyword syntax: use `@foo` instead of `-k foo` - added t_recurs command to define task recursion (weekly, monthly, yearly...) -- added full text search with t_list -s foo +- added full text search with `t_list -s foo` - enhanced t_list display - added purge command (t_purge) to remove old tasks - added Windows support From a3c6a2d70e4bd5f281a8714dcf20a525f566f3bc Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Mon, 4 Nov 2024 19:04:05 +0100 Subject: [PATCH 43/44] Prepare 1.3.0 --- CHANGELOG.md | 6 ++++++ MANIFEST.in | 4 ++-- scripts/mkdist.sh | 18 ++++++++++++------ setup.py | 2 +- yokadi/__init__.py | 2 +- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5c2bd3d..dde39b3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 1.3.0 - 2024-11-05 + +- Update SQLAlchemy to 2.0.32. +- Use color to for keywords in tables. +- Fix crash handler failing on Windows. + ## 1.2.0 - 2019-02-10 ### New features diff --git a/MANIFEST.in b/MANIFEST.in index fab47c78..b4782b8e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -9,6 +9,6 @@ include README.md include LICENSE include MANIFEST.in include version -include NEWS +include CHANGELOG.md include *requirements.txt -include .travis.yml +include .github diff --git a/scripts/mkdist.sh b/scripts/mkdist.sh index cdeaeaeb..93ddb5e5 100755 --- a/scripts/mkdist.sh +++ b/scripts/mkdist.sh @@ -17,7 +17,7 @@ log() { SRC_DIR=$(cd "$(dirname $0)/.." ; pwd) DST_DIR=$(cd "$1" ; pwd) -[ -d "$DST_DIR" ] || die "Destination dir '$SRC_DIR' does not exist" +[ -d "$DST_DIR" ] || die "Destination dir '$DST_DIR' does not exist" WORK_DIR=$(mktemp -d "$DST_DIR/yokadi-dist.XXXXXX") @@ -30,7 +30,13 @@ git reset --hard HEAD git clean -q -dxf log "Building archives" -./setup.py -q sdist --formats=gztar,zip +python3 -m venv create "$WORK_DIR/venv" +( + . "$WORK_DIR/venv/bin/activate" + pip install build + python -m build +) +rm -rf "$WORK_DIR/venv" log "Installing archive" cd dist/ @@ -39,18 +45,18 @@ tar xf "$YOKADI_TARGZ" ARCHIVE_DIR="$PWD/${YOKADI_TARGZ%.tar.gz}" -virtualenv --python python3 "$WORK_DIR/venv" +python3 -m venv create "$WORK_DIR/venv" ( . "$WORK_DIR/venv/bin/activate" # Install Yokadi in the virtualenv and make sure it can be started # That ensures dependencies got installed by pip log "Smoke test" - pip3 install "$ARCHIVE_DIR" + pip install "$ARCHIVE_DIR" yokadi exit log "Installing extra requirements" - pip3 install -r "$ARCHIVE_DIR/extra-requirements.txt" + pip install -r "$ARCHIVE_DIR/extra-requirements.txt" log "Running tests" "$ARCHIVE_DIR/yokadi/tests/tests.py" @@ -58,6 +64,6 @@ virtualenv --python python3 "$WORK_DIR/venv" log "Moving archives out of work dir" cd "$WORK_DIR/dist" -mv ./*.tar.gz ./*.zip "$DST_DIR" +mv *.tar.gz *.whl "$DST_DIR" rm -rf "$WORK_DIR" log "Done" diff --git a/setup.py b/setup.py index 6c9d2410..65aadb50 100755 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ def createFileList(sourceDir, *patterns): # Additional files data_files = [] data_files.append(["share/yokadi", - ["README.md", "NEWS", "LICENSE"]]) + ["README.md", "CHANGELOG.md", "LICENSE"]]) # Doc data_files.append(["share/yokadi/doc", createFileList("doc", "*.md")]) diff --git a/yokadi/__init__.py b/yokadi/__init__.py index 808389d2..2e8abc03 100644 --- a/yokadi/__init__.py +++ b/yokadi/__init__.py @@ -6,4 +6,4 @@ @license:GPL v3 or later """ -__version__ = "1.2.0" +__version__ = "1.3.0" From cd0ea7cf3b217b9b4b2b5643bef18b1b4ae5c325 Mon Sep 17 00:00:00 2001 From: Aurelien Gateau Date: Tue, 5 Nov 2024 08:35:01 +0100 Subject: [PATCH 44/44] Update release doc --- doc/dev/release.md | 88 +++++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/doc/dev/release.md b/doc/dev/release.md index 31559954..4fe76c3d 100644 --- a/doc/dev/release.md +++ b/doc/dev/release.md @@ -7,61 +7,83 @@ of yokadi. ## In yokadi checkout - export version= +- [ ] Define version -Check dev is clean + ``` + export version= + ``` - git checkout dev - git pull - git status +- [ ] Check dev is clean -Update `CHANGELOG.md` file (add changes, check release date) + ``` + git checkout dev + git pull + git status + ``` -Ensure `yokadi/__init__.py` file contains $version +- [ ] Update `CHANGELOG.md` file (add changes, check release date) -Build archives +- [ ] Ensure `yokadi/__init__.py` file contains $version - ./scripts/mkdist.sh ../yokadi.github.com/download +- [ ] Build archives -Push changes + ``` + ./scripts/mkdist.sh ../yokadi.github.com/download + ``` - git push +- [ ] Push changes -When CI has checked the branch, merge changes in master + ``` + git push + ``` - git checkout master - git pull - git merge dev - git push +- [ ] When CI has checked the branch, merge changes in master -Tag the release + ``` + git checkout master + git pull + git merge dev + git push + ``` - git tag -a $version -m "Releasing $version" - git push --tags +- [ ] Tag the release + + ``` + git tag -a $version -m "Releasing $version" + git push --tags + ``` ## In yokadi.github.com checkout -Ensure checkout is up to date +- [ ] Ensure checkout is up to date -Update documentation +- [ ] Update documentation - ./updatedoc.py ../yokadi . + ``` + ./updatedoc.py ../yokadi . + ``` -Update version in download page (`download.md`) +- [ ] Update version in download page (`download.md`) -Write a blog entry in `_posts/` +- [ ] Write a blog entry in `_posts/` -Test it: +- [ ] Test it: - jekyll serve + ``` + jekyll serve + ``` -Upload archives on PyPI +- [ ] Upload archives on PyPI - cd download/ - twine upload yokadi-.* + ``` + cd download/ + twine upload yokadi-.* + ``` -Publish blog post +- [ ] Publish blog post - git add . - git commit -m "Releasing $version" - git push + ``` + git add . + git commit -m "Releasing $version" + git push + ```