Merge remote-tracking branch 'upstream/master' into ignore-non-matching-patterns

This commit is contained in:
Darko Poljak 2017-02-15 21:30:50 +01:00
commit 2238ea7042
10 changed files with 218 additions and 172 deletions

View File

@ -36,11 +36,13 @@ MAIL = 'nico-ctt at schottelius.org'
WWW = 'http://www.nico.schottelius.org/software/ctt/' WWW = 'http://www.nico.schottelius.org/software/ctt/'
# Name of the folder to create - should not contain special characters # Name of the folder to create - should not contain special characters
# to ensure cross-os compatibility # to ensure cross-os compatibility
DISKFORMAT = DATETIMEFORMAT DISKFORMAT = DATETIMEFORMAT
class Error(Exception): class Error(Exception):
pass pass
# Our output format # Our output format
def user_timedelta(seconds): def user_timedelta(seconds):
"""Format timedelta for the user""" """Format timedelta for the user"""
@ -59,15 +61,17 @@ def user_timedelta(seconds):
return (hours, minutes, seconds) return (hours, minutes, seconds)
def ctt_dir(): def ctt_dir():
home = os.environ['HOME'] home = os.environ['HOME']
ctt_dir = os.path.join(home, ".ctt") ctt_dir = os.path.join(home, ".ctt")
return ctt_dir return ctt_dir
def project_dir(project): def project_dir(project):
project_dir = os.path.join(ctt_dir(), project) project_dir = os.path.join(ctt_dir(), project)
return project_dir return project_dir
return os.listdir(ctt_dir) # return os.listdir(ctt_dir)

View File

@ -26,6 +26,7 @@ import os
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class ListProjects(object): class ListProjects(object):
"""Return existing projects""" """Return existing projects"""
@ -33,7 +34,6 @@ class ListProjects(object):
def commandline(cls, args): def commandline(cls, args):
cls.print_projects() cls.print_projects()
@classmethod @classmethod
def print_projects(cls): def print_projects(cls):
for project in cls.list_projects(): for project in cls.list_projects():

View File

@ -21,16 +21,13 @@
# #
# #
import calendar
import datetime import datetime
import logging import logging
import time
import os import os
import os.path import os.path
import re import re
import sys
import glob import glob
import collections import collections
@ -39,11 +36,12 @@ import ctt.listprojects
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Report(object): class Report(object):
"""Create a report on tracked time""" """Create a report on tracked time"""
def __init__(self, project, start_date, end_date, def __init__(self, project, start_date, end_date,
output_format, regexp, ignore_case): output_format, regexp, ignore_case):
self.project = project self.project = project
self.project_dir = ctt.project_dir(self.project) self.project_dir = ctt.project_dir(self.project)
@ -63,7 +61,7 @@ class Report(object):
def commandline(cls, args): def commandline(cls, args):
# Report time for all projects # Report time for all projects
if args.all: if args.all:
projects=ctt.listprojects.ListProjects.list_projects() projects = ctt.listprojects.ListProjects.list_projects()
else: else:
projects = [] projects = []
@ -75,8 +73,8 @@ class Report(object):
reports = collections.OrderedDict() reports = collections.OrderedDict()
for project in projects: for project in projects:
report = cls(project=project, start_date=args.start, report = cls(project=project, start_date=args.start,
end_date=args.end, output_format=args.output_format, end_date=args.end, output_format=args.output_format,
regexp=args.regexp, ignore_case=args.ignore_case) regexp=args.regexp, ignore_case=args.ignore_case)
report_data = report.report() report_data = report.report()
reports[report.project] = (report, report_data) reports[report.project] = (report, report_data)
total_time = total_time + report.total_time total_time = total_time + report.total_time
@ -84,7 +82,6 @@ class Report(object):
cls.summary(total_time) cls.summary(total_time)
@staticmethod @staticmethod
def print_report_time_entries(report_data, output_format, summary): def print_report_time_entries(report_data, output_format, summary):
''' Print time entries from report_data report using output_format. ''' Print time entries from report_data report using output_format.
@ -109,53 +106,58 @@ class Report(object):
report, report_data = reports[project] report, report_data = reports[project]
if summary: if summary:
for time in report_data: for time in report_data:
if not time in summary_report: if time not in summary_report:
summary_report[time] = report_data[time] summary_report[time] = report_data[time]
else: else:
summary_report[time].extend(report_data[time]) summary_report[time].extend(report_data[time])
else: else:
report.header() report.header()
Report.print_report_time_entries(report_data, Report.print_report_time_entries(report_data,
output_format, summary) output_format, summary)
if summary: # For summary do not print time entries.
Report.print_report_time_entries(summary_report, # if summary:
output_format, summary) # Report.print_report_time_entries(summary_report,
# output_format, summary)
def _init_date(self, start_date, end_date): def _init_date(self, start_date, end_date):
"""Setup date - either default or user given values""" """Setup date - either default or user given values"""
now = datetime.datetime.now() now = datetime.datetime.now()
first_day_this_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) first_day_this_month = now.replace(
next_month = first_day_this_month.replace(day=28) + datetime.timedelta(days=4) day=1, hour=0, minute=0, second=0, microsecond=0)
next_month = first_day_this_month.replace(
day=28) + datetime.timedelta(days=4)
first_day_next_month = next_month.replace(day=1) first_day_next_month = next_month.replace(day=1)
last_day_this_month = first_day_next_month - datetime.timedelta(seconds=1) last_day_this_month = first_day_next_month - datetime.timedelta(
seconds=1)
default_start_date = first_day_this_month default_start_date = first_day_this_month
default_end_date = last_day_this_month default_end_date = last_day_this_month
#default_end_date = first_day - datetime.timedelta(days=1) # default_end_date = first_day - datetime.timedelta(days=1)
#default_start_date = default_end_date.replace(day=1) # default_start_date = default_end_date.replace(day=1)
try: try:
if start_date: if start_date:
self.start_date = datetime.datetime.strptime(start_date[0], ctt.DATEFORMAT) self.start_date = datetime.datetime.strptime(
start_date[0], ctt.DATEFORMAT)
else: else:
self.start_date = default_start_date self.start_date = default_start_date
if end_date: if end_date:
self.end_date = datetime.datetime.strptime(end_date[0], ctt.DATEFORMAT) self.end_date = datetime.datetime.strptime(
end_date[0], ctt.DATEFORMAT)
else: else:
self.end_date = default_end_date self.end_date = default_end_date
except ValueError as e: except ValueError as e:
raise ctt.Error(e) raise ctt.Error(e)
self.end_date = self.end_date.replace(hour=23,minute=59,second=59) self.end_date = self.end_date.replace(
hour=23, minute=59, second=59)
if self.start_date >= self.end_date: if self.start_date >= self.end_date:
raise ctt.Error("End date must be after start date (%s >= %s)" % raise ctt.Error("End date must be after start date (%s >= %s)" %
(self.start_date, self.end_date)) (self.start_date, self.end_date))
def _init_report_db(self): def _init_report_db(self):
"""Read all contents from db""" """Read all contents from db"""
@ -167,14 +169,20 @@ class Report(object):
for dirname in os.listdir(self.project_dir): for dirname in os.listdir(self.project_dir):
log.debug("Dirname: %s" % dirname) log.debug("Dirname: %s" % dirname)
try: try:
dir_datetime = datetime.datetime.strptime(dirname, ctt.DISKFORMAT) dir_datetime = datetime.datetime.strptime(
dirname, ctt.DISKFORMAT)
except ValueError: except ValueError:
log.warning("Invalid time entry {entry} for project {project}, skipping.".format(entry=dirname, project=self.project)) log.warning("Invalid time entry {entry} for project "
"{project}, skipping.".format(
entry=dirname, project=self.project))
continue continue
if dir_datetime >= self.start_date and dir_datetime <= self.end_date: if (dir_datetime >= self.start_date and
filename = os.path.join(self.project_dir, dirname, ctt.FILE_DELTA) dir_datetime <= self.end_date):
comment_filename = os.path.join(self.project_dir, dirname, ctt.FILE_COMMENT) filename = os.path.join(
self.project_dir, dirname, ctt.FILE_DELTA)
comment_filename = os.path.join(
self.project_dir, dirname, ctt.FILE_COMMENT)
# Check for matching comment # Check for matching comment
comment = None comment = None
@ -183,10 +191,11 @@ class Report(object):
comment = fd.read().rstrip('\n') comment = fd.read().rstrip('\n')
# If regular expression given, but not matching, skip entry # If regular expression given, but not matching, skip entry
if self.regexp and not re.search(self.regexp, comment, self.search_flags): if (self.regexp and
not re.search(self.regexp, comment,
self.search_flags)):
continue continue
self._report_db[dirname] = {} self._report_db[dirname] = {}
if comment: if comment:
self._report_db[dirname]['comment'] = comment self._report_db[dirname]['comment'] = comment
@ -194,7 +203,8 @@ class Report(object):
with open(filename, "r") as fd: with open(filename, "r") as fd:
self._report_db[dirname]['delta'] = fd.read().rstrip('\n') self._report_db[dirname]['delta'] = fd.read().rstrip('\n')
log.debug("Recording: %s: %s" % (dirname, self._report_db[dirname]['delta'])) log.debug("Recording: %s: %s"
% (dirname, self._report_db[dirname]['delta']))
else: else:
log.debug("Skipping: %s" % dirname) log.debug("Skipping: %s" % dirname)
@ -202,14 +212,14 @@ class Report(object):
def header(self): def header(self):
project_name = os.path.basename(self.project) project_name = os.path.basename(self.project)
print("Report for %s between %s and %s" % print("Report for %s between %s and %s" %
(project_name, self.start_date, self.end_date)) (project_name, self.start_date, self.end_date))
@staticmethod @staticmethod
def summary(total_time): def summary(total_time):
hours, minutes, seconds = ctt.user_timedelta(total_time) hours, minutes, seconds = ctt.user_timedelta(total_time)
print("Total time tracked: %sh %sm %ss." % print("Total time tracked: %sh %sm %ss." %
(hours, minutes, seconds)) (hours, minutes, seconds))
@property @property
def total_time(self): def total_time(self):
@ -223,17 +233,16 @@ class Report(object):
return count return count
def _get_report_entry(self, time, entry): def _get_report_entry(self, time, entry):
''' Get one time entry data. ''' Get one time entry data.
''' '''
report = {} report = {}
start_datetime = datetime.datetime.strptime(time, ctt.DATETIMEFORMAT) start_datetime = datetime.datetime.strptime(time, ctt.DATETIMEFORMAT)
delta = datetime.timedelta(seconds=int(float(entry['delta']))) delta = datetime.timedelta(seconds=int(float(entry['delta'])))
end_datetime = (start_datetime + delta).replace(microsecond = 0) end_datetime = (start_datetime + delta).replace(microsecond=0)
report['start_datetime'] = start_datetime.strftime(ctt.DATETIMEFORMAT) report['start_datetime'] = start_datetime.strftime(ctt.DATETIMEFORMAT)
report['end_datetime'] = end_datetime.strftime(ctt.DATETIMEFORMAT) report['end_datetime'] = end_datetime.strftime(ctt.DATETIMEFORMAT)
report['delta'] = delta report['delta'] = delta
report['delta_seconds'] = int(float(entry['delta'])) report['delta_seconds'] = int(float(entry['delta']))
@ -245,7 +254,6 @@ class Report(object):
report['comment'] = False report['comment'] = False
return report return report
def report(self): def report(self):
"""Return total time tracked""" """Return total time tracked"""
@ -254,7 +262,7 @@ class Report(object):
for time in time_keys: for time in time_keys:
entry = self._report_db[time] entry = self._report_db[time]
report = self._get_report_entry(time, entry) report = self._get_report_entry(time, entry)
if not time in entries: if time not in entries:
entries[time] = [report] entries[time] = [report]
else: else:
entries[time].append(report) entries[time].append(report)

View File

@ -22,7 +22,9 @@
import os import os
import unittest import unittest
fixtures_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "fixtures")) fixtures_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
"fixtures"))
class CttTestCase(unittest.TestCase): class CttTestCase(unittest.TestCase):
def setUp(self): def setUp(self):

View File

@ -25,12 +25,13 @@ import ctt
import ctt.listprojects as cttls import ctt.listprojects as cttls
import ctt.test import ctt.test
class ListProjectsTestCase(ctt.test.CttTestCase): class ListProjectsTestCase(ctt.test.CttTestCase):
def test_list_projects(self): def test_list_projects(self):
projects = cttls.ListProjects.list_projects() projects = cttls.ListProjects.list_projects()
expected_projects = [ 'foo1', 'foo2', 'foo3', 'spam-eggs', expected_projects = ['foo1', 'foo2', 'foo3', 'spam-eggs',
'test-1', 'test-2', 'test-3', ] 'test-1', 'test-2', 'test-3', ]
gotten_projects = sorted(projects) gotten_projects = sorted(projects)
self.assertEqual(gotten_projects, expected_projects) self.assertEqual(gotten_projects, expected_projects)

View File

@ -51,17 +51,17 @@ class ReportTestCase(ctt.test.CttTestCase):
report_data = { report_data = {
'2016-04-07-0826': [ '2016-04-07-0826': [
{ {
'start_datetime': '2016-04-07-0826', 'start_datetime': '2016-04-07-0826',
'end_datetime': '2016-04-07-2359', 'end_datetime': '2016-04-07-2359',
'comment': 'foo1', 'comment': 'foo1',
'delta': '6', 'delta': '6',
'delta_seconds': '6', 'delta_seconds': '6',
'delta_minutes': '0', 'delta_minutes': '0',
}, },
], ],
} }
report.Report.print_report_time_entries(report_data, ctt.REPORTFORMAT, report.Report.print_report_time_entries(report_data, ctt.REPORTFORMAT,
False) False)
output = self._get_output() output = self._get_output()
expected_output = "2016-04-07-0826 (6): foo1" expected_output = "2016-04-07-0826 (6): foo1"
self.assertEqual(output, expected_output) self.assertEqual(output, expected_output)
@ -70,70 +70,72 @@ class ReportTestCase(ctt.test.CttTestCase):
report_data = { report_data = {
'2016-04-07-0826': [ '2016-04-07-0826': [
{ {
'start_datetime': '2016-04-07-0826', 'start_datetime': '2016-04-07-0826',
'end_datetime': '2016-04-07-2359', 'end_datetime': '2016-04-07-2359',
'comment': 'foo1', 'comment': 'foo1',
'delta': '6', 'delta': '6',
'delta_seconds': '6', 'delta_seconds': '6',
'delta_minutes': '0', 'delta_minutes': '0',
}, },
], ],
'2016-04-07-0926': [ '2016-04-07-0926': [
{ {
'start_datetime': '2016-04-07-0926', 'start_datetime': '2016-04-07-0926',
'end_datetime': '2016-04-07-2359', 'end_datetime': '2016-04-07-2359',
'comment': 'foo12', 'comment': 'foo12',
'delta': '10', 'delta': '10',
'delta_seconds': '10', 'delta_seconds': '10',
'delta_minutes': '0', 'delta_minutes': '0',
}, },
], ],
} }
report.Report.print_report_time_entries(report_data, ctt.REPORTFORMAT, report.Report.print_report_time_entries(report_data, ctt.REPORTFORMAT,
True) True)
output = self._get_output() output = self._get_output()
expected_output = ("2016-04-07-0826 (6): foo1\n" expected_output = ("2016-04-07-0826 (6): foo1\n"
"2016-04-07-0926 (10): foo12") "2016-04-07-0926 (10): foo12")
self.assertEqual(output, expected_output) self.assertEqual(output, expected_output)
def test_print_reports(self): def test_print_reports(self):
reports = collections.OrderedDict() reports = collections.OrderedDict()
for project in ('foo1', 'foo2'): for project in ('foo1', 'foo2'):
rep = report.Report(project, ('2016-04-07',), ('2016-04-08',), rep = report.Report(project, ('2016-04-07', ), ('2016-04-08', ),
ctt.REPORTFORMAT, None, None) ctt.REPORTFORMAT, None, None)
report_data = rep.report() report_data = rep.report()
reports[project] = (rep, report_data) reports[project] = (rep, report_data)
expected_output = ( expected_output = (
"Report for foo1 between 2016-04-07 00:00:00 and 2016-04-08 23:59:59\n" "Report for foo1 between 2016-04-07 00:00:00 and "
"2016-04-08 23:59:59\n"
"2016-04-07-0826 (0:00:06): foo1\n" "2016-04-07-0826 (0:00:06): foo1\n"
"2016-04-08-1200 (1:23:20): foo1 12\n" "2016-04-08-1200 (1:23:20): foo1 12\n"
"Report for foo2 between 2016-04-07 00:00:00 and 2016-04-08 23:59:59\n" "Report for foo2 between 2016-04-07 00:00:00 and "
"2016-04-08 23:59:59\n"
"2016-04-07-0810 (0:00:10): foo2" "2016-04-07-0810 (0:00:10): foo2"
) )
rep.print_reports(reports, ctt.REPORTFORMAT, summary=False) rep.print_reports(reports, ctt.REPORTFORMAT, summary=False)
output = self._get_output() output = self._get_output()
self.assertEqual(output, expected_output) self.assertEqual(output, expected_output)
def test_print_reports_summary(self): # Summary should not print time entries
reports = collections.OrderedDict() # def test_print_reports_summary(self):
for project in ('foo1', 'foo2'): # reports = collections.OrderedDict()
rep = report.Report(project, ('2016-04-07',), ('2016-04-08',), # for project in ('foo1', 'foo2'):
ctt.REPORTFORMAT, None, None) # rep = report.Report(project, ('2016-04-07',), ('2016-04-08',),
report_data = rep.report() # ctt.REPORTFORMAT, None, None)
reports[project] = (rep, report_data) # report_data = rep.report()
expected_output = ( # reports[project] = (rep, report_data)
"2016-04-07-0810 (0:00:10): foo2\n" # expected_output = (
"2016-04-07-0826 (0:00:06): foo1\n" # "2016-04-07-0810 (0:00:10): foo2\n"
"2016-04-08-1200 (1:23:20): foo1 12" # "2016-04-07-0826 (0:00:06): foo1\n"
) # "2016-04-08-1200 (1:23:20): foo1 12"
rep.print_reports(reports, ctt.REPORTFORMAT, summary=True) # )
output = self._get_output() # rep.print_reports(reports, ctt.REPORTFORMAT, summary=True)
self.assertEqual(output, expected_output) # output = self._get_output()
# self.assertEqual(output, expected_output)
def test__init_date(self): def test__init_date(self):
rep = report.Report('foo1', ('2016-04-07',), ('2016-04-07',), rep = report.Report('foo1', ('2016-04-07', ), ('2016-04-07', ),
ctt.REPORTFORMAT, None, None) ctt.REPORTFORMAT, None, None)
expected_start_date = datetime.datetime(2016, 4, 7) expected_start_date = datetime.datetime(2016, 4, 7)
expected_end_date = datetime.datetime(2016, 4, 7, 23, 59, 59) expected_end_date = datetime.datetime(2016, 4, 7, 23, 59, 59)
self.assertEqual(rep.start_date, expected_start_date) self.assertEqual(rep.start_date, expected_start_date)
@ -141,44 +143,46 @@ class ReportTestCase(ctt.test.CttTestCase):
@unittest.expectedFailure @unittest.expectedFailure
def test__init_date_fail(self): def test__init_date_fail(self):
rep = report.Report('foo1', ('2016-04-08',), ('2016-04-07',), report.Report('foo1', ('2016-04-08', ), ('2016-04-07', ),
ctt.REPORTFORMAT, None, None) ctt.REPORTFORMAT, None, None)
def test__init_date_defaults(self): def test__init_date_defaults(self):
rep = report.Report('foo1', None, None, rep = report.Report('foo1', None, None,
ctt.REPORTFORMAT, None, None) ctt.REPORTFORMAT, None, None)
now = datetime.datetime.now() now = datetime.datetime.now()
expected_start_date = now.replace(day=1, hour=0, minute=0, second=0, expected_start_date = now.replace(day=1, hour=0, minute=0, second=0,
microsecond=0) microsecond=0)
next_month = expected_start_date.replace(day=28) + datetime.timedelta(days=4) next_month = expected_start_date.replace(day=28) + datetime.timedelta(
days=4)
first_day_next_month = next_month.replace(day=1) first_day_next_month = next_month.replace(day=1)
expected_end_date = first_day_next_month - datetime.timedelta(seconds=1) expected_end_date = first_day_next_month - datetime.timedelta(
seconds=1)
self.assertEqual(rep.start_date, expected_start_date) self.assertEqual(rep.start_date, expected_start_date)
self.assertEqual(rep.end_date, expected_end_date) self.assertEqual(rep.end_date, expected_end_date)
@unittest.expectedFailure @unittest.expectedFailure
def test__init_report_db_fail(self): def test__init_report_db_fail(self):
rep = report.Report('unexisting', ('2016-04-07',), ('2016-04-07',), report.Report('unexisting', ('2016-04-07',), ('2016-04-07',),
ctt.REPORTFORMAT, None, None) ctt.REPORTFORMAT, None, None)
def test__init_report_db(self): def test__init_report_db(self):
rep = report.Report('foo1', ('2016-04-07',), ('2016-04-07',), rep = report.Report('foo1', ('2016-04-07',), ('2016-04-07',),
ctt.REPORTFORMAT, None, None) ctt.REPORTFORMAT, None, None)
expected_db = { expected_db = {
'2016-04-07-0826': { '2016-04-07-0826': {
'comment': 'foo1', 'comment': 'foo1',
'delta': '6.248274' 'delta': '6.248274'
}, },
} }
self.assertEqual(rep._report_db, expected_db) self.assertEqual(rep._report_db, expected_db)
def test_header(self): def test_header(self):
rep = report.Report('foo1', ('2016-04-07',), ('2016-04-07',), rep = report.Report('foo1', ('2016-04-07',), ('2016-04-07',),
ctt.REPORTFORMAT, None, None) ctt.REPORTFORMAT, None, None)
rep.header() rep.header()
output = self._get_output() output = self._get_output()
self.assertEqual(output, ("Report for foo1 between 2016-04-07 00:00:00" self.assertEqual(output, ("Report for foo1 between 2016-04-07 00:00:00"
" and 2016-04-07 23:59:59")) " and 2016-04-07 23:59:59"))
def test_summary(self): def test_summary(self):
report.Report.summary(10) report.Report.summary(10)
@ -186,36 +190,36 @@ class ReportTestCase(ctt.test.CttTestCase):
self.assertEqual(output, "Total time tracked: 0h 0m 10s.") self.assertEqual(output, "Total time tracked: 0h 0m 10s.")
def test_total_time(self): def test_total_time(self):
rep = report.Report('foo1', ('2016-04-07',), ('2016-04-07',), rep = report.Report('foo1', ('2016-04-07',), ('2016-04-07',),
ctt.REPORTFORMAT, None, None) ctt.REPORTFORMAT, None, None)
total_time = rep.total_time total_time = rep.total_time
expected_total_time = 6.248274 expected_total_time = 6.248274
self.assertEqual(total_time, expected_total_time) self.assertEqual(total_time, expected_total_time)
def test_report(self): def test_report(self):
rep = report.Report('foo1', ('2016-04-07',), ('2016-04-08',), rep = report.Report('foo1', ('2016-04-07',), ('2016-04-08',),
ctt.REPORTFORMAT, None, None) ctt.REPORTFORMAT, None, None)
expected_entries = { expected_entries = {
'2016-04-07-0826': [ '2016-04-07-0826': [
{ {
'start_datetime': '2016-04-07-0826', 'start_datetime': '2016-04-07-0826',
'end_datetime': '2016-04-07-0826', 'end_datetime': '2016-04-07-0826',
'comment': 'foo1', 'comment': 'foo1',
'delta': datetime.timedelta(seconds=6), 'delta': datetime.timedelta(seconds=6),
'delta_seconds': 6, 'delta_seconds': 6,
'delta_minutes': 0, 'delta_minutes': 0,
}, },
], ],
'2016-04-08-1200': [ '2016-04-08-1200': [
{ {
'start_datetime': '2016-04-08-1200', 'start_datetime': '2016-04-08-1200',
'end_datetime': '2016-04-08-1323', 'end_datetime': '2016-04-08-1323',
'comment': 'foo1 12', 'comment': 'foo1 12',
'delta': datetime.timedelta(seconds=5000), 'delta': datetime.timedelta(seconds=5000),
'delta_seconds': 5000, 'delta_seconds': 5000,
'delta_minutes': 83, 'delta_minutes': 83,
}, },
], ],
} }
entries = rep.report() entries = rep.report()
self.assertEqual(entries, expected_entries) self.assertEqual(entries, expected_entries)

View File

@ -28,6 +28,7 @@ import os
import datetime import datetime
import shutil import shutil
class TrackerTestCase(ctt.test.CttTestCase): class TrackerTestCase(ctt.test.CttTestCase):
def setUp(self): def setUp(self):
@ -43,7 +44,7 @@ class TrackerTestCase(ctt.test.CttTestCase):
def test___init__(self): def test___init__(self):
project = 'foo1' project = 'foo1'
expected_project_dir = os.path.join(ctt.test.fixtures_dir, expected_project_dir = os.path.join(ctt.test.fixtures_dir,
os.path.join('.ctt', project)) os.path.join('.ctt', project))
tracker = tr.Tracker(project) tracker = tr.Tracker(project)
self.assertEqual(tracker.project, project) self.assertEqual(tracker.project, project)
self.assertEqual(tracker.project_dir, expected_project_dir) self.assertEqual(tracker.project_dir, expected_project_dir)
@ -52,31 +53,31 @@ class TrackerTestCase(ctt.test.CttTestCase):
self.assertIsNone(tracker.comment) self.assertIsNone(tracker.comment)
self.assertFalse(tracker._tracked_time) self.assertFalse(tracker._tracked_time)
tracker = tr.Tracker(project, start_datetime=('2016-04-09-0900',)) tracker = tr.Tracker(project, start_datetime=('2016-04-09-0900', ))
self.assertEqual(tracker.start_datetime, self.assertEqual(tracker.start_datetime,
datetime.datetime(2016, 4, 9, 9, 0)) datetime.datetime(2016, 4, 9, 9, 0))
self.assertIsNone(tracker.end_datetime) self.assertIsNone(tracker.end_datetime)
self.assertFalse(tracker._tracked_time) self.assertFalse(tracker._tracked_time)
tracker = tr.Tracker(project, start_datetime=('2016-04-04-0900',), tracker = tr.Tracker(project, start_datetime=('2016-04-04-0900', ),
end_datetime=('2016-04-09-2000',)) end_datetime=('2016-04-09-2000',))
self.assertEqual(tracker.start_datetime, self.assertEqual(tracker.start_datetime,
datetime.datetime(2016, 4, 4, 9, 0)) datetime.datetime(2016, 4, 4, 9, 0))
self.assertEqual(tracker.end_datetime, self.assertEqual(tracker.end_datetime,
datetime.datetime(2016, 4, 9, 20, 0)) datetime.datetime(2016, 4, 9, 20, 0))
self.assertTrue(tracker._tracked_time) self.assertTrue(tracker._tracked_time)
@unittest.expectedFailure @unittest.expectedFailure
def test__init__fail(self): def test__init__fail(self):
project = 'foo1' project = 'foo1'
tracker = tr.Tracker(project, start_datetime=('2016-04-090900',)) tr.Tracker(project, start_datetime=('2016-04-090900', ))
def test_delta(self): def test_delta(self):
project = 'foo1' project = 'foo1'
start_dt = datetime.datetime(2016, 4, 4, 9, 0) start_dt = datetime.datetime(2016, 4, 4, 9, 0)
end_dt = datetime.datetime(2016, 4, 9, 20, 0) end_dt = datetime.datetime(2016, 4, 9, 20, 0)
tracker = tr.Tracker(project, start_datetime=('2016-04-04-0900',), tracker = tr.Tracker(project, start_datetime=('2016-04-04-0900', ),
end_datetime=('2016-04-09-2000',)) end_datetime=('2016-04-09-2000', ))
expected_delta = end_dt - start_dt expected_delta = end_dt - start_dt
tracker._tracked_time = True tracker._tracked_time = True
delta = tracker.delta(True) delta = tracker.delta(True)
@ -93,7 +94,7 @@ class TrackerTestCase(ctt.test.CttTestCase):
project = 'foo1' project = 'foo1'
start_dt = '2016-04-09-1730' start_dt = '2016-04-09-1730'
tracker = tr.Tracker(project, start_datetime=(start_dt,), tracker = tr.Tracker(project, start_datetime=(start_dt,),
comment=True) comment=True)
end_dt = datetime.datetime(2016, 4, 9, hour=17, minute=45) end_dt = datetime.datetime(2016, 4, 9, hour=17, minute=45)
expected_delta = str(15 * 60) + '.0\n' # seconds expected_delta = str(15 * 60) + '.0\n' # seconds
tracker.end_datetime = end_dt tracker.end_datetime = end_dt
@ -102,7 +103,7 @@ class TrackerTestCase(ctt.test.CttTestCase):
tracker.comment = expected_comment tracker.comment = expected_comment
expected_comment += "\n" expected_comment += "\n"
timedir = os.path.join(ctt.test.fixtures_dir, '.ctt', project, timedir = os.path.join(ctt.test.fixtures_dir, '.ctt', project,
'2016-04-09-1730') '2016-04-09-1730')
self.rm_dirs.append(timedir) self.rm_dirs.append(timedir)
if os.path.exists(timedir): if os.path.exists(timedir):
shutil.rmtree(timedir) shutil.rmtree(timedir)
@ -124,15 +125,15 @@ class TrackerTestCase(ctt.test.CttTestCase):
project = 'foo1' project = 'foo1'
start_dt = '2016-04-09-1730' start_dt = '2016-04-09-1730'
tracker = tr.Tracker(project, start_datetime=(start_dt,), tracker = tr.Tracker(project, start_datetime=(start_dt,),
comment=True) comment=True)
end_dt = datetime.datetime(2016, 4, 9, hour=17, minute=45) end_dt = datetime.datetime(2016, 4, 9, hour=17, minute=45)
expected_delta = 15 * 60 # seconds # expected_delta = 15 * 60 # seconds
tracker.end_datetime = end_dt tracker.end_datetime = end_dt
tracker._tracked_time = True tracker._tracked_time = True
expected_comment = "test" expected_comment = "test"
tracker.comment = expected_comment tracker.comment = expected_comment
timedir = os.path.join(ctt.test.fixtures_dir, '.ctt', project, timedir = os.path.join(ctt.test.fixtures_dir, '.ctt', project,
'2016-04-09-1730') '2016-04-09-1730')
self.rm_dirs.append(timedir) self.rm_dirs.append(timedir)
if os.path.exists(timedir): if os.path.exists(timedir):
shutil.rmtree(timedir) shutil.rmtree(timedir)

View File

@ -22,17 +22,17 @@
import datetime import datetime
import logging import logging
import time
import os import os
import os.path import os.path
import sys
import ctt import ctt
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class Tracker: class Tracker:
def __init__(self, project, start_datetime = None, end_datetime = None, comment = True): def __init__(self, project, start_datetime=None, end_datetime=None,
comment=True):
self.project = project self.project = project
self.project_dir = ctt.project_dir(project) self.project_dir = ctt.project_dir(project)
@ -42,12 +42,14 @@ class Tracker:
# Setup default values # Setup default values
try: try:
if start_datetime: if start_datetime:
self.start_datetime = datetime.datetime.strptime(start_datetime[0], ctt.DATETIMEFORMAT) self.start_datetime = datetime.datetime.strptime(
start_datetime[0], ctt.DATETIMEFORMAT)
else: else:
self.start_datetime = None self.start_datetime = None
if end_datetime: if end_datetime:
self.end_datetime = datetime.datetime.strptime(end_datetime[0], ctt.DATETIMEFORMAT) self.end_datetime = datetime.datetime.strptime(
end_datetime[0], ctt.DATETIMEFORMAT)
else: else:
self.end_datetime = None self.end_datetime = None
except ValueError as e: except ValueError as e:
@ -56,7 +58,6 @@ class Tracker:
if self.start_datetime and self.end_datetime: if self.start_datetime and self.end_datetime:
self._tracked_time = True self._tracked_time = True
@classmethod @classmethod
def commandline(cls, args): def commandline(cls, args):
tracker = cls(args.project[0], args.start, args.end, args.comment) tracker = cls(args.project[0], args.start, args.end, args.comment)
@ -100,13 +101,14 @@ class Tracker:
if self.start_datetime >= self.end_datetime: if self.start_datetime >= self.end_datetime:
raise ctt.Error("End date must be after start date! (%s > %s)!" % raise ctt.Error("End date must be after start date! (%s > %s)!" %
(self.start_datetime, self.end_datetime)) (self.start_datetime, self.end_datetime))
subdirname = self.start_datetime.strftime(ctt.DISKFORMAT) subdirname = self.start_datetime.strftime(ctt.DISKFORMAT)
time_dir = os.path.join(self.project_dir, subdirname) time_dir = os.path.join(self.project_dir, subdirname)
if os.path.exists(time_dir): if os.path.exists(time_dir):
raise ctt.Error("Already tracked time at this beginning for this project") raise ctt.Error(
"Already tracked time at this beginning for this project")
os.makedirs(time_dir, mode=0o700) os.makedirs(time_dir, mode=0o700)

View File

@ -23,65 +23,89 @@
import argparse import argparse
import logging import logging
import os.path
import sys import sys
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# Setup locale for calendar printing # Setup locale for calendar printing
# Setup locale to get Timezone information? # Setup locale to get Timezone information?
#print(locale.getlocale()) # print(locale.getlocale())
# Record tags # Record tags
def parse_argv(argv, version): def parse_argv(argv, version):
parser = {} parser = {}
parser['loglevel'] = argparse.ArgumentParser(add_help=False) parser['loglevel'] = argparse.ArgumentParser(add_help=False)
parser['loglevel'].add_argument('-d', '--debug', parser['loglevel'].add_argument(
help='set log level to debug', action='store_true', '-d', '--debug', help='set log level to debug', action='store_true',
default=False) default=False)
parser['loglevel'].add_argument('-v', '--verbose', parser['loglevel'].add_argument(
'-v', '--verbose',
help='set log level to info, be more verbose', help='set log level to info, be more verbose',
action='store_true', default=False) action='store_true', default=False)
parser['main'] = argparse.ArgumentParser(description='ctt ' + version, parser['main'] = argparse.ArgumentParser(description='ctt ' + version,
parents=[parser['loglevel']]) parents=[parser['loglevel']])
parser['sub'] = parser['main'].add_subparsers(title="Commands") parser['sub'] = parser['main'].add_subparsers(title="Commands")
parser['listprojects'] = parser['sub'].add_parser(
parser['listprojects'] = parser['sub'].add_parser('listprojects', 'listprojects', parents=[parser['loglevel']])
parents=[parser['loglevel']])
parser['listprojects'].set_defaults(func=ListProjects.commandline) parser['listprojects'].set_defaults(func=ListProjects.commandline)
parser['track'] = parser['sub'].add_parser('track', parser['track'] = parser['sub'].add_parser('track',
parents=[parser['loglevel']]) parents=[parser['loglevel']])
parser['track'].set_defaults(func=Tracker.commandline) parser['track'].set_defaults(func=Tracker.commandline)
parser['track'].add_argument("--sd", "--start", help="start date (default: first of this month, format: %s)" % ctt.DATEFORMAT_PLAIN, parser['track'].add_argument(
"--sd", "--start",
help="start date (default: first of this month, format: %s)"
% ctt.DATEFORMAT_PLAIN,
nargs=1, dest="start") nargs=1, dest="start")
parser['track'].add_argument("--ed", "--end", help="end date (default: last of this month, format: %s)" % ctt.DATEFORMAT_PLAIN, parser['track'].add_argument(
"--ed", "--end",
help="end date (default: last of this month, format: %s)"
% ctt.DATEFORMAT_PLAIN,
nargs=1, default=None, dest="end") nargs=1, default=None, dest="end")
parser['track'].add_argument("-n", "--no-comment", help="disable comment prompting after tracking", parser['track'].add_argument(
"-n", "--no-comment", help="disable comment prompting after tracking",
action='store_false', dest="comment") action='store_false', dest="comment")
parser['track'].add_argument("project", help="project to track time for", nargs=1) parser['track'].add_argument(
"project", help="project to track time for", nargs=1)
parser['report'] = parser['sub'].add_parser('report', parser['report'] = parser['sub'].add_parser('report',
parents=[parser['loglevel']]) parents=[parser['loglevel']])
parser['report'].set_defaults(func=Report.commandline) parser['report'].set_defaults(func=Report.commandline)
parser['report'].add_argument("project", help="project to report time for", nargs='*') parser['report'].add_argument(
parser['report'].add_argument("--sd", "--start", help="start date (default: first of this month, format: %s)" % ctt.DATEFORMAT_PLAIN, "project", help="project to report time for", nargs='*')
parser['report'].add_argument(
"--sd", "--start",
help="start date (default: first of this month, format: %s)"
% ctt.DATEFORMAT_PLAIN,
nargs=1, dest="start") nargs=1, dest="start")
parser['report'].add_argument("--ed", "--end", help="end date (default: last of this month, format: %s)" % ctt.DATEFORMAT_PLAIN, parser['report'].add_argument(
"--ed", "--end",
help="end date (default: last of this month, format: %s)"
% ctt.DATEFORMAT_PLAIN,
nargs=1, default=None, dest="end") nargs=1, default=None, dest="end")
parser['report'].add_argument("-a", "--all", help="List entries for all projects", action='store_true') parser['report'].add_argument(
parser['report'].add_argument("-e", "--regexp", help="regular expression to match", "-a", "--all", help="List entries for all projects",
default=None) action='store_true')
parser['report'].add_argument("-i", "--ignore-case", help="ignore case distinctions", action="store_true") parser['report'].add_argument(
parser['report'].add_argument("-f", "--format", help="output format (default: %s)" % ctt.REPORTFORMAT, "-e", "--regexp", help="regular expression to match", default=None)
parser['report'].add_argument(
"-i", "--ignore-case", help="ignore case distinctions",
action="store_true")
parser['report'].add_argument(
"-f", "--format",
help="output format (default: %s)" % ctt.REPORTFORMAT,
default=ctt.REPORTFORMAT, dest="output_format") default=ctt.REPORTFORMAT, dest="output_format")
parser['report'].add_argument("-s", "--summary", help="hide project names and list time entries in chronological order", action="store_true") parser['report'].add_argument(
"-s", "--summary",
help="hide project names and list time entries in chronological order",
action="store_true")
#parser['track'].add_argument("-t", "--tag", help="Add tags", # parser['track'].add_argument("-t", "--tag", help="Add tags",
# action="store_true") # action="store_true")
args = parser['main'].parse_args() args = parser['main'].parse_args()
@ -114,6 +138,5 @@ if __name__ == "__main__":
from ctt.report import Report from ctt.report import Report
from ctt.listprojects import ListProjects from ctt.listprojects import ListProjects
parse_argv(sys.argv[1:], ctt.VERSION) parse_argv(sys.argv[1:], ctt.VERSION)
sys.exit(0) sys.exit(0)

View File

@ -4,9 +4,10 @@ script to install ctt
""" """
import sys import sys
from setuptools import setup from setuptools import setup
sys.path.insert(0, 'lib/')
import ctt import ctt
sys.path.insert(0, 'lib/')
setup(name='ctt', setup(name='ctt',
version=ctt.VERSION, version=ctt.VERSION,
author=ctt.AUTHOR, author=ctt.AUTHOR,
@ -28,5 +29,5 @@ setup(name='ctt',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Operating System :: POSIX', 'Operating System :: POSIX',
'Programming Language :: Python', 'Programming Language :: Python',
'Requires-Python:: 3.x'] 'Requires-Python:: 3.x'
) ])