diff --git a/dit/dit.py b/dit/dit.py index d9346e3..67daf9a 100644 --- a/dit/dit.py +++ b/dit/dit.py @@ -298,6 +298,7 @@ def load_plugin(plugin_name): FILTER_OPTIONS = [ "--verbose", "--id-only", + "--name-only", "--sum", "--from", "--to", @@ -1173,6 +1174,8 @@ def status(self, argv): options['verbose'] = True elif opt in ["--id-only", "-i"]: options['id-only'] = True + elif opt in ["--name-only", "-i"]: + options['name-only'] = True elif opt in ["--sum", "-s"]: options["sum"] = True elif opt in ["--from"]: @@ -1214,6 +1217,7 @@ def list(self, argv): @command("o", LIST_OPTIONS + ["--output", "--format"], SELECT_FORWARD, True) def export(self, argv, listing=False): all = False + daily = False output_file = None output_format = None @@ -1230,6 +1234,8 @@ def export(self, argv, listing=False): options['verbose'] = True elif opt in ["--id-only", "-i"]: options['id-only'] = True + elif opt in ["--name-only", "-i"]: + options['name-only'] = True elif opt in ["--sum", "-s"]: options["sum"] = True elif opt in ["--from"]: @@ -1240,6 +1246,8 @@ def export(self, argv, listing=False): filters["where"] = [argv.pop(0), re.compile(argv.pop(0))] elif opt in ["--all", "-a"]: all = True + elif opt in ["--daily", "-d"]: + daily = True elif opt in ["--concluded", "-c"]: options['concluded'] = True elif opt in ["--compact", "-z"]: @@ -1271,7 +1279,10 @@ def export(self, argv, listing=False): output_format = output_format or 'dit' - self.exporter = load_plugin("%s_exporter" % output_format) + if daily: + self.exporter = load_plugin("%s_exporter_daily" % output_format) + else: + self.exporter = load_plugin("%s_exporter" % output_format) self.exporter.setup(exporter_stdout, options) self.exporter.begin() diff --git a/dit/dit_exporter.py b/dit/dit_exporter.py index 5b30529..0a43f2b 100644 --- a/dit/dit_exporter.py +++ b/dit/dit_exporter.py @@ -17,6 +17,7 @@ _options = { 'verbose': False, 'id-only': False, + 'name-only': False, 'concluded': False, 'statussing': False, 'compact-header': False, @@ -125,6 +126,7 @@ def task(group, group_id, subgroup, subgroup_id, task, task_id, data): # options verbose = _options['verbose'] id_only = _options['id-only'] + name_only = _options['name-only'] concluded = _options['concluded'] statussing = _options['statussing'] filters = _options['filters'] @@ -141,6 +143,9 @@ def task(group, group_id, subgroup, subgroup_id, task, task_id, data): if id_only: _write('%s/%s/%s' % (group_id, subgroup_id, task_id)) return + elif name_only: + _write(names_to_string(group, subgroup, task)) + return if _options.get('compact-header'): _write(_ca('[%s/%s/%s]' % (group_id, subgroup_id, task_id)) + ' ' + diff --git a/dit/dit_exporter_daily.py b/dit/dit_exporter_daily.py new file mode 100644 index 0000000..4df8565 --- /dev/null +++ b/dit/dit_exporter_daily.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- + +import io +import sys +import subprocess +from datetime import timedelta + +from .dit import names_to_string +from .utils import ( + apply_filters, + convert_datetimes, + dt2str, + now, +) + +_0_seconds = timedelta(0) + +_file = None +_isatty = False +_pager = None +_days = {} +_options = { + 'filters': {} +} +_now = None + + +# =========================================== +# Colors + + +def _ca(string): + if _isatty: + return '\033[2;34m' + string + '\033[0m' + return string + + +def _cc(string): + if _isatty: + return '\033[2;31m' + string + '\033[0m' + return string + + +def _cd(string): + if _isatty: + return '\033[2;32m' + string + '\033[0m' + return string + + +def _ce(string): + if _isatty: + return '\033[0;36m' + string + '\033[0m' + return string + + +# =========================================== +# Write helpers + + +def _write(string=''): + _file.write('%s\n' % string) + + +# =========================================== +# Logic helpers + +def _lazy_now(): + global _now + if _now is None: + _now = now() + return _now + + +def _calc_delta(log_entry): + if log_entry[1] is None: + return _lazy_now() - log_entry[0] + return log_entry[1] - log_entry[0] + + +# =========================================== +# API + + +def setup(file, options): + global _file + global _options + global _isatty + _file = file + _isatty = file.isatty() + _options.update(options) + + +def begin(): + if _isatty: + global _pager + global _file + _pager = subprocess.Popen(['less', '-F', '-R', '-S', '-X', '-K'], + stdin=subprocess.PIPE, stdout=sys.stdout) + _file = io.TextIOWrapper(_pager.stdin, 'UTF-8') + + +def end(): + for d, logs in sorted(_days.items(), reverse=True): + total = sum(map(_calc_delta, logs), _0_seconds) + + _write('%s (%s)' % (_ca(d.strftime(r'%A %x')), _ce('%s' % total))) + for lin, lout, name, title in sorted(logs, reverse=True): + string = ' - %s' % dt2str(lin) + lout_s = dt2str(lout) if lout else 'ongoing' + string += ' ~ %s (%s)' % (lout_s, _ce('%s' % _calc_delta([lin, lout]))) + string += ' : [%s] %s' % (_cc(name), _cd(title)) + _write(string) + + if _isatty: + _file.close() + _pager.wait() + + +def group(group, group_id): + pass + + +def subgroup(group, group_id, subgroup, subgroup_id): + pass + + +def task(group, group_id, subgroup, subgroup_id, task, task_id, data): + filters = _options['filters'] + + data = apply_filters(convert_datetimes(data), filters) + if not data: + return + + logbook = data.get('logbook', []) + + if not logbook: + return + + for log in logbook: + d = log['in'].date() + + if d not in _days: + _days[d] = [] + + _days[d].append(( + log['in'], + log['out'], + names_to_string(group, subgroup, task), + data.get('title'), + )) diff --git a/dit/utils.py b/dit/utils.py index 9032a69..decec38 100644 --- a/dit/utils.py +++ b/dit/utils.py @@ -63,7 +63,7 @@ def today(): LOCALZONE = get_localzone() def now(**kwargs): - return datetime.now(LOCALZONE) + return datetime.now(LOCALZONE).replace(microsecond=0) def today(): return now().replace(hour=0,