Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .trkr/config
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
project = "trk"

[csv]
filename = "/home/dvklo/timetrackers/timetracker_trk_$USER$.csv"
filename = "../timetrackers/timetracker_trk_$USER$.csv"
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
71 changes: 52 additions & 19 deletions tests/test_finder.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -31,37 +40,40 @@ 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)
dirgit_exp = join(dirproj, '.git')
_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
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -102,16 +120,19 @@ 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
assert finder.project == proj, f'PROJ EXP({proj}) != ACT({finder.project})'
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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand All @@ -180,4 +213,4 @@ def _msg_exists(proj, dirproj, dirtrk, dirgit=None):


if __name__ == '__main__':
test_cfgbase_temp()
test_finder(prt_desc=False)
107 changes: 76 additions & 31 deletions timetracker/cfg/finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,30 @@
__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"""

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):
Expand All @@ -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.
Expand All @@ -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"""
Expand All @@ -74,38 +82,75 @@ 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"""
if self.dirtrk is not None:
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.
Loading