Skip to content

Commit

Permalink
Unifies all command datetime inputs
Browse files Browse the repository at this point in the history
Fixes #292

- Rename DateParamType to DateTimeParamType because it now supports
times in the following formats: 'HH:mm' and 'HH:mm:ss'.
- Remove TimeParamType by supporting its formats in DateTimeParamType.
- Add the new valid formats to tests (previously invalid).
- Tests for the 'stop' command now support the same dates/times than the
other commands.
  • Loading branch information
davidag committed Oct 16, 2019
1 parent 448561f commit 5517841
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 63 deletions.
27 changes: 20 additions & 7 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ class TestCliCmd:
('2018-04-10T12:30', '2018-04-10 12:30:00'),
('2018-04-10 12', '2018-04-10 12:00:00'),
('2018-04-10T12', '2018-04-10 12:00:00'),
(
'14:05:12',
arrow.now()
.replace(hour=14, minute=5, second=12)
.format('YYYY-MM-DD HH:mm:ss')
),
(
'14:05',
arrow.now()
.replace(hour=14, minute=5, second=0)
.format('YYYY-MM-DD HH:mm:ss')
),
]

invalid_dates_data = [
Expand All @@ -54,9 +66,7 @@ class TestCliCmd:
('tomorrow'),
('14:05:12.000'), # Times alone are not allowed
('140512.000'),
('14:05:12'),
('140512'),
('14:05'),
('14.05'),
('2018-04-10T'),
('2018-04-10T12:30:43.'),
Expand Down Expand Up @@ -168,16 +178,19 @@ def test_invalid_date(self, watson, test_dt):
class TestCliStopCmd(TestCliCmd):

valid_times_data = [
('14:12:43', '2019-04-10 14:12:43'),
('14:12'),
('14:12:43'),
('2019-04-10T14:12'),
('2019-04-10T14:12:43'),
]
start_dt = datetime(2019, 4, 10, 14, tzinfo=tzlocal())
start_dt = datetime(2019, 4, 10, 14, 0, 0, tzinfo=tzlocal())

@pytest.mark.parametrize('test_dt,expected', valid_times_data)
def test_valid_time(self, mocker, watson, test_dt, expected):
@pytest.mark.parametrize('at_dt', valid_times_data)
def test_valid_time(self, mocker, watson, at_dt):
mocker.patch('arrow.arrow.datetime', wraps=datetime)
result = self._run_start(watson)
assert result.exit_code == 0
result = self._run_stop(watson, test_dt)
result = self._run_stop(watson, at_dt)
assert result.exit_code == 0

def _run_start(self, watson):
Expand Down
105 changes: 49 additions & 56 deletions watson/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import json
import operator
import os
import re

from dateutil import tz
from functools import reduce, wraps
Expand Down Expand Up @@ -61,15 +60,17 @@ def _raise_exclusive_error(self):
['`--{}`'.format(_) for _ in self.mutually_exclusive]))))


class DateParamType(click.ParamType):
name = 'date'
class DateTimeParamType(click.ParamType):
name = 'datetime'

def convert(self, value, param, ctx):
if value:
try:
date = arrow.get(value)
except (ValueError, TypeError) as e:
raise click.UsageError(str(e))
date = self._parse_multiformat(value)
if date is None:
raise click.UsageError(
"Could not match value '{}' to any supported date format"
.format(value)
)
# When we parse a date, we want to parse it in the timezone
# expected by the user, so that midnight is midnight in the local
# timezone, not in UTC. Cf issue #16.
Expand All @@ -83,34 +84,26 @@ def convert(self, value, param, ctx):
start_time=date, week_start=week_start)
return date


class TimeParamType(click.ParamType):
name = 'time'

def convert(self, value, param, ctx):
if isinstance(value, arrow.Arrow):
return value

date_pattern = r'\d{4}-\d\d-\d\d'
time_pattern = r'\d\d:\d\d(:\d\d)?'

if re.match('^{time_pat}$'.format(time_pat=time_pattern), value):
cur_date = arrow.now().date().isoformat()
cur_time = '{date}T{time}'.format(date=cur_date, time=value)
elif re.match('^{date_pat}T{time_pat}'.format(
date_pat=date_pattern, time_pat=time_pattern), value):
cur_time = value
else:
errmsg = ('Could not parse time.'
'Please specify in (YYYY-MM-DDT)?HH:MM(:SS)? format.')
raise click.ClickException(style('error', errmsg))

local_tz = tz.tzlocal()
return arrow.get(cur_time).replace(tzinfo=local_tz)
def _parse_multiformat(self, value):
date = None
for fmt in (None, 'HH:mm:ss', 'HH:mm'):
try:
if fmt is None:
date = arrow.get(value)
else:
date = arrow.get(value, fmt)
date = arrow.now().replace(
hour=date.hour,
minute=date.minute,
second=date.second
)
break
except (ValueError, TypeError):
pass
return date


Date = DateParamType()
Time = TimeParamType()
DateTime = DateTimeParamType()


def catch_watson_error(func):
Expand Down Expand Up @@ -240,7 +233,7 @@ def start(ctx, watson, confirm_new_project, confirm_new_tag, args, gap_=True):


@cli.command(context_settings={'ignore_unknown_options': True})
@click.option('--at', 'at_', type=Time, default=None,
@click.option('--at', 'at_', type=DateTime, default=None,
help=('Stop frame at this time. Must be in '
'(YYYY-MM-DDT)?HH:MM(:SS)? format.'))
@click.pass_obj
Expand Down Expand Up @@ -418,37 +411,37 @@ def status(watson, project, tags, elapsed):
@cli.command()
@click.option('-c/-C', '--current/--no-current', 'current', default=None,
help="(Don't) include currently running frame in report.")
@click.option('-f', '--from', 'from_', cls=MutuallyExclusiveOption, type=Date,
default=arrow.now().shift(days=-7),
@click.option('-f', '--from', 'from_', cls=MutuallyExclusiveOption,
type=DateTime, default=arrow.now().shift(days=-7),
mutually_exclusive=_SHORTCUT_OPTIONS,
help="The date from when the report should start. Defaults "
"to seven days ago.")
@click.option('-t', '--to', cls=MutuallyExclusiveOption, type=Date,
@click.option('-t', '--to', cls=MutuallyExclusiveOption, type=DateTime,
default=arrow.now(),
mutually_exclusive=_SHORTCUT_OPTIONS,
help="The date at which the report should stop (inclusive). "
"Defaults to tomorrow.")
@click.option('-y', '--year', cls=MutuallyExclusiveOption, type=Date,
@click.option('-y', '--year', cls=MutuallyExclusiveOption, type=DateTime,
flag_value=_SHORTCUT_OPTIONS_VALUES['year'],
mutually_exclusive=['day', 'week', 'luna', 'month', 'all'],
help='Reports activity for the current year.')
@click.option('-m', '--month', cls=MutuallyExclusiveOption, type=Date,
@click.option('-m', '--month', cls=MutuallyExclusiveOption, type=DateTime,
flag_value=_SHORTCUT_OPTIONS_VALUES['month'],
mutually_exclusive=['day', 'week', 'luna', 'year', 'all'],
help='Reports activity for the current month.')
@click.option('-l', '--luna', cls=MutuallyExclusiveOption, type=Date,
@click.option('-l', '--luna', cls=MutuallyExclusiveOption, type=DateTime,
flag_value=_SHORTCUT_OPTIONS_VALUES['luna'],
mutually_exclusive=['day', 'week', 'month', 'year', 'all'],
help='Reports activity for the current moon cycle.')
@click.option('-w', '--week', cls=MutuallyExclusiveOption, type=Date,
@click.option('-w', '--week', cls=MutuallyExclusiveOption, type=DateTime,
flag_value=_SHORTCUT_OPTIONS_VALUES['week'],
mutually_exclusive=['day', 'month', 'luna', 'year', 'all'],
help='Reports activity for the current week.')
@click.option('-d', '--day', cls=MutuallyExclusiveOption, type=Date,
@click.option('-d', '--day', cls=MutuallyExclusiveOption, type=DateTime,
flag_value=_SHORTCUT_OPTIONS_VALUES['day'],
mutually_exclusive=['week', 'month', 'luna', 'year', 'all'],
help='Reports activity for the current day.')
@click.option('-a', '--all', cls=MutuallyExclusiveOption, type=Date,
@click.option('-a', '--all', cls=MutuallyExclusiveOption, type=DateTime,
flag_value=_SHORTCUT_OPTIONS_VALUES['all'],
mutually_exclusive=['day', 'week', 'month', 'luna', 'year'],
help='Reports all activities.')
Expand Down Expand Up @@ -705,12 +698,12 @@ def _final_print(lines):
@cli.command()
@click.option('-c/-C', '--current/--no-current', 'current', default=None,
help="(Don't) include currently running frame in report.")
@click.option('-f', '--from', 'from_', cls=MutuallyExclusiveOption, type=Date,
default=arrow.now().shift(days=-7),
@click.option('-f', '--from', 'from_', cls=MutuallyExclusiveOption,
type=DateTime, default=arrow.now().shift(days=-7),
mutually_exclusive=_SHORTCUT_OPTIONS,
help="The date from when the report should start. Defaults "
"to seven days ago.")
@click.option('-t', '--to', cls=MutuallyExclusiveOption, type=Date,
@click.option('-t', '--to', cls=MutuallyExclusiveOption, type=DateTime,
default=arrow.now(),
mutually_exclusive=_SHORTCUT_OPTIONS,
help="The date at which the report should stop (inclusive). "
Expand Down Expand Up @@ -847,34 +840,34 @@ def aggregate(ctx, watson, current, from_, to, projects, tags, output_format,
@cli.command()
@click.option('-c/-C', '--current/--no-current', 'current', default=None,
help="(Don't) include currently running frame in output.")
@click.option('-f', '--from', 'from_', type=Date,
@click.option('-f', '--from', 'from_', type=DateTime,
default=arrow.now().shift(days=-7),
help="The date from when the log should start. Defaults "
"to seven days ago.")
@click.option('-t', '--to', type=Date, default=arrow.now(),
@click.option('-t', '--to', type=DateTime, default=arrow.now(),
help="The date at which the log should stop (inclusive). "
"Defaults to tomorrow.")
@click.option('-y', '--year', cls=MutuallyExclusiveOption, type=Date,
@click.option('-y', '--year', cls=MutuallyExclusiveOption, type=DateTime,
flag_value=_SHORTCUT_OPTIONS_VALUES['year'],
mutually_exclusive=['day', 'week', 'month', 'all'],
help='Reports activity for the current year.')
@click.option('-m', '--month', cls=MutuallyExclusiveOption, type=Date,
@click.option('-m', '--month', cls=MutuallyExclusiveOption, type=DateTime,
flag_value=_SHORTCUT_OPTIONS_VALUES['month'],
mutually_exclusive=['day', 'week', 'year', 'all'],
help='Reports activity for the current month.')
@click.option('-l', '--luna', cls=MutuallyExclusiveOption, type=Date,
@click.option('-l', '--luna', cls=MutuallyExclusiveOption, type=DateTime,
flag_value=_SHORTCUT_OPTIONS_VALUES['luna'],
mutually_exclusive=['day', 'week', 'month', 'year', 'all'],
help='Reports activity for the current moon cycle.')
@click.option('-w', '--week', cls=MutuallyExclusiveOption, type=Date,
@click.option('-w', '--week', cls=MutuallyExclusiveOption, type=DateTime,
flag_value=_SHORTCUT_OPTIONS_VALUES['week'],
mutually_exclusive=['day', 'month', 'year', 'all'],
help='Reports activity for the current week.')
@click.option('-d', '--day', cls=MutuallyExclusiveOption, type=Date,
@click.option('-d', '--day', cls=MutuallyExclusiveOption, type=DateTime,
flag_value=_SHORTCUT_OPTIONS_VALUES['day'],
mutually_exclusive=['week', 'month', 'year', 'all'],
help='Reports activity for the current day.')
@click.option('-a', '--all', cls=MutuallyExclusiveOption, type=Date,
@click.option('-a', '--all', cls=MutuallyExclusiveOption, type=DateTime,
flag_value=_SHORTCUT_OPTIONS_VALUES['all'],
mutually_exclusive=['day', 'week', 'month', 'year'],
help='Reports all activities.')
Expand Down Expand Up @@ -1122,9 +1115,9 @@ def frames(watson):

@cli.command(context_settings={'ignore_unknown_options': True})
@click.argument('args', nargs=-1)
@click.option('-f', '--from', 'from_', required=True, type=Date,
@click.option('-f', '--from', 'from_', required=True, type=DateTime,
help="Date and time of start of tracked activity")
@click.option('-t', '--to', required=True, type=Date,
@click.option('-t', '--to', required=True, type=DateTime,
help="Date and time of end of tracked activity")
@click.option('-c', '--confirm-new-project', is_flag=True, default=False,
help="Confirm addition of new project.")
Expand Down

0 comments on commit 5517841

Please sign in to comment.