Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timespan selection helper options for the report and log commands #130

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/user-guide/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ By default, the sessions from the last 7 days are printed. This timespan
can be controlled with the `--from` and `--to` arguments. The dates
must have the format `YEAR-MONTH-DAY`, like: `2014-05-19`.

You can also use special shortcut options for easier timespan control:
`--day` sets the log timespan to the current day (beginning at 00:00h)
and `--year`, `--month` and `--week` to the current year, month or week
respectively.

You can limit the log to a project or a tag using the `--project` and
`--tag` options. They can be specified several times each to add multiple
projects or tags to the log.
Expand Down Expand Up @@ -159,6 +164,10 @@ Flag | Help
-----|-----
`-f, --from DATE` | The date from when the log should start. Defaults to seven days ago.
`-t, --to DATE` | The date at which the log should stop (inclusive). Defaults to tomorrow.
`-y, --year` | Reports activity for the current year.
`-m, --month` | Reports activity for the current month.
`-w, --week` | Reports activity for the current week.
`-d, --day` | Reports activity for the current day.
`-p, --project TEXT` | Logs activity only for the given project. You can add other projects by using this option several times.
`-T, --tag TEXT` | Logs activity only for frames containing the given tag. You can add several tags by using this option multiple times
`--help` | Show this message and exit.
Expand Down Expand Up @@ -308,6 +317,11 @@ By default, the time spent the last 7 days is printed. This timespan
can be controlled with the `--from` and `--to` arguments. The dates
must have the format `YEAR-MONTH-DAY`, like: `2014-05-19`.

You can also use special shortcut options for easier timespan control:
`--day` sets the report timespan to the current day (beginning at 00:00h)
and `--year`, `--month` and `--week` to the current year, month or week
respectively.

You can limit the report to a project or a tag using the `--project` and
`--tag` options. They can be specified several times each to add multiple
projects or tags to the report.
Expand Down Expand Up @@ -360,6 +374,10 @@ Flag | Help
-----|-----
`-f, --from DATE` | The date from when the report should start. Defaults to seven days ago.
`-t, --to DATE` | The date at which the report should stop (inclusive). Defaults to tomorrow.
`-y, --year` | Reports activity for the current year.
`-m, --month` | Reports activity for the current month.
`-w, --week` | Reports activity for the current week.
`-d, --day` | Reports activity for the current day.
`-p, --project TEXT` | Reports activity only for the given project. You can add other projects by using this option several times.
`-T, --tag TEXT` | Reports activity only for frames containing the given tag. You can add several tags by using this option multiple times
`--help` | Show this message and exit.
Expand Down
53 changes: 53 additions & 0 deletions tests/test_watson.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import sys
import json
import os
import datetime

try:
from unittest import mock
Expand All @@ -17,9 +18,12 @@
import requests
import arrow

from dateutil.tz.tz import tzutc

from click import get_app_dir
from watson import Watson, WatsonError
from watson.watson import ConfigurationError, ConfigParser
from watson.utils import get_start_time_for_period

TEST_FIXTURE_DIR = py.path.local(
os.path.dirname(
Expand All @@ -35,6 +39,33 @@
builtins = '__builtin__'


def mock_datetime(dt, dt_module):

class DateTimeMeta(type):

@classmethod
def __instancecheck__(mcs, obj):
return isinstance(obj, datetime.datetime)

class BaseMockedDateTime(datetime.datetime):

@classmethod
def now(cls, tz=None):
return dt.replace(tzinfo=tz)

@classmethod
def utcnow(cls):
return dt

@classmethod
def today(cls):
return dt

MockedDateTime = DateTimeMeta('datetime', (BaseMockedDateTime,), {})

return mock.patch.object(dt_module, 'datetime', MockedDateTime)


@pytest.fixture
def config_dir(tmpdir):
return str(tmpdir.mkdir('config'))
Expand Down Expand Up @@ -828,3 +859,25 @@ def test_merge_report(watson, datafiles):

assert conflicting[0].id == '2'
assert merging[0].id == '3'


# report/log

_dt = datetime.datetime
_tz = {'tzinfo': tzutc()}


@pytest.mark.parametrize('now, mode, start_time', [
(_dt(2016, 6, 2, **_tz), 'year', _dt(2016, 1, 1, **_tz)),
(_dt(2016, 6, 2, **_tz), 'month', _dt(2016, 6, 1, **_tz)),
(_dt(2016, 6, 2, **_tz), 'week', _dt(2016, 5, 30, **_tz)),
(_dt(2016, 6, 2, **_tz), 'day', _dt(2016, 6, 2, **_tz)),

(_dt(2012, 2, 24, **_tz), 'year', _dt(2012, 1, 1, **_tz)),
(_dt(2012, 2, 24, **_tz), 'month', _dt(2012, 2, 1, **_tz)),
(_dt(2012, 2, 24, **_tz), 'week', _dt(2012, 2, 20, **_tz)),
(_dt(2012, 2, 24, **_tz), 'day', _dt(2012, 2, 24, **_tz)),
])
def test_get_start_time_for_period(now, mode, start_time):
with mock_datetime(now, datetime):
assert get_start_time_for_period(mode).datetime == start_time
86 changes: 81 additions & 5 deletions watson/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,27 @@
from . import watson
from .frames import Frame
from .utils import (format_timedelta, get_frame_from_argument, options,
sorted_groupby, style)
sorted_groupby, style, get_start_time_for_period)


class MutuallyExclusiveOption(click.Option):
def __init__(self, *args, **kwargs):
self.mutually_exclusive = set(kwargs.pop('mutually_exclusive', []))
super(MutuallyExclusiveOption, self).__init__(*args, **kwargs)

def handle_parse_result(self, ctx, opts, args):
if self.mutually_exclusive.intersection(opts) and self.name in opts:
raise click.UsageError(
'`--{name}` is mutually exclusive with the following options: '
'{options}'.format(name=self.name.replace('_', ''),
options=', '
.join(['`--{}`'.format(_) for _ in
self.mutually_exclusive]))
)

return super(MutuallyExclusiveOption, self).handle_parse_result(
ctx, opts, args
)


class WatsonCliError(click.ClickException):
Expand Down Expand Up @@ -265,14 +285,36 @@ def status(watson):
))


_SHORTCUT_OPTIONS = ['year', 'month', 'week', 'day']


@cli.command()
@click.option('-f', '--from', 'from_', type=Date,
@click.option('-f', '--from', 'from_', cls=MutuallyExclusiveOption, type=Date,
default=arrow.now().replace(days=-7),
mutually_exclusive=_SHORTCUT_OPTIONS,
help="The date from when the report should start. Defaults "
"to seven days ago.")
@click.option('-t', '--to', type=Date, default=arrow.now(),
@click.option('-t', '--to', cls=MutuallyExclusiveOption, type=Date,
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,
flag_value=get_start_time_for_period('year'),
mutually_exclusive=['day', 'week', 'month'],
help='Reports activity for the current year.')
@click.option('-m', '--month', cls=MutuallyExclusiveOption, type=Date,
flag_value=get_start_time_for_period('month'),
mutually_exclusive=['day', 'week', 'year'],
help='Reports activity for the current month.')
@click.option('-w', '--week', cls=MutuallyExclusiveOption, type=Date,
flag_value=get_start_time_for_period('week'),
mutually_exclusive=['day', 'month', 'year'],
help='Reports activity for the current week.')
@click.option('-d', '--day', cls=MutuallyExclusiveOption, type=Date,
flag_value=get_start_time_for_period('day'),
mutually_exclusive=['week', 'month', 'year'],
help='Reports activity for the current day.')
@click.option('-p', '--project', 'projects', multiple=True,
help="Reports activity only for the given project. You can add "
"other projects by using this option several times.")
Expand All @@ -281,7 +323,7 @@ def status(watson):
"tag. You can add several tags by using this option multiple "
"times")
@click.pass_obj
def report(watson, from_, to, projects, tags):
def report(watson, from_, to, projects, tags, year, month, week, day):
"""
Display a report of the time spent on each project.

Expand All @@ -293,6 +335,11 @@ def report(watson, from_, to, projects, tags):
can be controlled with the `--from` and `--to` arguments. The dates
must have the format `YEAR-MONTH-DAY`, like: `2014-05-19`.

You can also use special shortcut options for easier timespan control:
`--day` sets the report timespan to the current day (beginning at 00:00h)
and `--year`, `--month` and `--week` to the current year, month or week
respectively.

You can limit the report to a project or a tag using the `--project` and
`--tag` options. They can be specified several times each to add multiple
projects or tags to the report.
Expand Down Expand Up @@ -339,6 +386,10 @@ def report(watson, from_, to, projects, tags):
[steering 10h 33m 37s]
[wheels 10h 11m 35s]
"""
for start_time in (_ for _ in [day, week, month, year]
if _ is not None):
from_ = start_time

if from_ > to:
raise click.ClickException("'from' must be anterior to 'to'")

Expand Down Expand Up @@ -407,6 +458,22 @@ def report(watson, from_, to, projects, tags):
@click.option('-t', '--to', type=Date, default=arrow.now(),
help="The date at which the log should stop (inclusive). "
"Defaults to tomorrow.")
@click.option('-y', '--year', cls=MutuallyExclusiveOption, type=Date,
flag_value=get_start_time_for_period('year'),
mutually_exclusive=['day', 'week', 'month'],
help='Reports activity for the current year.')
@click.option('-m', '--month', cls=MutuallyExclusiveOption, type=Date,
flag_value=get_start_time_for_period('month'),
mutually_exclusive=['day', 'week', 'year'],
help='Reports activity for the current month.')
@click.option('-w', '--week', cls=MutuallyExclusiveOption, type=Date,
flag_value=get_start_time_for_period('week'),
mutually_exclusive=['day', 'month', 'year'],
help='Reports activity for the current week.')
@click.option('-d', '--day', cls=MutuallyExclusiveOption, type=Date,
flag_value=get_start_time_for_period('day'),
mutually_exclusive=['week', 'month', 'year'],
help='Reports activity for the current day.')
@click.option('-p', '--project', 'projects', multiple=True,
help="Logs activity only for the given project. You can add "
"other projects by using this option several times.")
Expand All @@ -415,14 +482,19 @@ def report(watson, from_, to, projects, tags):
"tag. You can add several tags by using this option multiple "
"times")
@click.pass_obj
def log(watson, from_, to, projects, tags):
def log(watson, from_, to, projects, tags, year, month, week, day):
"""
Display each recorded session during the given timespan.

By default, the sessions from the last 7 days are printed. This timespan
can be controlled with the `--from` and `--to` arguments. The dates
must have the format `YEAR-MONTH-DAY`, like: `2014-05-19`.

You can also use special shortcut options for easier timespan control:
`--day` sets the log timespan to the current day (beginning at 00:00h)
and `--year`, `--month` and `--week` to the current year, month or week
respectively.

You can limit the log to a project or a tag using the `--project` and
`--tag` options. They can be specified several times each to add multiple
projects or tags to the log.
Expand Down Expand Up @@ -456,6 +528,10 @@ def log(watson, from_, to, projects, tags):
02cb269 09:53 to 12:43 2h 50m 07s apollo11 [wheels]
1070ddb 13:48 to 16:17 2h 29m 11s voyager1 [antenna, sensors]
""" # noqa
for start_time in (_ for _ in [day, week, month, year]
if _ is not None):
from_ = start_time

if from_ > to:
raise click.ClickException("'from' must be anterior to 'to'")

Expand Down
27 changes: 27 additions & 0 deletions watson/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import itertools
import datetime

import click
import arrow

from click.exceptions import UsageError

Expand Down Expand Up @@ -111,3 +113,28 @@ def get_frame_from_argument(watson, arg):
style('error', "No frame found with id"),
style('short_id', arg))
)


def get_start_time_for_period(period):
# Using now() from datetime instead of arrow for mocking compatibility.
now = arrow.Arrow.fromdatetime(datetime.datetime.now())
date = now.date()

day = date.day
month = date.month
year = date.year

weekday = now.weekday()

if period == 'day':
start_time = arrow.Arrow(year, month, day)
elif period == 'week':
start_time = arrow.Arrow.fromdate(now.replace(days=-weekday).date())
elif period == 'month':
start_time = arrow.Arrow(year, month, 1)
elif period == 'year':
start_time = arrow.Arrow(year, 1, 1)
else:
raise ValueError('Unsupported period value: {}'.format(period))

return start_time