Skip to content

Commit 99c3a40

Browse files
committed
Introduce options to configure the ordering of the projects and tags commands
1 parent d28a2bd commit 99c3a40

4 files changed

Lines changed: 108 additions & 29 deletions

File tree

tests/test_watson.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -681,35 +681,49 @@ def json(self):
681681
# projects
682682

683683
def test_projects(watson):
684+
i = 0
684685
for name in ('foo', 'bar', 'bar', 'bar', 'foo', 'lol'):
685-
watson.frames.add(name, 4000, 4000)
686+
watson.frames.add(name, 4000 + i, 4000 + i)
687+
i += 1
686688

687-
assert watson.projects == ['bar', 'foo', 'lol']
689+
watson.frames.add("oldest", 2000, 2005)
690+
watson.frames.add("recent", 5000, 5005)
691+
692+
assert watson.projects() == ['bar', 'foo', 'lol', 'oldest', 'recent']
693+
assert (watson.projects(orderby='start')
694+
== ['oldest', 'bar', 'foo', 'lol', 'recent'])
695+
assert (watson.projects(orderby='start', descending=True)
696+
== ['recent', 'lol', 'foo', 'bar', 'oldest'])
688697

689698

690699
def test_projects_no_frames(watson):
691-
assert watson.projects == []
700+
assert watson.projects() == []
692701

693702

694703
# tags
695704

696705
def test_tags(watson):
697-
samples = (
706+
samples = [
698707
('foo', ('A', 'D')),
699708
('bar', ('A', 'C')),
700709
('foo', ('B', 'C')),
701710
('lol', ()),
702711
('bar', ('C'))
703-
)
712+
]
704713

714+
i = 0
705715
for name, tags in samples:
706-
watson.frames.add(name, 4000, 4000, tags)
716+
watson.frames.add(name, 4000 + i, 4000 + i, tags)
717+
i += 1
707718

708-
assert watson.tags == ['A', 'B', 'C', 'D']
719+
assert watson.tags() == ['A', 'B', 'C', 'D']
720+
assert watson.tags(orderby='start') == ['D', 'A', 'B', 'C']
721+
assert (watson.tags(orderby='start', descending=True)
722+
== ['C', 'B', 'A', 'D'])
709723

710724

711725
def test_tags_no_frames(watson):
712-
assert watson.tags == []
726+
assert watson.tags() == []
713727

714728

715729
# merge

watson/autocompletion.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def prepend_plus(tag_suggestions):
6262
def get_projects(ctx, args, incomplete):
6363
"""Function to return all projects matching the prefix."""
6464
watson = _bypass_click_bug_to_ensure_watson(ctx)
65-
for cur_project in watson.projects:
65+
for cur_project in watson.projects():
6666
if cur_project.startswith(incomplete):
6767
yield cur_project
6868

@@ -98,7 +98,7 @@ def get_rename_types(ctx, args, incomplete):
9898
def get_tags(ctx, args, incomplete):
9999
"""Function to return all tags matching the prefix."""
100100
watson = _bypass_click_bug_to_ensure_watson(ctx)
101-
for cur_tag in watson.tags:
101+
for cur_tag in watson.tags():
102102
if cur_tag.startswith(incomplete):
103103
yield cur_tag
104104

watson/cli.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -215,15 +215,15 @@ def start(ctx, watson, confirm_new_project, confirm_new_tag, args, gap_=True):
215215
# Confirm creation of new project if that option is set
216216
if (watson.config.getboolean('options', 'confirm_new_project') or
217217
confirm_new_project):
218-
confirm_project(project, watson.projects)
218+
confirm_project(project, watson.projects())
219219

220220
# Parse all the tags
221221
tags = parse_tags(args)
222222

223223
# Confirm creation of new tag(s) if that option is set
224224
if (watson.config.getboolean('options', 'confirm_new_tag') or
225225
confirm_new_tag):
226-
confirm_tags(tags, watson.tags)
226+
confirm_tags(tags, watson.tags())
227227

228228
if project and watson.is_started and not gap_:
229229
current = watson.current
@@ -1060,9 +1060,13 @@ def _final_print(lines):
10601060

10611061

10621062
@cli.command()
1063+
@click.option('-b', '--by', 'orderby', default='name',
1064+
help="Sort the ouput by either 'name' or 'time'")
1065+
@click.option('-r', '--reverse', 'descending', default=False, is_flag=True,
1066+
help="Reverse the output (sort by descending order)")
10631067
@click.pass_obj
10641068
@catch_watson_error
1065-
def projects(watson):
1069+
def projects(watson, orderby, descending):
10661070
"""
10671071
Display the list of all the existing projects.
10681072
@@ -1075,14 +1079,30 @@ def projects(watson):
10751079
voyager1
10761080
voyager2
10771081
"""
1078-
for project in watson.projects:
1082+
if orderby == 'name':
1083+
orderby = 'project'
1084+
elif orderby == 'time':
1085+
orderby = 'start'
1086+
else:
1087+
raise click.ClickException(style(
1088+
'error',
1089+
u'--by option can be either "name" or "time". '
1090+
u'You supplied "%s"' % orderby
1091+
))
1092+
1093+
projects = watson.projects(orderby=orderby, descending=descending)
1094+
for project in projects:
10791095
click.echo(style('project', project))
10801096

10811097

10821098
@cli.command()
1099+
@click.option('-b', '--by', 'orderby', default='name',
1100+
help="Sort the ouput by either 'name' or 'time'")
1101+
@click.option('-r', '--reverse', 'descending', default=False, is_flag=True,
1102+
help="Reverse the output (sort by descending order)")
10831103
@click.pass_obj
10841104
@catch_watson_error
1085-
def tags(watson):
1105+
def tags(watson, orderby, descending):
10861106
"""
10871107
Display the list of all the tags.
10881108
@@ -1103,7 +1123,19 @@ def tags(watson):
11031123
transmission
11041124
wheels
11051125
"""
1106-
for tag in watson.tags:
1126+
if orderby == 'name':
1127+
orderby = 'project'
1128+
elif orderby == 'time':
1129+
orderby = 'start'
1130+
else:
1131+
raise click.ClickException(style(
1132+
'error',
1133+
u'--by option can be either "name" or "time". '
1134+
u'You supplied "%s"' % orderby
1135+
))
1136+
1137+
tags = watson.tags(orderby=orderby, descending=descending)
1138+
for tag in tags:
11071139
click.echo(style('tag', tag))
11081140

11091141

@@ -1160,15 +1192,15 @@ def add(watson, args, from_, to, confirm_new_project, confirm_new_tag):
11601192
# Confirm creation of new project if that option is set
11611193
if (watson.config.getboolean('options', 'confirm_new_project') or
11621194
confirm_new_project):
1163-
confirm_project(project, watson.projects)
1195+
confirm_project(project, watson.projects())
11641196

11651197
# Parse all the tags
11661198
tags = parse_tags(args)
11671199

11681200
# Confirm creation of new tag(s) if that option is set
11691201
if (watson.config.getboolean('options', 'confirm_new_tag') or
11701202
confirm_new_tag):
1171-
confirm_tags(tags, watson.tags)
1203+
confirm_tags(tags, watson.tags())
11721204

11731205
# add a new frame, call watson save to update state files
11741206
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):
12601292
# Confirm creation of new tag(s) if that option is set
12611293
if (watson.config.getboolean('options', 'confirm_new_tag') or
12621294
confirm_new_tag):
1263-
confirm_tags(tags, watson.tags)
1295+
confirm_tags(tags, watson.tags())
12641296
start = arrow.get(data['start'], datetime_format).replace(
12651297
tzinfo=local_tz).to('utc')
12661298
stop = arrow.get(data['stop'], datetime_format).replace(

watson/watson.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- coding: utf-8 -*-
22

3+
from collections import OrderedDict
34
import datetime
45
from functools import reduce
56
import json
@@ -303,19 +304,51 @@ def cancel(self):
303304
self.current = None
304305
return old_current
305306

306-
@property
307-
def projects(self):
307+
def projects(self, orderby="project", descending=False):
308308
"""
309-
Return the list of all the existing projects, sorted by name.
309+
Return the list of all the existing projects.
310310
"""
311-
return sorted(set(self.frames['project']))
312311

313-
@property
314-
def tags(self):
312+
ordered_frames = sorted(self.frames, key=operator.attrgetter(orderby))
313+
ordered_projects = list(map(operator.attrgetter("project"),
314+
ordered_frames))
315+
316+
# Keep latest occurence only, so we feed a reversed list
317+
# to the ordered set.
318+
ordered_projects.reverse()
319+
320+
# Use OrderedDict as a set to remove duplicates by keep order.
321+
result = list(OrderedDict.fromkeys(ordered_projects))
322+
323+
if not descending:
324+
result.reverse()
325+
326+
return result
327+
328+
def tags(self, orderby="tag", descending=False):
315329
"""
316-
Return the list of the tags, sorted by name.
330+
Return the list of the tags.
317331
"""
318-
return sorted(set(tag for tags in self.frames['tags'] for tag in tags))
332+
if orderby == "tag":
333+
tags = [tag for tags in self.frames['tags'] for tag in tags]
334+
return sorted(set(tags))
335+
else:
336+
ordered_frames = sorted(self.frames,
337+
key=operator.attrgetter(orderby))
338+
ordered_tags = map(operator.attrgetter("tags"), ordered_frames)
339+
flat_tags = [tag for tags in ordered_tags for tag in tags]
340+
341+
# Keep latest occurence only, so we feed a reversed list
342+
# to the ordered set.
343+
flat_tags.reverse()
344+
345+
result = list(OrderedDict.fromkeys(flat_tags))
346+
347+
if not descending:
348+
result.reverse()
349+
350+
# Use OrderedDict as a set to remove duplicates keep order.
351+
return result
319352

320353
def _get_request_info(self, route):
321354
config = self.config
@@ -542,7 +575,7 @@ def report(self, from_, to, current=None, projects=None, tags=None,
542575

543576
def rename_project(self, old_project, new_project):
544577
"""Rename a project in all affected frames."""
545-
if old_project not in self.projects:
578+
if old_project not in self.projects():
546579
raise WatsonError(u'Project "%s" does not exist' % old_project)
547580

548581
updated_at = arrow.utcnow()
@@ -559,7 +592,7 @@ def rename_project(self, old_project, new_project):
559592

560593
def rename_tag(self, old_tag, new_tag):
561594
"""Rename a tag in all affected frames."""
562-
if old_tag not in self.tags:
595+
if old_tag not in self.tags():
563596
raise WatsonError(u'Tag "%s" does not exist' % old_tag)
564597

565598
updated_at = arrow.utcnow()

0 commit comments

Comments
 (0)