Skip to content

Commit

Permalink
Implement undo solving pimutils#505
Browse files Browse the repository at this point in the history
  • Loading branch information
r4ulill0 committed Apr 7, 2023
1 parent 1cd3cf2 commit 3ad0b76
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 0 deletions.
49 changes: 49 additions & 0 deletions tests/test_cli.py
Expand Up @@ -9,6 +9,7 @@
import click
import hypothesis.strategies as st
import pytest
import pytz
from dateutil.tz import tzlocal
from freezegun import freeze_time
from hypothesis import given
Expand Down Expand Up @@ -749,6 +750,54 @@ def test_done_recurring(runner, todo_factory, todos):
assert todo.rrule == rrule


def test_undo(runner, todo_factory, todos):
completed_at = datetime.datetime.now(tz=pytz.UTC)
percentage = 100
todo = todo_factory(completed_at=completed_at, percent_complete=percentage)

result = runner.invoke(cli, ["undo", "1"])
assert not result.exception

todo = next(todos(status="ANY"))
assert todo.percent_complete == 0
assert todo.is_completed is False
assert not todo.rrule


def test_undo_recurring(runner, default_database, todo_factory, todos):
rrule = "FREQ=DAILY;UNTIL=20990315T020000Z"
recurred = todo_factory(id=2, rrule=rrule)

completed_at = datetime.datetime.now(tz=pytz.UTC)
percentage = 100
related = [recurred]
original = todo_factory(
id=1,
rrule=None,
completed_at=completed_at,
percent_complete=percentage,
related=related,
)
assert original.related

default_database.update_cache()
result = runner.invoke(cli, ["undo", "1"])
assert not result.exception

default_database.update_cache()

todos = todos(status="ANY")
todo = next(todos)

assert todo.percent_complete == 0
assert todo.is_completed is False
assert not todo.related
assert todo.rrule == rrule

todo = next(todos)
assert not todo


def test_cancel(runner, todo_factory, todos):
todo = todo_factory()

Expand Down
24 changes: 24 additions & 0 deletions todoman/cli.py
Expand Up @@ -467,6 +467,30 @@ def done(ctx, todos):
click.echo(ctx.formatter.detailed(todo))


@cli.command()
@pass_ctx
@click.argument(
"todos",
nargs=-1,
required=True,
type=click.IntRange(0),
callback=_validate_todos,
)
# @catch_errors
def undo(ctx, todos):
"""Undo one or more tasks."""
toremove = []
for todo in todos:
removable = todo.undo()
if removable:
toremove.append(removable)
ctx.db.save(todo)
click.echo(ctx.formatter.detailed(todo))

for todo in toremove:
ctx.db.delete(todo)


@cli.command()
@pass_ctx
@click.argument(
Expand Down
21 changes: 21 additions & 0 deletions todoman/model.py
Expand Up @@ -252,6 +252,27 @@ def complete(self) -> None:
self.percent_complete = 100
self.status = "COMPLETED"

def undo(self) -> Todo | None:
"""
Immediately restores this todo. Marks it as needs action, resets the
percentage to 0 and deletes the completed_at datetime.
If this todo belongs to a series, the one created at completion will
be deleted.
Returns the todo that will be deleted.
"""
original = None
if self.is_recurring and self.related:
original = self.related.pop()
self.rrule = original.rrule

self.completed_at = None
self.percent_complete = 0
self.status = "NEEDS-ACTION"

return original

@cached_property
def path(self) -> str:
if not self.list:
Expand Down

0 comments on commit 3ad0b76

Please sign in to comment.