diff --git a/.trkr/config b/.trkr/config index cd3f3e49..83d319b3 100644 --- a/.trkr/config +++ b/.trkr/config @@ -3,4 +3,4 @@ project = "trk" [csv] -filename = "/home/dvklo/timetrackers/timetracker_trk_$USER$.csv" +filename = "../timetrackers/timetracker_trk_$USER$.csv" diff --git a/pyproject.toml b/pyproject.toml index 7cefefd2..7c6e961e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,7 +65,7 @@ classifiers=[ [project.scripts] -trk = "timetracker.cli:main" +trk = "timetracker.main:main" timestr = "timetracker.epoch.cli.timestr:main" timecalc = "timetracker.epoch.cli.calc:main" diff --git a/tests/test_finder.py b/tests/test_finder.py index 9fd971ce..cf110e10 100755 --- a/tests/test_finder.py +++ b/tests/test_finder.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Test the TimeTracker project config dir finder""" +# https://stackoverflow.com/questions/16373510/improving-speed-of-python-module-import from os import makedirs from os.path import exists @@ -8,7 +9,15 @@ from logging import basicConfig from logging import DEBUG from tempfile import TemporaryDirectory + +from timeit import default_timer +from datetime import timedelta + +tic0 = default_timer() +# pylint: disable=wrong-import-position from timetracker.cfg.finder import CfgFinder +print(f'{timedelta(seconds=default_timer()-tic0)} IMPORT CfgFinder') # PRT + from tests.pkgtttest.mkprojs import mkdirs from tests.pkgtttest.mkprojs import findhome from tests.pkgtttest.cmpstr import str_get_dirtrk @@ -19,7 +28,7 @@ SEP1 = f'\n{"="*80}\n' SEP2 = f'{"-"*80}\n' -def test_cfgbase_temp(trksubdir='.timetracker'): +def test_finder(trksubdir='.timetracker', prt_desc=True): """Test the TimeTracker project config dir finder""" print(f'{SEP1}1) INITIALIZE "HOME" DIRECTORY') # Test finder when current directory is NOT time-tracked @@ -31,28 +40,28 @@ def test_cfgbase_temp(trksubdir='.timetracker'): print(f'{SEP1} NO .timetracker NO .git') with TemporaryDirectory() as tmp_home: proj2wdir = mkdirs(tmp_home) - _test_tracked0_git0(proj2wdir, trksubdir) + _test_tracked0_git0(proj2wdir, trksubdir, prt_desc) # Test finder when current directory is NOT time-tracked and is NOT git-tracked print(f'{SEP1} YES .timetracker NO .git') with TemporaryDirectory() as tmp_home: proj2wdir = mkdirs(tmp_home) - _test_tracked1_git0(proj2wdir, trksubdir) + _test_tracked1_git0(proj2wdir, trksubdir, prt_desc) # Test finder when current directory is NOT time-tracked and is NOT git-tracked print(f'{SEP1} NO .timetracker YES .git') with TemporaryDirectory() as tmp_home: proj2wdir = mkdirs(tmp_home) - _test_tracked0_git1(proj2wdir, trksubdir) + _test_tracked0_git1(proj2wdir, trksubdir, prt_desc) # Test finder when current directory IS time-tracked AND IS git-tracked print(f'{SEP1} YES .timetracker YES .git') with TemporaryDirectory() as tmp_home: proj2wdir = mkdirs(tmp_home) - _test_tracked1_git1(proj2wdir, trksubdir) - + _test_tracked1_git1(proj2wdir, trksubdir, prt_desc) -def _test_tracked0_git0(proj2wdir, trksubdir): +# pylint: disable=line-too-long +def _test_tracked0_git0(proj2wdir, trksubdir, prt_desc=True): """Test Finder when proj/.timetracker directory does not exist""" for proj, dirproj in proj2wdir.items(): dirtrk_exp = join(dirproj, trksubdir) @@ -60,8 +69,11 @@ def _test_tracked0_git0(proj2wdir, trksubdir): _msg_exists(proj, dirproj, dirtrk_exp, dirgit_exp) dircur = dirproj + tic = default_timer() finder = CfgFinder(dircur=dircur, trksubdir=trksubdir) - print(f'{proj:11} TEST {finder.get_desc()}') + print(f'{timedelta(seconds=default_timer()-tic)} NEW CfgFinder() _tracked0_git0 {dircur}') # PRT + if prt_desc: + print(f'{proj:11} TEST {finder.get_desc()}') assert finder.dirtrk is None assert finder.dirproj == dirproj assert finder.get_dirgit() is None @@ -71,8 +83,11 @@ def _test_tracked0_git0(proj2wdir, trksubdir): f'ACT({finder.get_dircsv_default()}) != EXP({dirname(dirtrk_exp)})') dircur = join(dirproj, 'doc') + tic = default_timer() finder = CfgFinder(dircur=dircur, trksubdir=trksubdir) - print(f'{proj:11} TEST {finder.get_desc()}') + print(f'{timedelta(seconds=default_timer()-tic)} NEW CfgFinder() _tracked0_git0 {dircur}') # PRT + if prt_desc: + print(f'{proj:11} TEST {finder.get_desc()}') assert finder.dirtrk is None, str(finder) assert finder.dirproj == dircur, f't0g0 DIRPOJ EXP({dirproj}) != ACT({finder.dirproj})' assert finder.get_dirgit() is None @@ -82,7 +97,7 @@ def _test_tracked0_git0(proj2wdir, trksubdir): assert finder.get_dircsv_default() == '.', ('t0g0 DIRCSV ' f'ACT({finder.get_dircsv_default()}) != EXP({dirname(dirtrk_exp)})') -def _test_tracked1_git0(proj2wdir, trksubdir): +def _test_tracked1_git0(proj2wdir, trksubdir, prt_desc=True): """Test Finder when proj/.timetracker directory exists""" for proj, dirproj in proj2wdir.items(): dirtrk_exp = join(dirproj, trksubdir) @@ -91,8 +106,11 @@ def _test_tracked1_git0(proj2wdir, trksubdir): _msg_exists(proj, dirproj, dirtrk_exp, dirgit_exp) dircur = dirproj + tic = default_timer() finder = CfgFinder(dircur=dircur, trksubdir=trksubdir) - print(f'{proj:11} TEST {finder.get_desc()}') + print(f'{timedelta(seconds=default_timer()-tic)} NEW CfgFinder() _tracked1_git0 {dircur}') # PRT + if prt_desc: + print(f'{proj:11} TEST {finder.get_desc()}') assert finder.dirtrk == dirtrk_exp, str_get_dirtrk(dirtrk_exp, finder) assert finder.dirproj == dirproj assert finder.get_dirgit() is None @@ -102,8 +120,11 @@ def _test_tracked1_git0(proj2wdir, trksubdir): f'ACT({finder.get_dircsv_default()}) != EXP({dirname(dirtrk_exp)})') dircur = join(dirproj, 'doc') + tic = default_timer() finder = CfgFinder(dircur=dircur, trksubdir=trksubdir) - print(f'{proj:11} TEST {finder.get_desc()}') + print(f'{timedelta(seconds=default_timer()-tic)} NEW CfgFinder() _tracked1_git0 {dircur}') # PRT + if prt_desc: + print(f'{proj:11} TEST {finder.get_desc()}') assert finder.dirtrk == dirtrk_exp, str_get_dirtrk(dirtrk_exp, finder) assert finder.dirproj == dirproj assert finder.get_dirgit() is None @@ -111,7 +132,7 @@ def _test_tracked1_git0(proj2wdir, trksubdir): assert finder.get_dirtrk() == dirtrk_exp, f"\nEXP: {dirtrk_exp}\nACT: {str(finder)}" assert finder.get_dircsv_default() == dirproj -def _test_tracked0_git1(proj2wdir, trksubdir): +def _test_tracked0_git1(proj2wdir, trksubdir, prt_desc=True): """Test Finder when proj/.timetracker directory does not exist""" for proj, dirproj in proj2wdir.items(): dirtrk_exp = join(dirproj, trksubdir) @@ -120,8 +141,11 @@ def _test_tracked0_git1(proj2wdir, trksubdir): _msg_exists(proj, dirproj, dirtrk_exp, dirgit_exp) dircur = dirproj + tic = default_timer() finder = CfgFinder(dircur=dircur, trksubdir=trksubdir) - print(f'{proj:11} TEST {finder.get_desc()}') + print(f'{timedelta(seconds=default_timer()-tic)} NEW CfgFinder() _tracked0_git1 {dircur}') # PRT + if prt_desc: + print(f'{proj:11} TEST {finder.get_desc()}') assert finder.dirtrk is None assert finder.dirproj == dirproj # pylint: disable=line-too-long @@ -132,8 +156,11 @@ def _test_tracked0_git1(proj2wdir, trksubdir): f'ACT({finder.get_dircsv_default()}) != EXP(".")') dircur = join(dirproj, 'doc') + tic = default_timer() finder = CfgFinder(dircur=dircur, trksubdir=trksubdir) - print(f'{proj:11} TEST {finder.get_desc()}') + print(f'{timedelta(seconds=default_timer()-tic)} NEW CfgFinder() _tracked0_git1 {dircur}') # PRT + if prt_desc: + print(f'{proj:11} TEST {finder.get_desc()}') assert finder.dirtrk is None, str(finder) assert finder.dirproj == dirproj assert finder.get_dirgit() == dirgit_exp @@ -142,7 +169,7 @@ def _test_tracked0_git1(proj2wdir, trksubdir): assert finder.get_dircsv_default() == dirproj, ('t0g1 DIRCSV ' f'ACT({finder.get_dircsv_default()}) != EXP({dirname(dirgit_exp)})') -def _test_tracked1_git1(proj2wdir, trksubdir): +def _test_tracked1_git1(proj2wdir, trksubdir, prt_desc=True): """Test Finder when proj/.timetracker directory does not exist""" for proj, dirproj in proj2wdir.items(): dirtrk_exp = join(dirproj, trksubdir) @@ -152,8 +179,11 @@ def _test_tracked1_git1(proj2wdir, trksubdir): _msg_exists(proj, dirproj, dirtrk_exp, dirgit_exp) dircur = dirproj + tic = default_timer() finder = CfgFinder(dircur=dircur, trksubdir=trksubdir) - print(f'{proj:11} TEST {finder.get_desc()}') + print(f'{timedelta(seconds=default_timer()-tic)} NEW CfgFinder() _tracked1_git1 {dircur}') # PRT + if prt_desc: + print(f'{proj:11} TEST {finder.get_desc()}') assert finder.dirtrk == dirtrk_exp, str_get_dirtrk(dirtrk_exp, finder) assert finder.dirproj == dirproj assert finder.get_dirgit() == dirgit_exp @@ -163,8 +193,11 @@ def _test_tracked1_git1(proj2wdir, trksubdir): f'ACT({finder.get_dircsv_default()}) != EXP(".")') dircur = join(dirproj, 'doc') + tic = default_timer() finder = CfgFinder(dircur=dircur, trksubdir=trksubdir) - print(f'{proj:11} TEST {finder.get_desc()}') + print(f'{timedelta(seconds=default_timer()-tic)} NEW CfgFinder() _tracked1_git1 {dircur}') # PRT + if prt_desc: + print(f'{proj:11} TEST {finder.get_desc()}') assert finder.dirtrk == dirtrk_exp, str_get_dirtrk(dirtrk_exp, finder) assert finder.dirproj == dirproj assert finder.get_dirgit() == dirgit_exp @@ -180,4 +213,4 @@ def _msg_exists(proj, dirproj, dirtrk, dirgit=None): if __name__ == '__main__': - test_cfgbase_temp() + test_finder(prt_desc=False) diff --git a/timetracker/cfg/finder.py b/timetracker/cfg/finder.py index 6e2ae6fa..77860307 100755 --- a/timetracker/cfg/finder.py +++ b/timetracker/cfg/finder.py @@ -3,10 +3,16 @@ __copyright__ = 'Copyright (C) 2025-present, DV Klopfenstein, PhD. All rights reserved.' __author__ = "DV Klopfenstein, PhD" -import os.path as os_path -from os.path import dirname +import os.path as op from timetracker.consts import DIRTRK +# pylint: disable=invalid-name +JOIN = op.join +EXISTS = op.exists +NORMPATH = op.normpath +DIRNAME = op.dirname +GITSUBDIR = '.git' + class CfgFinder: """Functionality to find the local project config, if one is present""" @@ -14,12 +20,13 @@ class CfgFinder: def __init__(self, dircur, trksubdir=None): self.dircur = dircur self.trksubdir = trksubdir if trksubdir is not None else DIRTRK + dirs_trk_git = _finddirs_trk_git(dircur, self.trksubdir) # Existing directory (ex: ./timetracker) or None if dir not exist - self.dirtrk = _get_abspathtrk(dircur, self.trksubdir) - self.dirgit = _get_abspathtrk(dircur, '.git') + self.dirtrk = dirs_trk_git.get('trk') + self.dirgit = dirs_trk_git.get('git') # Get the project tracking directory that is or will be tracked self.dirtrk_pathname = self._init_dirtrk() - self.dirproj = dirname(self.dirtrk_pathname) + self.dirproj = DIRNAME(self.dirtrk_pathname) self.project = self._init_project() def get_dirtrk(self): @@ -28,11 +35,12 @@ def get_dirtrk(self): def get_dirgit(self): """Get the .git directory if it is the current dir or any parents""" - return _get_abspathtrk(self.dircur, '.git') + ##return _get_abspathtrk(self.dircur, GITSUBDIR) + return self.dirgit def get_cfgfilename(self): """Get the local (aka project) config full filename""" - return os_path.join(self.dirtrk_pathname, 'config') + return op.join(self.dirtrk_pathname, 'config') def get_dircsv_default(self): """Get the default csv directory for use in the cli help string. @@ -47,18 +55,18 @@ def get_dircsv_default(self): +--+--+--+-- Not using in default value """ - if os_path.realpath(self.dircur) == os_path.realpath(self.dirproj): + if op.realpath(self.dircur) == op.realpath(self.dirproj): return '.' assert not (self.dirtrk is None and self.dirgit is None) return self.dirproj def get_dirproj(self): """Get the project directory""" - return dirname(self.dirtrk_pathname) + return DIRNAME(self.dirtrk_pathname) def get_dircur_rel(self): """Get the current directory relative to the project directory""" - return os_path.relpath(self.dircur, self.get_dirproj()) + return op.relpath(self.dircur, self.get_dirproj()) def get_desc(self): """Get a description of the state of a CfgFinder instance""" @@ -74,7 +82,7 @@ def get_desc(self): def _init_project(self): dirtrk = self.dirtrk_pathname if self.dirtrk is None else self.dirtrk - return os_path.basename(dirname(dirtrk)) + return op.basename(DIRNAME(dirtrk)) def _init_dirtrk(self): """Get the project tracking directory that is or will be tracked""" @@ -82,30 +90,67 @@ def _init_dirtrk(self): return self.dirtrk dirgit = self.get_dirgit() if dirgit is not None: - return os_path.normpath(os_path.join(dirname(dirgit), self.trksubdir)) - return os_path.normpath(os_path.join(self.dircur, self.trksubdir)) - + return op.normpath(op.join(DIRNAME(dirgit), self.trksubdir)) + return op.normpath(op.join(self.dircur, self.trksubdir)) -def _get_abspathtrk(path, trksubdir): - """Get .timetracker/ proj dir by searching up parent path""" - trkabsdir, found = _finddirtrk(path, trksubdir) - return trkabsdir if found else None -def _finddirtrk(path, trksubdir): +def _finddirs_trk_git(path, trksubdir): """Walk up dirs until find .timetracker/ proj dir or mount dir""" - path = os_path.abspath(path) - join = os_path.join - trkdir = join(path, trksubdir) - exists = os_path.exists - if exists(trkdir): - return os_path.normpath(trkdir), True - ismount = os_path.ismount + path = op.abspath(path) + ret = _init_dict_trkgit(path, trksubdir) + if set(ret) == {'trk', 'git'}: + return ret + path = DIRNAME(path) + ismount = op.ismount while not ismount(path): - trkdir = join(path, trksubdir) - if exists(trkdir): - return os_path.normpath(trkdir), True - path = dirname(path) - return os_path.normpath(path), False + _add_trk_git(ret, path, trksubdir) + if set(ret) == {'trk', 'git'}: + return ret + path = DIRNAME(path) + ret['mountdir'] = path + return ret + +def _add_trk_git(ret, path, trksubdir): + if 'trk' not in ret: + trkdir = JOIN(path, trksubdir) + if EXISTS(trkdir): + ret['trk'] = NORMPATH(trkdir) + if 'git' not in ret: + gitdir = JOIN(path, GITSUBDIR) + if EXISTS(gitdir): + ret['git'] = NORMPATH(gitdir) + +def _init_dict_trkgit(path, trksubdir): + trkdir = JOIN(path, trksubdir) + gitdir = JOIN(path, GITSUBDIR) + exists_trk = EXISTS(trkdir) + exists_git = EXISTS(gitdir) + if exists_trk and exists_git: + return {'trk': NORMPATH(trkdir), 'git': NORMPATH(gitdir)} + if exists_trk: + return {'trk': NORMPATH(trkdir)} + return {'git': NORMPATH(gitdir)} if exists_git else {} + +####def _get_abspathtrk(path, trksubdir): +#### """Get .timetracker/ proj dir by searching up parent path""" +#### trkabsdir, found = _finddirtrk(path, trksubdir) +#### return trkabsdir if found else None + +####def _finddirtrk(path, trksubdir): +#### """Walk up dirs until find .timetracker/ proj dir or mount dir""" +#### path = op.abspath(path) +#### join = op.join +#### trkdir = join(path, trksubdir) +#### exists = op.exists +#### if exists(trkdir): +#### return op.normpath(trkdir), True +#### ismount = op.ismount +#### while not ismount(path): +#### trkdir = join(path, trksubdir) +#### if exists(trkdir): +#### return op.normpath(trkdir), True +#### path = DIRNAME(path) +#### return op.normpath(path), False # Copyright (C) 2025-present, DV Klopfenstein, PhD. All rights reserved. diff --git a/timetracker/cli.py b/timetracker/cli.py index 09e60158..a08e7dae 100755 --- a/timetracker/cli.py +++ b/timetracker/cli.py @@ -28,16 +28,6 @@ #print(f'{timedelta(seconds=default_timer()-tic)} TOP OF CLI: AFTER IMPORTS') # PRT -def main(): - """Connect all parts of the timetracker""" - #from logging import basicConfig, DEBUG - #basicConfig(level=DEBUG) - #print('ENTERING Cli') - obj = Cli() - #print('ENTERING Cli.run') - obj.run() - #print('EXITING Cli.run') - class Cli: """Command line interface (CLI) for timetracking""" @@ -104,7 +94,8 @@ def _init_args(self, arglist): sys_exit(0) return args - def _init_trksubdir(self): + @staticmethod + def _init_trksubdir(): found = False for arg in sys_argv: if found: @@ -325,7 +316,4 @@ def _add_subparser_running(self, subparsers): return parser -if __name__ == '__main__': - main() - # Copyright (C) 2025-present, DV Klopfenstein, PhD. All rights reserved. diff --git a/timetracker/cmd/start.py b/timetracker/cmd/start.py index 259babfa..986cb5d3 100755 --- a/timetracker/cmd/start.py +++ b/timetracker/cmd/start.py @@ -47,7 +47,7 @@ def run_start(cfgproj, name=None, start_at=None, last=None, **kwargs): return startobj startobj.wr_starttime(starttime, kwargs.get('activity'), kwargs.get('tag')) if not kwargs.get('quiet', False): - print(f'Timetracker {_get_msg(start_at, force)}: ' + print(f'Timetracker {_get_msg(start_at, force, last)}: ' f'{starttime.strftime("%a %I:%M %p")}: ' f'{starttime} ') #f"for project '{cfgproj.project}'") @@ -77,10 +77,10 @@ def _get_next_starttime(fcsv): return csvfile.get_next_start_datetime(ntd, seconds=1) return None -def _get_msg(start_at, force): +def _get_msg(start_at, force, last): if force: return "start reset to" - return "started now" if start_at is None else "started at" + return "started now" if start_at is None and last is None else "started at" # Copyright (C) 2025-present, DV Klopfenstein, PhD. All rights reserved. diff --git a/timetracker/main.py b/timetracker/main.py new file mode 100755 index 00000000..a1fc85ae --- /dev/null +++ b/timetracker/main.py @@ -0,0 +1,37 @@ +"""Command line interface (CLI) for timetracking""" + +__copyright__ = 'Copyright (C) 2025-present, DV Klopfenstein, PhD. All rights reserved.' +__author__ = "DV Klopfenstein, PhD" + +import sys +#from timeit import default_timer # PRT +#tic = default_timer() # PRT +#from datetime import timedelta # PRT +#print(f'{timedelta(seconds=default_timer()-tic)} AFTER IMPORT datetime.timedelta') # PRT +#print(f'{timedelta(seconds=default_timer()-tic)} BEFORE IMPORT Cli') # PRT +import timetracker.cli as modcli +#print(f'{timedelta(seconds=default_timer()-tic)} AFTER IMPORT Cli') # PRT + + +def main(): + """Connect all parts of the timetracker""" + #from logging import basicConfig, DEBUG + #basicConfig(level=DEBUG) + #print('ENTERING Cli') + args_sys = sys.argv[1:] +# print(f'{timedelta(seconds=default_timer()-tic)} AFTER sys.argv[1:]') # PRT + if not args_sys: + pass +# print('NO ARGS') # PRT +# ##print(f'{timedelta(seconds=default_timer()-tic)} AFTER sys.argv[1:]') # PRT + # timetracker/cmd/common.py str_uninitialized + obj = modcli.Cli() + #print('ENTERING Cli.run') + obj.run() + #print('EXITING Cli.run') + + +if __name__ == '__main__': + main() + +# Copyright (C) 2025-present, DV Klopfenstein, PhD. All rights reserved.