Skip to content

Commit

Permalink
Merge pull request #9 from AminFadaee/release/0.2.0
Browse files Browse the repository at this point in the history
Release/0.2.0
  • Loading branch information
AminFadaee authored Jun 30, 2020
2 parents f6f8a7c + 2ceb94e commit 28d7d37
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 19 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ Install using python 3.8 and pip3:
```bash
pip3 install cli-task
```

## Versions
### 0.2.0
* Added remove functionality:

```bash
~ task remove Movies John
```

## Future
I try to add sorting feature for list and exports, also I might try
to add attributes to each task (due date, urgency, ...).
Expand Down
18 changes: 17 additions & 1 deletion cli_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,23 @@ def edit(group, entry):
except LookupError:
click.secho(config.FAILED_LOOKUP.format(entry=entry, group=group), fg='red')
except ConflictError:
click.secho(config.EDIT_FAILED_CONFLICT.format(entry=entry, group=group), fg='red')
click.secho(config.CONFLICTING_ENTRIES.format(entry=entry, group=group), fg='red')


@task.command(help=config.REMOVE_HELP)
@click.argument('group', nargs=1)
@click.argument('entry', nargs=-1, required=True)
def remove(group, entry):
entry = ' '.join(word for word in entry)
manager = ClientManagerFactory.create(group)
try:
full_name = manager.get_entry_full_name(partial_name=entry)
task_name = manager.delete_entry(full_name)
click.echo(config.REMOVE_SUCCESS.format(entry=full_name, group=group, new_entry=task_name))
except LookupError:
click.secho(config.FAILED_LOOKUP.format(entry=entry, group=group), fg='red')
except ConflictError:
click.secho(config.CONFLICTING_ENTRIES.format(entry=entry, group=group), fg='red')


@task.command(name='list', help=config.LIST_HELP)
Expand Down
5 changes: 4 additions & 1 deletion cli_client/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
EDIT_HELP = 'Edit an entry in a specific group (prefix matches are valid).'
EDIT_NEW_NAME_PROMPT = 'Enter the new name for {entry}'
EDIT_SUCCESS = 'Entry {entry} in {group} changed to {new_entry}.'
EDIT_FAILED_CONFLICT = 'More than one entry match for {entry} in {group}!'

REMOVE_HELP = 'Remove an entry from a specific group (prefix matches are valid).'
REMOVE_SUCCESS = 'Entry {entry} in {group} removed.'

CONFLICTING_ENTRIES = 'More than one entry match for {entry} in {group}!'
FAILED_LOOKUP = 'Entry {entry} not found in {group}!'

FINISH_HELP = 'Finish an entry in a specific group (accepts non-existing entries).'
Expand Down
4 changes: 4 additions & 0 deletions manager/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ def get_entry_full_name(self, partial_name):
def add_entry(self, entry: str) -> str:
pass

@abstractmethod
def delete_entry(self, entry: str) -> str:
pass

@abstractmethod
def edit_entry(self, entry: str, new_entry: str) -> str:
pass
Expand Down
5 changes: 5 additions & 0 deletions manager/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ def edit_entry(self, entry: str, new_entry: str) -> str:
self.storage.put(tasks)
return new_entry

def delete_entry(self, entry: str) -> None:
tasks = self.retrieve()
tasks.delete(entry)
self.storage.put(tasks)

def finish_entry(self, entry: str) -> str:
tasks = self.retrieve()
if tasks.has(entry):
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
click==7.1.1
fpdf==1.7.2
coverage==5.1
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setup(
name='cli-task',
version='0.1',
version='0.2.0',
description='A simple cli to-do list.',
long_description=README,
long_description_content_type="text/markdown",
Expand Down
6 changes: 6 additions & 0 deletions tasks/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ def add(self, task_name: str):
self._task_names.add(task_name)
return task

def delete(self, task_name: str):
index = self._find_task_index_based_on_full_match_or_prefix_match_on_name(task_name)
full_name = self._tasks[index].name
self._task_names.remove(full_name)
del self._tasks[index]

def _find_task_index_based_on_full_match_or_prefix_match_on_name(self, task_name):
matched_indices = []
for index, task in enumerate(self._tasks):
Expand Down
80 changes: 64 additions & 16 deletions tests/test_cli_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from cli_client import client
from cli_client import config
from cli_client.client import task, add, edit, finish, list_entries, export, undo
from cli_client.client import task, add, edit, finish, list_entries, export, undo, remove
from cli_client.factory import ClientManagerFactory
from manager.abstract import TasksManager
from tasks.errors import UniqueViolationError, ConflictError
Expand All @@ -13,6 +13,12 @@


class ConcreteTasksManager(TasksManager):
def delete_entry(self, entry: str) -> str:
global storage
if self.name not in storage:
raise LookupError
storage[self.name].remove(entry)

def get_entry_full_name(self, partial_name):
return partial_name

Expand Down Expand Up @@ -158,7 +164,7 @@ def patched_edit(*args, **kwargs):
original_edit_entry = ConcreteTasksManager.edit_entry
ConcreteTasksManager.edit_entry = patched_edit
result = runner.invoke(edit, ['work', 'task'], input='another task')
msg = config.EDIT_FAILED_CONFLICT.format(entry='task', group='work')
msg = config.CONFLICTING_ENTRIES.format(entry='task', group='work')
self.assertEqual(0, result.exit_code)
self.assertTrue(msg in result.output)
self.assertEqual({}, storage)
Expand All @@ -169,41 +175,83 @@ def test_edit_outputs_correct_success_text(self):
with runner.isolated_filesystem():
result = runner.invoke(add, ['work', 'task_1'])
self.assertEqual(0, result.exit_code)
self.assertTrue(config.ADD_SUCCESS.format(group='work', entry='task_1') in result.output)
result = runner.invoke(edit, ['work', 'task_1'], input='task_2')
self.assertEqual(0, result.exit_code)
self.assertTrue(
config.EDIT_SUCCESS.format(group='work', entry='task_1', new_entry='task_2') in result.output)

def test_edit_creates_group_if_not_existing(self):
def test_edit_accepts_entry_with_spaces(self):
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(add, ['work', 'task_1'])
result = runner.invoke(add, ['work', 'this', 'is', 'an', 'entry', 'with', 'spaces'])
self.assertEqual(0, result.exit_code)
self.assertEqual('task_1', storage['work'][0])
result = runner.invoke(edit, ['work', 'this', 'is', 'an', 'entry', 'with', 'spaces'], input='new_task')
self.assertEqual(0, result.exit_code)
self.assertEqual('new_task', storage['work'])

def test_edit_accepts_entry_with_spaces(self):
def test_remove_helps_outputs_the_help(self):
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(add, ['work', 'this', 'is', 'an', 'entry', 'with', 'spaces'])
result = runner.invoke(remove, ['--help'])
self.assertEqual(0, result.exit_code)
self.assertEqual('this is an entry with spaces', storage['work'][0])
self.assertTrue(config.REMOVE_HELP in result.output)

def test_edit_correctly_adds_to_an_already_existing_group(self):
def test_remove_removes_the_task_correctly(self):
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(add, ['work', 'task 1'])
self.assertEqual(0, result.exit_code)
self.assertEqual('task 1', storage['work'][0])
result = runner.invoke(add, ['work', 'task 2'])
self.assertEqual(0, result.exit_code)
self.assertEqual('task 1', storage['work'][0])
self.assertEqual('task 2', storage['work'][1])
result = runner.invoke(remove, ['work', 'task 2'])
self.assertEqual(0, result.exit_code)
self.assertEqual('task 1', storage['work'][0])
self.assertEqual(1, len(storage['work']))

def test_edit_does_not_print_error_when_entry_already_exists(self):
def test_remove_prints_error_if_entry_does_not_exists(self):
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(add, ['work', 'task 1'])
result = runner.invoke(remove, ['work', 'task'])
msg = config.FAILED_LOOKUP.format(entry='task', group='work')
print(result.output)
self.assertEqual(0, result.exit_code)
self.assertEqual('task 1', storage['work'][0])
result = runner.invoke(add, ['work', 'task 1'])
self.assertTrue(msg in result.output)
self.assertEqual({}, storage)

def test_remove_prints_error_if_entry_matches_more_than_one_tasks(self):
runner = CliRunner()
with runner.isolated_filesystem():
def patched_remove(*args, **kwargs):
raise ConflictError

original_remove_entry = ConcreteTasksManager.delete_entry
ConcreteTasksManager.delete_entry = patched_remove
result = runner.invoke(remove, ['work', 'task'])
msg = config.CONFLICTING_ENTRIES.format(entry='task', group='work')
self.assertEqual(0, result.exit_code)
self.assertTrue(config.ADD_FAILED.format(group='work', entry='task 1') in result.output)
self.assertTrue(msg in result.output)
self.assertEqual({}, storage)
ConcreteTasksManager.delete_entry = original_remove_entry

def test_remove_outputs_correct_success_text(self):
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(add, ['work', 'task_1'])
self.assertEqual(0, result.exit_code)
result = runner.invoke(remove, ['work', 'task_1'])
self.assertEqual(0, result.exit_code)
self.assertTrue(config.REMOVE_SUCCESS.format(group='work', entry='task_1') in result.output)

def test_remove_accepts_entry_with_spaces(self):
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(add, ['work', 'this', 'is', 'an', 'entry', 'with', 'spaces'])
self.assertEqual(0, result.exit_code)
result = runner.invoke(remove, ['work', 'this', 'is', 'an', 'entry', 'with', 'spaces'])
self.assertEqual(0, result.exit_code)
self.assertEqual(storage['work'], [])

def test_list_helps_outputs_the_help(self):
runner = CliRunner()
Expand Down
31 changes: 31 additions & 0 deletions tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,34 @@ def test_has_looks_up_correctly(self):
self.assertFalse(tasks.has('tasks 3'))
self.assertTrue(tasks.has('tasks 2'))
self.assertTrue(tasks.has('tasks 1'))

def test_delete_correctly_deletes_a_task(self):
tasks = SimpleTasks('work')
tasks.add('tasks 1')
tasks.add('tasks 2')
tasks.add('tasks 3')
self.assertTrue(tasks.has('tasks 1'))
self.assertTrue(tasks.has('tasks 2'))
self.assertTrue(tasks.has('tasks 3'))
tasks.delete('tasks 2')
self.assertTrue(tasks.has('tasks 1'))
self.assertTrue(not tasks.has('tasks 2'))
self.assertTrue(tasks.has('tasks 3'))

def test_delete_correctly_deletes_a_task_with_prefix_match(self):
tasks = SimpleTasks('work')
tasks.add('first task')
tasks.add('second task')
tasks.add('third task')
self.assertTrue(tasks.has('first task'))
self.assertTrue(tasks.has('second task'))
self.assertTrue(tasks.has('third task'))
tasks.delete('sec')
self.assertTrue(tasks.has('first task'))
self.assertTrue(not tasks.has('second task'))
self.assertTrue(tasks.has('third task'))

def test_delete_raises_lookup_error_when_task_not_present(self):
tasks = SimpleTasks('work')
tasks.add('tasks 1')
self.assertRaises(LookupError, tasks.delete, 'tasks 2')
11 changes: 11 additions & 0 deletions tests/test_tasks_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ def test_manager_get_full_name_get_the_complete_name_of_the_entry_correctly(self
tasks_manager.add_entry('three job')
self.assertEqual('one job', tasks_manager.get_entry_full_name('one'))

def test_manager_delete_entry_deletes_the_entry_correctly(self):
tasks_manager = SimpleTasksManager('work', self.storage)
jobs = ['one job', 'two job', 'three job']
for job in jobs:
tasks_manager.add_entry(job)
saved_jobs = [file['./work.foo']['tasks'][i]['name'] for i in range(len(file['./work.foo']['tasks']))]
self.assertEqual(saved_jobs, jobs)
tasks_manager.delete_entry('two job')
saved_jobs = [file['./work.foo']['tasks'][i]['name'] for i in range(len(file['./work.foo']['tasks']))]
self.assertEqual(['one job', 'three job'], saved_jobs)

def tearDown(self) -> None:
global file
file = {}

0 comments on commit 28d7d37

Please sign in to comment.