From 42b590fec00be5d3e54813695520c6238bcb85ba Mon Sep 17 00:00:00 2001 From: Stephen Arnold Date: Sun, 1 Sep 2024 14:08:26 -0700 Subject: [PATCH 1/6] chg: cleanup docstrings and desktop file, update pkg metadata and readme Signed-off-by: Stephen Arnold --- README.rst | 66 ++++++++++++++++------------- data/timew-status-indicator.desktop | 2 +- setup.cfg | 6 ++- src/timew_status/utils.py | 6 ++- 4 files changed, 46 insertions(+), 34 deletions(-) diff --git a/README.rst b/README.rst index d9bc9a3..91e728a 100644 --- a/README.rst +++ b/README.rst @@ -133,36 +133,42 @@ generated python byte-code. In the latter case, the list of installed files can be obtained with the following command:: $ python -m pip show -f timew-addons - Name: timew-addons - Version: 0.1.2.dev3+gda11428.d20240825 - Summary: A collection of timewarrior extensions and experiments - Home-page: https://github.com/sarnold/timew-addons - Author: Stephen L Arnold - Author-email: - License: - Location: /home/user/src/timew-addons/.tox/check/lib/python3.11/site-packages - Requires: munch, pycairo, PyGObject, timew-report - Required-by: - Files: - ../../../bin/timew-status-indicator - ../../../share/applications/timew-status-indicator.desktop - ../../../share/icons/hicolor/48x48/apps/timew.png - ../../../share/icons/hicolor/scalable/apps/timew.svg - ../../../share/icons/hicolor/scalable/status/timew_error.svg - ../../../share/icons/hicolor/scalable/status/timew_inactive.svg - ../../../share/icons/hicolor/scalable/status/timew_info.svg - ../../../share/icons/hicolor/scalable/status/timew_warning.svg - ../../../share/timew-addons/extensions/csv_rpt.py - ../../../share/timew-addons/extensions/onelineday.py - ../../../share/timew-addons/extensions/totals.py - timew_addons-0.1.2.dev3+gda11428.d20240825.dist-info/INSTALLER - timew_addons-0.1.2.dev3+gda11428.d20240825.dist-info/METADATA - timew_addons-0.1.2.dev3+gda11428.d20240825.dist-info/RECORD - timew_addons-0.1.2.dev3+gda11428.d20240825.dist-info/REQUESTED - timew_addons-0.1.2.dev3+gda11428.d20240825.dist-info/WHEEL - timew_addons-0.1.2.dev3+gda11428.d20240825.dist-info/top_level.txt - timew_status/__init__.py - timew_status/utils.py + Name: timew-addons + Version: 0.2.2.dev0+g4659e21.d20240901 + Summary: A collection of timewarrior extensions and experiments + Home-page: https://github.com/sarnold/timew-addons + Author: Stephen L Arnold + Author-email: nerdboy@gentoo.org + License: GPLv3+ + Location: /home/nerdboy/src/timew-addons/.tox/py/lib/python3.11/site-packages + Requires: munch, pycairo, PyGObject, timew-report + Required-by: + Files: + ../../../bin/timew-status-indicator + ../../../share/applications/timew-status-indicator.desktop + ../../../share/icons/hicolor/48x48/apps/timew.png + ../../../share/icons/hicolor/scalable/apps/timew.svg + ../../../share/icons/hicolor/scalable/status/timew_error.svg + ../../../share/icons/hicolor/scalable/status/timew_inactive.svg + ../../../share/icons/hicolor/scalable/status/timew_info.svg + ../../../share/icons/hicolor/scalable/status/timew_warning.svg + ../../../share/timew-addons/extensions/__pycache__/csv_rpt.cpython-311.pyc + ../../../share/timew-addons/extensions/__pycache__/onelineday.cpython-311.pyc + ../../../share/timew-addons/extensions/__pycache__/totals.cpython-311.pyc + ../../../share/timew-addons/extensions/csv_rpt.py + ../../../share/timew-addons/extensions/onelineday.py + ../../../share/timew-addons/extensions/totals.py + timew_addons-0.2.2.dev0+g4659e21.d20240901.dist-info/INSTALLER + timew_addons-0.2.2.dev0+g4659e21.d20240901.dist-info/METADATA + timew_addons-0.2.2.dev0+g4659e21.d20240901.dist-info/RECORD + timew_addons-0.2.2.dev0+g4659e21.d20240901.dist-info/REQUESTED + timew_addons-0.2.2.dev0+g4659e21.d20240901.dist-info/WHEEL + timew_addons-0.2.2.dev0+g4659e21.d20240901.dist-info/direct_url.json + timew_addons-0.2.2.dev0+g4659e21.d20240901.dist-info/top_level.txt + timew_status/__init__.py + timew_status/__pycache__/__init__.cpython-311.pyc + timew_status/__pycache__/utils.cpython-311.pyc + timew_status/utils.py Generated files --------------- diff --git a/data/timew-status-indicator.desktop b/data/timew-status-indicator.desktop index e03363d..e99f1ec 100644 --- a/data/timew-status-indicator.desktop +++ b/data/timew-status-indicator.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Name=Timew Status Tool +Name=Timew Status Indicator Comment=Control and status tool for timew Exec=timew-status-indicator Icon=timew diff --git a/setup.cfg b/setup.cfg index 343dfcd..66a5f0f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,9 +4,10 @@ version = attr: setuptools_scm.get_version description = A collection of timewarrior extensions and experiments url = https://github.com/sarnold/timew-addons author = Stephen L Arnold -email = nerdboy@gentoo.org +author_email = nerdboy@gentoo.org long_description = file: README.rst long_description_content_type = text/rst; charset=UTF-8 +license = GPLv3+ license_expression = GPL-3.0-or-later license_files = ["LICEN[CS]E*",] classifiers = @@ -24,6 +25,9 @@ classifiers = keywords = appindicator + extensions + timewarrior + timew-report timew [options] diff --git a/src/timew_status/utils.py b/src/timew_status/utils.py index 1afb845..7d0e0fd 100644 --- a/src/timew_status/utils.py +++ b/src/timew_status/utils.py @@ -32,10 +32,12 @@ def do_install(cfg): """ - Install report extensions to timew extensions directory. The default + Install report extensions to timew extensions directory. The default src paths are preconfigured and should probably not be changed unless you know what you are doing, since *they are created during install or setup*. - Return a destination path string for each installed extension script. + You should, however, adjust the destination path in ``extensions_dir`` if + needed for your platform. Returns the destination path string for each + installed extension script. :param cfg: runtime CFG dict :return files: list of strings From 26d5dc75dbb9e10e625341ecd015ba23c9df527f Mon Sep 17 00:00:00 2001 From: Stephen Arnold Date: Sun, 1 Sep 2024 16:48:37 -0700 Subject: [PATCH 2/6] fix: refactoring cleanup, add type hints/ignores and fix an error * mypy helped find an error, but we still need to ignore the optional munch YAML attributes * update flake8 config bits to allow one more char of line length Signed-off-by: Stephen Arnold --- .pep8speaks.yml | 2 +- .pre-commit-config.yaml | 24 ++++++++++++++---------- setup.cfg | 2 +- src/timew_status/utils.py | 36 +++++++++++++++++++++--------------- tox.ini | 3 +++ 5 files changed, 40 insertions(+), 27 deletions(-) diff --git a/.pep8speaks.yml b/.pep8speaks.yml index 04ed79e..d0287a9 100644 --- a/.pep8speaks.yml +++ b/.pep8speaks.yml @@ -11,7 +11,7 @@ exclude = build, dist -max-line-length = 99 +max-line-length = 100 ignore = # too many leading '#' for block comment diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b8f9a0a..3a91850 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: - id: check-useless-excludes - id: check-hooks-apply - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.6.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -28,31 +28,35 @@ repos: - id: check-toml - repo: https://github.com/ambv/black - rev: 23.3.0 + rev: 24.8.0 hooks: - id: black name: "Format code" language_version: python3 - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort args: [--settings-path=pyproject.toml] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.0 + rev: v1.11.2 hooks: - id: mypy args: - --follow-imports=normal - --install-types - --non-interactive - - --ignore-missing-imports - #files: src/ + - --implicit-reexport + additional_dependencies: + - "PyYAML" + - "munch" + - "munch-stubs" + #files: src/ - repo: "https://github.com/asottile/blacken-docs" - rev: "1.15.0" + rev: "1.18.0" hooks: - id: "blacken-docs" name: "Format docs (blacken-docs)" @@ -76,7 +80,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/myint/autoflake - rev: v2.2.0 + rev: v2.3.1 hooks: - id: autoflake #files: src/ @@ -87,14 +91,14 @@ repos: - --remove-unused-variables - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 + rev: 7.1.1 hooks: - id: flake8 #files: src/ additional_dependencies: ["flake8-bugbear"] - repo: https://github.com/PyCQA/bandit - rev: 1.7.5 + rev: 1.7.9 hooks: - id: bandit args: ["-ll", "-q"] diff --git a/setup.cfg b/setup.cfg index 66a5f0f..a343d1a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -111,7 +111,7 @@ exclude = docs, tests -max-line-length = 99 +max-line-length = 100 max-complexity = 10 addons = file,open,basestring,xrange,unicode,long,cmp ignore = diff --git a/src/timew_status/utils.py b/src/timew_status/utils.py index 7d0e0fd..91dd318 100644 --- a/src/timew_status/utils.py +++ b/src/timew_status/utils.py @@ -1,13 +1,17 @@ """ Base configuration and app helper functions. """ + +import datetime import os import subprocess from datetime import timedelta from pathlib import Path +from typing import Dict, NewType, Optional from munch import Munch +TimeDelta = NewType("TimeDelta", datetime.timedelta) APP_NAME = 'timew_status_indicator' CFG = { # time strings are HH:MM (no seconds) @@ -30,7 +34,7 @@ } -def do_install(cfg): +def do_install(cfg: Dict): """ Install report extensions to timew extensions directory. The default src paths are preconfigured and should probably not be changed unless you @@ -60,7 +64,7 @@ def do_install(cfg): return files -def get_config(file_encoding='utf-8'): +def get_config(file_encoding: str = 'utf-8'): """ Load configuration file and munchify the data. If local file is not found in config directory, the default will be loaded and saved to @@ -74,14 +78,14 @@ def get_config(file_encoding='utf-8'): cfgdir = get_userdirs() cfgfile = cfgdir.joinpath('config.yaml') if not cfgfile.exists(): - print(f"Saving initial config data to {cfgfile}") - cfgfile.write_text(Munch.toYAML(CFG), encoding=file_encoding) - cfgobj = Munch.fromYAML(cfgfile.read_text(encoding=file_encoding)) - + print(f"Saving initial config data to {cfgfile}") # fmt: off + cfgfile.write_text(Munch.toYAML(CFG), encoding=file_encoding) # type: ignore[attr-defined] + cfgobj = Munch.fromYAML(cfgfile.read_text(encoding=file_encoding)) # type: ignore[attr-defined] + # fmt: on return cfgobj, cfgfile -def get_delta_limits(ucfg): +def get_delta_limits(ucfg: Dict): """ Return config max/snooze limits as timedeltas. Everything comes from static config values and gets padded with seconds. @@ -101,7 +105,7 @@ def get_delta_limits(ucfg): return day_max, day_limit, seat_max, seat_limit -def get_state_icon(state, cfg): +def get_state_icon(state: str, cfg: Dict): """ Look up the state msg and return the icon name. Use builtin symbolic icons as fallback. @@ -139,7 +143,9 @@ def get_state_icon(state, cfg): return state_dict.get(state, state_dict['INACTIVE']) -def get_state_str(cmproc, count, cfg): +def get_state_str( + cmproc: subprocess.CompletedProcess[bytes], count: TimeDelta, cfg: Dict +): """ Return timew state message and tracking state, ie, the key for dict with icons. @@ -207,7 +213,7 @@ def get_userdirs(): return configdir -def parse_for_tag(text): +def parse_for_tag(text: str): """ Parse the output of timew start/stop commands for the tag string. @@ -219,17 +225,17 @@ def parse_for_tag(text): return line.split('"')[1] -def run_cmd(action='status', tag=None): +def run_cmd(cfg: Dict, action: str = 'status', tag: Optional[str] = None): """ Run timew command subject to the given action. :param action: one of :return: completed proc obj and result msg """ - + extension = cfg["extension_script"] actions = ['start', 'stop', 'status'] svc_list = ['timew'] - sts_list = [CFG["extension_script"], "today"] + sts_list = [extension, "today"] cmd = svc_list act_list = [action] @@ -261,12 +267,12 @@ def run_cmd(action='status', tag=None): print(f'run_cmd exception: {exc}') -def to_td(hms): +def to_td(hms: str): """ Convert a time string in HH:MM:SS format to a timedelta object. :param hms: time string - :return: timedelta obj + :return: timedelta """ hrs, mins, secs = hms.split(':') return timedelta(hours=int(hrs), minutes=int(mins), seconds=int(secs)) diff --git a/tox.ini b/tox.ini index 13e43e1..1d78974 100644 --- a/tox.ini +++ b/tox.ini @@ -241,6 +241,9 @@ setenv = PYTHONPATH = {toxinidir} deps = pip>=23.1 + munch[yaml] + munch-stubs + PyYAML timew-report mypy From eb89737da38f144dc45a498f12c93468777ac742 Mon Sep 17 00:00:00 2001 From: Stephen Arnold Date: Sun, 1 Sep 2024 17:55:45 -0700 Subject: [PATCH 3/6] fix: complete error fix, more import cleanup, add helper func and a test * this commit removes support for legacy AppIndicator3 in favor of AyatanaAppIndicator3 only Signed-off-by: Stephen Arnold --- .pre-commit-config.yaml | 2 ++ scripts/timew-status-indicator | 19 +++++++------------ src/timew_status/utils.py | 20 +++++++++++++++++++- tests/test_utils.py | 9 +++++++++ 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3a91850..561e97a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -53,6 +53,8 @@ repos: - "PyYAML" - "munch" - "munch-stubs" + - "PyGObject" + - "PyGObject-stubs" #files: src/ - repo: "https://github.com/asottile/blacken-docs" diff --git a/scripts/timew-status-indicator b/scripts/timew-status-indicator index 217c47a..5b888e4 100755 --- a/scripts/timew-status-indicator +++ b/scripts/timew-status-indicator @@ -15,19 +15,14 @@ from threading import Thread from typing import cast import gi +from munch import Munch gi.require_version("Gtk", "3.0") gi.require_version('Notify', '0.7') +gi.require_version('AyatanaAppIndicator3', '0.1') -try: - gi.require_version('AyatanaAppIndicator3', '0.1') - from gi.repository import AyatanaAppIndicator3 as appindicator -except ValueError: - gi.require_version('AppIndicator3', '0.1') - from gi.repository import AppIndicator3 as appindicator - +from gi.repository import AyatanaAppIndicator3 as appindicator from gi.repository import Gdk, Gtk, Notify -from munch import Munch from timew_status import ( CFG, @@ -123,7 +118,7 @@ class Indicator: new_state = 'INACTIVE' print(f'{new_state} state msg: {result.stdout.decode().strip()}') else: - proc, _ = run_cmd() + proc, _ = run_cmd(CFG) msg, new_state = get_state_str(proc, current_tick_count, CFG) print(f'{new_state} state msg: {msg.strip()}') # if there is a change in state, update the icon @@ -238,18 +233,18 @@ class Indicator: def startd(self, source): my_tag = TAG["text"] - _, svc_msg = run_cmd(action='start', tag=my_tag) + _, svc_msg = run_cmd(CFG, action='start', tag=my_tag) self.indicator.set_icon_full(get_state_icon('ACTIVE', CFG), 'ACTIVE') Notify.Notification.new("Timew status", svc_msg, None).show() def statusd(self, source): - _, svc_msg = run_cmd() + _, svc_msg = run_cmd(CFG) seat_minutes = round(Decimal(COUNT["SeatTick"] / 60), 1) svc_msg = f'INFO: current seat time is {seat_minutes} mins \n' + svc_msg Notify.Notification.new("Timew status", svc_msg, None).show() def stopd(self, source): - _, svc_msg = run_cmd(action='stop') + _, svc_msg = run_cmd(CFG, action='stop') if svc_msg: self.last_tag = svc_msg if CFG['use_last_tag']: diff --git a/src/timew_status/utils.py b/src/timew_status/utils.py index 91dd318..d7e2011 100644 --- a/src/timew_status/utils.py +++ b/src/timew_status/utils.py @@ -5,8 +5,10 @@ import datetime import os import subprocess +import sys from datetime import timedelta from pathlib import Path +from shutil import which from typing import Dict, NewType, Optional from munch import Munch @@ -34,6 +36,21 @@ } +def check_for_timew(): + """ + Make sure we can find the ``timew`` binary in the user environment + and return a path string. + + :return timew_path: program path strings + :rtype tuple: path to program if found, else None + """ + timew_path = which('timew') + if not timew_path: + print('Cannot continue, no path found for timew') + sys.exit(1) + return timew_path + + def do_install(cfg: Dict): """ Install report extensions to timew extensions directory. The default src @@ -232,9 +249,10 @@ def run_cmd(cfg: Dict, action: str = 'status', tag: Optional[str] = None): :param action: one of :return: completed proc obj and result msg """ + timew_cmd = check_for_timew() extension = cfg["extension_script"] actions = ['start', 'stop', 'status'] - svc_list = ['timew'] + svc_list = [timew_cmd] sts_list = [extension, "today"] cmd = svc_list act_list = [action] diff --git a/tests/test_utils.py b/tests/test_utils.py index 6093eb9..7d93b08 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -3,6 +3,7 @@ from munch import Munch from timew_status.utils import ( + check_for_timew, do_install, get_config, get_delta_limits, @@ -38,6 +39,14 @@ """ +def test_timew_check(): + timew = check_for_timew() + print(timew) + assert 'timew' in timew + assert 'bin' in timew + assert isinstance(timew, str) + + def test_do_install(script_loc, tmpdir_session): cfg = Munch.fromDict(CFG) # destination dir From 5e4f985be8a1208350cdbd530dbe2e8c6df4a1e9 Mon Sep 17 00:00:00 2001 From: Stephen Arnold Date: Sun, 1 Sep 2024 18:22:47 -0700 Subject: [PATCH 4/6] chg: use future annotations workaround on python 3.8 * the above import trick is correct for ``get_state_str()`` signature Signed-off-by: Stephen Arnold --- src/timew_status/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/timew_status/utils.py b/src/timew_status/utils.py index d7e2011..bcb1b38 100644 --- a/src/timew_status/utils.py +++ b/src/timew_status/utils.py @@ -2,6 +2,8 @@ Base configuration and app helper functions. """ +from __future__ import annotations + import datetime import os import subprocess From 713ffc14a67b3631d227850bc9c8a19652d000cf Mon Sep 17 00:00:00 2001 From: Stephen Arnold Date: Mon, 2 Sep 2024 21:57:57 -0700 Subject: [PATCH 5/6] fix: add still more annotations, adjust and apply pre-commit fixes Signed-off-by: Stephen Arnold --- .pre-commit-config.yaml | 2 +- src/timew_status/__init__.py | 1 + src/timew_status/utils.py | 40 +++++++++++++++++++++--------------- toxfile.py | 1 + 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 561e97a..4e2c8f1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -55,7 +55,7 @@ repos: - "munch-stubs" - "PyGObject" - "PyGObject-stubs" - #files: src/ + files: src/ - repo: "https://github.com/asottile/blacken-docs" rev: "1.18.0" diff --git a/src/timew_status/__init__.py b/src/timew_status/__init__.py index 0cb3deb..5fbad2a 100644 --- a/src/timew_status/__init__.py +++ b/src/timew_status/__init__.py @@ -2,6 +2,7 @@ Monitoring and reporting tools for Timew tracking intervals with optional appindicator alerts for keyboard time and daily hours. """ + import sys from .utils import ( diff --git a/src/timew_status/utils.py b/src/timew_status/utils.py index bcb1b38..a1d530f 100644 --- a/src/timew_status/utils.py +++ b/src/timew_status/utils.py @@ -11,7 +11,7 @@ from datetime import timedelta from pathlib import Path from shutil import which -from typing import Dict, NewType, Optional +from typing import Dict, List, NewType, Optional, Tuple from munch import Munch @@ -38,13 +38,13 @@ } -def check_for_timew(): +def check_for_timew() -> str: """ Make sure we can find the ``timew`` binary in the user environment and return a path string. :return timew_path: program path strings - :rtype tuple: path to program if found, else None + :rtype str: path to program if found, else None """ timew_path = which('timew') if not timew_path: @@ -53,7 +53,7 @@ def check_for_timew(): return timew_path -def do_install(cfg: Dict): +def do_install(cfg: Dict) -> List[str]: """ Install report extensions to timew extensions directory. The default src paths are preconfigured and should probably not be changed unless you @@ -83,7 +83,7 @@ def do_install(cfg: Dict): return files -def get_config(file_encoding: str = 'utf-8'): +def get_config(file_encoding: str = 'utf-8') -> Tuple[Munch, Path]: """ Load configuration file and munchify the data. If local file is not found in config directory, the default will be loaded and saved to @@ -104,7 +104,7 @@ def get_config(file_encoding: str = 'utf-8'): return cfgobj, cfgfile -def get_delta_limits(ucfg: Dict): +def get_delta_limits(ucfg: Dict) -> Tuple[timedelta, timedelta, timedelta, timedelta]: """ Return config max/snooze limits as timedeltas. Everything comes from static config values and gets padded with seconds. @@ -124,7 +124,7 @@ def get_delta_limits(ucfg: Dict): return day_max, day_limit, seat_max, seat_limit -def get_state_icon(state: str, cfg: Dict): +def get_state_icon(state: str, cfg: Dict) -> str: """ Look up the state msg and return the icon name. Use builtin symbolic icons as fallback. @@ -164,7 +164,7 @@ def get_state_icon(state: str, cfg: Dict): def get_state_str( cmproc: subprocess.CompletedProcess[bytes], count: TimeDelta, cfg: Dict -): +) -> Tuple[str, str]: """ Return timew state message and tracking state, ie, the key for dict with icons. @@ -178,7 +178,7 @@ def get_state_str( :return: tuple of state msg and state string """ - DAY_MAX, DAY_LIMIT, SEAT_MAX, SEAT_LIMIT = get_delta_limits(cfg) + (DAY_MAX, DAY_LIMIT, SEAT_MAX, SEAT_LIMIT) = get_delta_limits(cfg) state = 'INACTIVE' if cmproc.returncode == 1 else 'ACTIVE' msg = cmproc.stdout.decode('utf8') @@ -203,20 +203,22 @@ def get_state_str( return msg, state -def get_status(): +def get_status() -> subprocess.CompletedProcess[bytes]: """ Return timew tracking status (output of ``timew`` with no arguments). :param None: :return: timew output str or None + :raises RuntimeError: for timew not found error """ try: return subprocess.run(["timew"], capture_output=True) except FileNotFoundError as exc: print(f'Timew status error: {exc}') + raise RuntimeError("Did you install timewarrior?") from exc -def get_userdirs(): +def get_userdirs() -> Path: """ Get XDG user configuration path defined as ``XDG_CONFIG_HOME`` plus application name. This may grow if needed. @@ -232,7 +234,7 @@ def get_userdirs(): return configdir -def parse_for_tag(text: str): +def parse_for_tag(text: str) -> str: """ Parse the output of timew start/stop commands for the tag string. @@ -242,14 +244,18 @@ def parse_for_tag(text: str): for line in text.splitlines(): if line.startswith(("Tracking", "Recorded")): return line.split('"')[1] + return "Tag extraction error" -def run_cmd(cfg: Dict, action: str = 'status', tag: Optional[str] = None): +def run_cmd( + cfg: Dict, action: str = 'status', tag: Optional[str] = None +) -> Tuple[subprocess.CompletedProcess[bytes], str]: """ Run timew command subject to the given action. :param action: one of :return: completed proc obj and result msg + :raises RuntimeError: for timew action error """ timew_cmd = check_for_timew() extension = cfg["extension_script"] @@ -260,8 +266,9 @@ def run_cmd(cfg: Dict, action: str = 'status', tag: Optional[str] = None): act_list = [action] if action not in actions: - print(f'Invalid action: {action}') - return + msg = f'Invalid action: {action}' + print(msg) + raise RuntimeError(msg) if action == 'start' and tag: act_list.append(tag) if action != 'status': @@ -285,9 +292,10 @@ def run_cmd(cfg: Dict, action: str = 'status', tag: Optional[str] = None): except Exception as exc: print(f'run_cmd exception: {exc}') + raise RuntimeError(f"Timew {action} error") from exc -def to_td(hms: str): +def to_td(hms: str) -> timedelta: """ Convert a time string in HH:MM:SS format to a timedelta object. diff --git a/toxfile.py b/toxfile.py index ae19a7b..79d59bc 100644 --- a/toxfile.py +++ b/toxfile.py @@ -4,6 +4,7 @@ MIT License Copyright (c) 2023 Masen Furer """ + from contextlib import contextmanager from typing import Any, Iterator, Optional, Sequence, Tuple From 6008c1494fe663631432a94e16787a79f9e07a75 Mon Sep 17 00:00:00 2001 From: Stephen Arnold Date: Sat, 14 Sep 2024 19:53:16 -0700 Subject: [PATCH 6/6] chg: dev: add more tests, cleanup readme, build some debs Signed-off-by: Stephen Arnold --- .github/workflows/debs.yml | 91 ++++++++++++++++++++++++++++++++++++++ README.rst | 13 ++++-- docs/source/index.rst | 1 - tests/test_utils.py | 29 +++++++++++- 4 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/debs.yml diff --git a/.github/workflows/debs.yml b/.github/workflows/debs.yml new file mode 100644 index 0000000..8fe3a31 --- /dev/null +++ b/.github/workflows/debs.yml @@ -0,0 +1,91 @@ +name: Debian packages + +on: + workflow_dispatch: + #push: + #branches: [ main ] + pull_request: + +jobs: + get_version: + name: Get version info + runs-on: ubuntu-22.04 + defaults: + run: + shell: bash + outputs: + version: ${{ steps.git_ver.outputs.version }} + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get package version + id: git_ver + run: | + version=$(git describe --tags | sed -e "s|-g|+g|") + echo "Version from git: ${version}" + echo "version=${version}" >> $GITHUB_OUTPUT + + build_debs: + name: ${{ matrix.name }} + runs-on: ubuntu-22.04 + needs: [get_version] + + strategy: + fail-fast: false + matrix: + name: [ + x64_bookworm, + x64_trixie, + x64_sid, + ] + + include: + - name: x64_bookworm + dist: bookworm + + - name: x64_trixie + dist: trixie + + - name: x64_sid + dist: sid + + steps: + - name: Check github variables + env: + VERSION: ${{ needs.get_version.outputs.version }} + run: | + echo "Package version from git: ${VERSION}" + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Fetch debian files + run: | + wget https://launchpad.net/~nerdboy/+archive/ubuntu/embedded/+sourcefiles/timew-addons/0.1.1-1ubuntu1.22.04.1/timew-addons_0.1.1-1ubuntu1.22.04.1.debian.tar.xz + tar xf timew-addons_0.1.1-1ubuntu1.22.04.1.debian.tar.xz && rm *debian.tar.xz + ls + + - name: Install deps and update debian changelog + run: | + sudo apt-get update + sudo apt-get install devscripts + debchange -v ${{ needs.get_version.outputs.version }}-${{ matrix.dist }} -b -M --distribution ${{ matrix.dist }} "ci build" + + - name: Build deb packages + uses: jtdor/build-deb-action@v1 + env: + DEB_BUILD_OPTIONS: noautodbgsym + with: + docker-image: "debian:${{ matrix.dist }}-slim" + buildpackage-opts: --build=binary --no-sign + extra-build-deps: git + + - name: Upload deb files + uses: actions/upload-artifact@v4 + with: + name: "timew-addons_${{ needs.get_version.outputs.version }}-${{ matrix.dist }}" + path: ./debian/artifacts/*.deb diff --git a/README.rst b/README.rst index 91e728a..d51ef5a 100644 --- a/README.rst +++ b/README.rst @@ -205,18 +205,23 @@ Uninstalling Depending on how it was installed, use on or more of the following: -* delete the cloned directory, eg, ``rm -rf src/timew-addons`` +* delete the cloned directory, eg, ``rm -rf path/to/timew-addons`` * delete the virtual environment, eg, ``rm -rf ``.venv`` -* remove the OS package, eg, on Ubuntu: + +If you installed into a local env via ``pip`` then run:: + + $ pip uninstall timew-addons + +* or, remove the OS package, eg, on Ubuntu: :: $ sudo apt remove timew-addons $ sudo apt autoremove -Finally, delete the above configuration file:: +Finally, delete the configuration file:: - $ rm ~/.config/timew_status_indicator/config.yaml + $ rm $HOME/.config/timew_status_indicator/config.yaml Reporting examples diff --git a/docs/source/index.rst b/docs/source/index.rst index 367b54d..45a70cd 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -24,4 +24,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` -* :ref:`search` diff --git a/tests/test_utils.py b/tests/test_utils.py index 7d93b08..2fe2135 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -8,8 +8,11 @@ get_config, get_delta_limits, get_state_icon, + get_state_str, + get_status, get_userdirs, parse_for_tag, + run_cmd, ) CFG = { @@ -18,6 +21,9 @@ "seat_max": "01:30", "seat_snooze": "00:40", "use_symbolic_icons": False, + "extension_script": "onelineday", + "default_jtag_str": "vct-sw,implement skeleton timew indicator", + "jtag_separator": ",", "extensions_dir": "~/.timewarrior/extensions", "install_dir": "lib/timew-addons/extensions", "install_prefix": "/usr", @@ -90,9 +96,10 @@ def test_get_state_icon(): def test_get_state_icon_fallback(): states = ['INACTIVE', 'ACTIVE', 'WARNING', 'ERROR', 'APP'] + other = ['DEAD', 'PARROTS', 'TINY', 'FISH'] cfg = Munch.fromDict(CFG) cfg.use_symbolic_icons = True - for state in states: + for state in states + other: icon = get_state_icon(state, cfg.toDict()) assert 'symbolic' in icon print(icon) @@ -100,11 +107,31 @@ def test_get_state_icon_fallback(): def test_get_userdirs(): udir = get_userdirs() + assert '.config' in str(udir) and 'timew_status_indicator' in str(udir) print(f'\nuserdir: {udir}') def test_parse_for_tag(): + expected = "vct-sw,refactor timew indicator config to yaml" ret = parse_for_tag(start_txt) + assert ret == expected print(f'\n{ret}') ret = parse_for_tag(stop_text) + assert ret == expected print(ret) + + +def test_run_cmd(): + proc, msg = run_cmd(CFG) + print(msg) + + +def test_get_state_str(): + proc, _ = run_cmd(CFG) + tick_count = timedelta(seconds=95) + msg, new_state = get_state_str(proc, tick_count, CFG) + + +def test_get_status(): + result = get_status() + print(result)