From 75cdb70e5cce71b7be5ed8a6b58d6c3921b47087 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Dardenne?= Date: Fri, 10 Apr 2020 22:21:05 +0200 Subject: [PATCH 1/3] Introduce options to configure the ordering of the projects and tags commands --- tests/test_watson.py | 30 +++++++++++++++++------ watson/autocompletion.py | 4 +-- watson/cli.py | 50 ++++++++++++++++++++++++++++++------- watson/watson.py | 53 ++++++++++++++++++++++++++++++++-------- 4 files changed, 108 insertions(+), 29 deletions(-) diff --git a/tests/test_watson.py b/tests/test_watson.py index 6b8261d2..9670e140 100644 --- a/tests/test_watson.py +++ b/tests/test_watson.py @@ -681,35 +681,49 @@ def json(self): # projects def test_projects(watson): + i = 0 for name in ('foo', 'bar', 'bar', 'bar', 'foo', 'lol'): - watson.frames.add(name, 4000, 4000) + watson.frames.add(name, 4000 + i, 4000 + i) + i += 1 - assert watson.projects == ['bar', 'foo', 'lol'] + watson.frames.add("oldest", 2000, 2005) + watson.frames.add("recent", 5000, 5005) + + assert watson.projects() == ['bar', 'foo', 'lol', 'oldest', 'recent'] + assert (watson.projects(orderby='start') + == ['oldest', 'bar', 'foo', 'lol', 'recent']) + assert (watson.projects(orderby='start', descending=True) + == ['recent', 'lol', 'foo', 'bar', 'oldest']) def test_projects_no_frames(watson): - assert watson.projects == [] + assert watson.projects() == [] # tags def test_tags(watson): - samples = ( + samples = [ ('foo', ('A', 'D')), ('bar', ('A', 'C')), ('foo', ('B', 'C')), ('lol', ()), ('bar', ('C')) - ) + ] + i = 0 for name, tags in samples: - watson.frames.add(name, 4000, 4000, tags) + watson.frames.add(name, 4000 + i, 4000 + i, tags) + i += 1 - assert watson.tags == ['A', 'B', 'C', 'D'] + assert watson.tags() == ['A', 'B', 'C', 'D'] + assert watson.tags(orderby='start') == ['D', 'A', 'B', 'C'] + assert (watson.tags(orderby='start', descending=True) + == ['C', 'B', 'A', 'D']) def test_tags_no_frames(watson): - assert watson.tags == [] + assert watson.tags() == [] # merge diff --git a/watson/autocompletion.py b/watson/autocompletion.py index 9152b69f..9602856e 100644 --- a/watson/autocompletion.py +++ b/watson/autocompletion.py @@ -62,7 +62,7 @@ def prepend_plus(tag_suggestions): def get_projects(ctx, args, incomplete): """Function to return all projects matching the prefix.""" watson = _bypass_click_bug_to_ensure_watson(ctx) - for cur_project in watson.projects: + for cur_project in watson.projects(): if cur_project.startswith(incomplete): yield cur_project @@ -98,7 +98,7 @@ def get_rename_types(ctx, args, incomplete): def get_tags(ctx, args, incomplete): """Function to return all tags matching the prefix.""" watson = _bypass_click_bug_to_ensure_watson(ctx) - for cur_tag in watson.tags: + for cur_tag in watson.tags(): if cur_tag.startswith(incomplete): yield cur_tag diff --git a/watson/cli.py b/watson/cli.py index 985d39f3..0cfa15dc 100644 --- a/watson/cli.py +++ b/watson/cli.py @@ -215,7 +215,7 @@ def start(ctx, watson, confirm_new_project, confirm_new_tag, args, gap_=True): # Confirm creation of new project if that option is set if (watson.config.getboolean('options', 'confirm_new_project') or confirm_new_project): - confirm_project(project, watson.projects) + confirm_project(project, watson.projects()) # Parse all the tags tags = parse_tags(args) @@ -223,7 +223,7 @@ def start(ctx, watson, confirm_new_project, confirm_new_tag, args, gap_=True): # Confirm creation of new tag(s) if that option is set if (watson.config.getboolean('options', 'confirm_new_tag') or confirm_new_tag): - confirm_tags(tags, watson.tags) + confirm_tags(tags, watson.tags()) if project and watson.is_started and not gap_: current = watson.current @@ -1060,9 +1060,13 @@ def _final_print(lines): @cli.command() +@click.option('-b', '--by', 'orderby', default='name', + help="Sort the ouput by either 'name' or 'time'") +@click.option('-r', '--reverse', 'reverse_order', default=False, is_flag=True, + help="Reverse the output (sort by descending order)") @click.pass_obj @catch_watson_error -def projects(watson): +def projects(watson, orderby, reverse_order): """ Display the list of all the existing projects. @@ -1075,14 +1079,30 @@ def projects(watson): voyager1 voyager2 """ - for project in watson.projects: + if orderby == 'name': + orderby = 'project' + elif orderby == 'time': + orderby = 'start' + else: + raise click.ClickException(style( + 'error', + u'--by option can be either "name" or "time". ' + u'You supplied "%s"' % orderby + )) + + projects = watson.projects(orderby=orderby, descending=reverse_order) + for project in projects: click.echo(style('project', project)) @cli.command() +@click.option('-b', '--by', 'orderby', default='name', + help="Sort the ouput by either 'name' or 'time'") +@click.option('-r', '--reverse', 'reverse_order', default=False, is_flag=True, + help="Reverse the output (sort by desceding order)") @click.pass_obj @catch_watson_error -def tags(watson): +def tags(watson, orderby, reverse_order): """ Display the list of all the tags. @@ -1103,7 +1123,19 @@ def tags(watson): transmission wheels """ - for tag in watson.tags: + if orderby == 'name': + orderby = 'project' + elif orderby == 'time': + orderby = 'start' + else: + raise click.ClickException(style( + 'error', + u'--by option can be either "name" or "time". ' + u'You supplied "%s"' % orderby + )) + + tags = watson.tags(orderby=orderby, descending=reverse_order) + for tag in tags: click.echo(style('tag', tag)) @@ -1160,7 +1192,7 @@ def add(watson, args, from_, to, confirm_new_project, confirm_new_tag): # Confirm creation of new project if that option is set if (watson.config.getboolean('options', 'confirm_new_project') or confirm_new_project): - confirm_project(project, watson.projects) + confirm_project(project, watson.projects()) # Parse all the tags tags = parse_tags(args) @@ -1168,7 +1200,7 @@ def add(watson, args, from_, to, confirm_new_project, confirm_new_tag): # Confirm creation of new tag(s) if that option is set if (watson.config.getboolean('options', 'confirm_new_tag') or confirm_new_tag): - confirm_tags(tags, watson.tags) + confirm_tags(tags, watson.tags()) # add a new frame, call watson save to update state files frame = watson.add(project=project, tags=tags, from_date=from_, to_date=to) @@ -1260,7 +1292,7 @@ def edit(watson, confirm_new_project, confirm_new_tag, id): # Confirm creation of new tag(s) if that option is set if (watson.config.getboolean('options', 'confirm_new_tag') or confirm_new_tag): - confirm_tags(tags, watson.tags) + confirm_tags(tags, watson.tags()) start = arrow.get(data['start'], datetime_format).replace( tzinfo=local_tz).to('utc') stop = arrow.get(data['stop'], datetime_format).replace( diff --git a/watson/watson.py b/watson/watson.py index 388bb973..852cf3d9 100644 --- a/watson/watson.py +++ b/watson/watson.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from collections import OrderedDict import datetime from functools import reduce import json @@ -303,19 +304,51 @@ def cancel(self): self.current = None return old_current - @property - def projects(self): + def projects(self, orderby="project", descending=False): """ - Return the list of all the existing projects, sorted by name. + Return the list of all the existing projects. """ - return sorted(set(self.frames['project'])) - @property - def tags(self): + ordered_frames = sorted(self.frames, key=operator.attrgetter(orderby)) + ordered_projects = list(map(operator.attrgetter("project"), + ordered_frames)) + + # Keep latest occurence only, so we feed a reversed list + # to the ordered set. + ordered_projects.reverse() + + # Use OrderedDict as a set to remove duplicates by keep order. + result = list(OrderedDict.fromkeys(ordered_projects)) + + if not descending: + result.reverse() + + return result + + def tags(self, orderby="tag", descending=False): """ - Return the list of the tags, sorted by name. + Return the list of the tags. """ - return sorted(set(tag for tags in self.frames['tags'] for tag in tags)) + if orderby == "tag": + tags = [tag for tags in self.frames['tags'] for tag in tags] + return sorted(set(tags)) + else: + ordered_frames = sorted(self.frames, + key=operator.attrgetter(orderby)) + ordered_tags = map(operator.attrgetter("tags"), ordered_frames) + flat_tags = [tag for tags in ordered_tags for tag in tags] + + # Keep latest occurence only, so we feed a reversed list + # to the ordered set. + flat_tags.reverse() + + result = list(OrderedDict.fromkeys(flat_tags)) + + if not descending: + result.reverse() + + # Use OrderedDict as a set to remove duplicates keep order. + return result def _get_request_info(self, route): config = self.config @@ -542,7 +575,7 @@ def report(self, from_, to, current=None, projects=None, tags=None, def rename_project(self, old_project, new_project): """Rename a project in all affected frames.""" - if old_project not in self.projects: + if old_project not in self.projects(): raise WatsonError(u'Project "%s" does not exist' % old_project) updated_at = arrow.utcnow() @@ -559,7 +592,7 @@ def rename_project(self, old_project, new_project): def rename_tag(self, old_tag, new_tag): """Rename a tag in all affected frames.""" - if old_tag not in self.tags: + if old_tag not in self.tags(): raise WatsonError(u'Tag "%s" does not exist' % old_tag) updated_at = arrow.utcnow() From 02592d7fa75e7278897a413059551abc7a64b6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Dardenne?= Date: Fri, 10 Apr 2020 22:56:28 +0200 Subject: [PATCH 2/3] Use a less confusion parameter name --- watson/cli.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/watson/cli.py b/watson/cli.py index 0cfa15dc..1060a003 100644 --- a/watson/cli.py +++ b/watson/cli.py @@ -1062,11 +1062,11 @@ def _final_print(lines): @cli.command() @click.option('-b', '--by', 'orderby', default='name', help="Sort the ouput by either 'name' or 'time'") -@click.option('-r', '--reverse', 'reverse_order', default=False, is_flag=True, +@click.option('-r', '--reverse', 'descending', default=False, is_flag=True, help="Reverse the output (sort by descending order)") @click.pass_obj @catch_watson_error -def projects(watson, orderby, reverse_order): +def projects(watson, orderby, descending): """ Display the list of all the existing projects. @@ -1090,7 +1090,7 @@ def projects(watson, orderby, reverse_order): u'You supplied "%s"' % orderby )) - projects = watson.projects(orderby=orderby, descending=reverse_order) + projects = watson.projects(orderby=orderby, descending=descending) for project in projects: click.echo(style('project', project)) @@ -1098,8 +1098,8 @@ def projects(watson, orderby, reverse_order): @cli.command() @click.option('-b', '--by', 'orderby', default='name', help="Sort the ouput by either 'name' or 'time'") -@click.option('-r', '--reverse', 'reverse_order', default=False, is_flag=True, - help="Reverse the output (sort by desceding order)") +@click.option('-r', '--reverse', 'descending', default=False, is_flag=True, + help="Reverse the output (sort by descending order)") @click.pass_obj @catch_watson_error def tags(watson, orderby, reverse_order): From 4f4899ec4131743a9e6f282bb0146d7a4164b1bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Dardenne?= Date: Sat, 11 Apr 2020 00:03:26 +0200 Subject: [PATCH 3/3] Use a less confusing parameter name --- watson/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/watson/cli.py b/watson/cli.py index 1060a003..71b446ab 100644 --- a/watson/cli.py +++ b/watson/cli.py @@ -1102,7 +1102,7 @@ def projects(watson, orderby, descending): help="Reverse the output (sort by descending order)") @click.pass_obj @catch_watson_error -def tags(watson, orderby, reverse_order): +def tags(watson, orderby, descending): """ Display the list of all the tags. @@ -1134,7 +1134,7 @@ def tags(watson, orderby, reverse_order): u'You supplied "%s"' % orderby )) - tags = watson.tags(orderby=orderby, descending=reverse_order) + tags = watson.tags(orderby=orderby, descending=descending) for tag in tags: click.echo(style('tag', tag))