Skip to content

Commit 8ec524d

Browse files
authored
Merge pull request #259 from wasi0013/v0.0.16
V0.0.16
2 parents 14e36db + 8971231 commit 8ec524d

16 files changed

+494
-59
lines changed

HISTORY.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,14 @@ History
109109
* Simplified task start command.
110110
* New tasks can be started without task name.
111111
* Task started without a name will have a default name which can be renamed later at ease.
112-
* Bug fix
112+
* Bug fix
113+
114+
0.0.16 (2023-11-11)
115+
-------------------
116+
117+
* added new command summary.
118+
* fixed bug and refactored invoice handler.
119+
* added tests for project handler.
120+
* added tests for task handler.
121+
* added tests for invoice handler.
122+
* updated make command to update README coverage percentage while testing.

Makefile

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ clean-pyc:
2727
lint:
2828
flake8 PyTM test
2929

30-
test:
31-
py.test
32-
33-
test-all:
34-
tox
30+
covtest:
31+
py.test --cov-report json --cov
32+
33+
test: covtest
34+
sed -i "s/coverage-[0-9]\+%25/coverage-$(shell jq -r .totals.percent_covered_display coverage.json)%25/g" README.rst
35+
rm -rf coverage.json
3536

3637
coverage:
3738
coverage run --source PyTM setup.py test

PyTM/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__author__ = "Wasi"
22
__email__ = "wasi0013@gmail.com"
3-
__version__ = "0.0.15"
3+
__version__ = "0.0.16"

PyTM/cli.py

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
import click
99
from rich.prompt import Confirm, Prompt
1010
from rich.table import Table
11+
from rich.tree import Tree
12+
from rich.panel import Panel
13+
from rich.layout import Layout
1114

1215
from PyTM import __version__, settings
13-
from PyTM.commands.project import project
16+
from PyTM.commands.project import project, get_duration_str
1417
from PyTM.commands.task import task
1518
from PyTM.console import console
1619
from PyTM.core import data_handler, invoice_handler
@@ -25,7 +28,7 @@ def init_data_store(show_messages=False):
2528
try:
2629
os.makedirs(settings.data_folder)
2730
messages.append(f"Created data folder: {settings.data_folder}")
28-
except:
31+
except Exception as _:
2932
messages.append(f"Data folder already exists: {settings.data_folder}")
3033
if not os.path.exists(settings.data_filepath):
3134
data_handler.init_data(settings.data_filepath)
@@ -59,7 +62,7 @@ def print_version(ctx, param, value):
5962
return
6063
console.print("\n[bold green]✨ PyTM ✨")
6164
console.print(f"version {__version__}")
62-
console.print(f"docs: https://pytm.rtfd.org")
65+
console.print("docs: https://pytm.rtfd.org")
6366
ctx.exit()
6467

6568

@@ -184,7 +187,7 @@ def config_invoice():
184187
invoice["logo"], os.path.join(settings.data_folder, "invoice-logo.png")
185188
)
186189
invoice["logo"] = os.path.join(settings.data_folder, "invoice-logo.png")
187-
except Exception as e:
190+
except Exception as _:
188191
console.print("[bold red] Error occured while saving the logo.")
189192
console.print_exception()
190193

@@ -274,7 +277,7 @@ def auto(project_name):
274277
"invoice_number"
275278
] = f'{int(state.get("config").get("invoice").get("invoice_number", "13")) + 1}'
276279
data_handler.save_data(state, settings.state_filepath)
277-
except:
280+
except Exception as _:
278281
pass
279282

280283
invoice_texts["title"] = Prompt.ask(
@@ -329,7 +332,7 @@ def auto(project_name):
329332
)
330333
try:
331334
os.makedirs(os.path.join(settings.data_folder, "invoices"))
332-
except:
335+
except Exception as _:
333336
pass
334337

335338
html_file = os.path.join(
@@ -365,7 +368,7 @@ def manual():
365368
"invoice_number"
366369
] = f'{int(state.get("config").get("invoice").get("invoice_number")) + 1}'
367370
data_handler.save_data(state, settings.state_filepath)
368-
except:
371+
except Exception as _:
369372
pass
370373

371374
invoice_texts["title"] = Prompt.ask(
@@ -420,7 +423,7 @@ def manual():
420423
)
421424
try:
422425
os.makedirs(os.path.join(settings.data_folder, "invoices"))
423-
except:
426+
except Exception as _:
424427
pass
425428
html_file = os.path.join(
426429
settings.data_folder, "invoices", f"{invoice_texts['title']}.html"
@@ -431,12 +434,57 @@ def manual():
431434
webbrowser.open(f"file:///{html_file}", autoraise=True)
432435

433436

437+
@click.command()
438+
def summary():
439+
"""
440+
- shows summary of all projects.
441+
"""
442+
data = data_handler.load_data()
443+
layout = Layout()
444+
445+
count = 0
446+
left, right = [], []
447+
for project_name in data:
448+
project_data = data.get(project_name, {}).get("tasks", {})
449+
tree = Tree(
450+
f'[bold blue]{project_name}[/bold blue] ([i]{data.get(project_name, {})["status"]}[/i])'
451+
)
452+
if project_data == {}:
453+
tree.add("[red] No tasks yet. [/red]")
454+
right.append(Panel(tree, title=f"{project_name}"))
455+
continue
456+
duration = 0
457+
for task_name, t in project_data.items():
458+
task_duration = int(round(t["duration"]))
459+
duration += task_duration
460+
tree.add(
461+
f"[green]{task_name}[/green]: {get_duration_str(task_duration)} ([i]{t['status']}[/i])"
462+
)
463+
left.append(
464+
Panel(
465+
tree,
466+
title=f"{project_name}",
467+
subtitle=f"[blue bold]Total time[/blue bold]: {get_duration_str(duration)}",
468+
expand=False,
469+
)
470+
)
471+
count += 1
472+
layout.split_row( # *p)
473+
Layout(name="left", size=45),
474+
Layout(name="right", size=55),
475+
)
476+
layout["left"].split_column(*left)
477+
layout["right"].split_column(*right)
478+
console.print(layout)
479+
480+
434481
cli.add_command(init)
435482
cli.add_command(project)
436483
cli.add_command(task)
437484
cli.add_command(show)
438485
cli.add_command(config)
439486
cli.add_command(invoice)
487+
cli.add_command(summary)
440488

441489
if __name__ == "__main__":
442490
cli()

PyTM/commands/project.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from PyTM.core import project_handler
1212

1313

14-
def _get_duration_str(sum_of_durations):
14+
def get_duration_str(sum_of_durations):
1515
m, s = divmod(sum_of_durations, 60)
1616
duration = ""
1717
if m > 60:
@@ -168,18 +168,18 @@ def summary(project_name):
168168
"""
169169
- shows total time of the project with task and duration.
170170
"""
171-
project = project_handler.summary(data_handler.load_data(), project_name)
172-
project_data = project["tasks"]
171+
project_data = project_handler.summary(data_handler.load_data(), project_name)
172+
project_data = project_data.get("tasks", {})
173173
tree = Tree(f'[bold blue]{project_name}[/bold blue] ([i]{project["status"]}[/i])')
174174
duration = 0
175175
for task, t in project_data.items():
176176
task_duration = int(round(t["duration"]))
177177
duration += task_duration
178178
tree.add(
179-
f"[green]{task}[/green]: {_get_duration_str(task_duration)} ([i]{t['status']}[/i])"
179+
f"[green]{task}[/green]: {get_duration_str(task_duration)} ([i]{t['status']}[/i])"
180180
)
181181
console.print(Panel.fit(tree))
182-
console.print(f"[blue bold]Total time[/blue bold]: {_get_duration_str(duration)}")
182+
console.print(f"[blue bold]Total time[/blue bold]: {get_duration_str(duration)}")
183183

184184

185185
@project.command()

PyTM/core/data_handler.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import json
2-
from PyTM.settings import data_filepath
32

3+
from PyTM import settings
44

5-
def init_data(path=data_filepath, data={}):
5+
6+
def init_data(path=settings.data_filepath, data={}):
67
"""
78
Creates the data to the given path.
89
"""
910
with open(path, "w") as f:
1011
json.dump(data, f)
1112

1213

13-
def load_data(path=data_filepath):
14+
def load_data(path=settings.data_filepath):
1415
"""
1516
Loads the data from the given path.
1617
"""
@@ -21,7 +22,7 @@ def load_data(path=data_filepath):
2122
return {}
2223

2324

24-
def save_data(data, path=data_filepath):
25+
def save_data(data, path=settings.data_filepath):
2526
"""
2627
Saves the data to the given path.
2728
"""
@@ -30,7 +31,7 @@ def save_data(data, path=data_filepath):
3031
json.dump(data, f)
3132

3233

33-
def update(func, path=data_filepath):
34+
def update(func, path=settings.data_filepath):
3435
"""
3536
Decorator for updating the data.
3637
"""

PyTM/core/invoice_handler.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from PyTM import settings
2-
from PyTM.core import data_handler
3-
from PyTM.commands.project import _get_duration_str
41
import datetime
52

3+
from PyTM import settings
4+
from PyTM.commands.project import get_duration_str
5+
66

77
def generate(invoice_number, invoice_texts, user, project, discount=0):
88
title, logo, foot_note = (
@@ -60,7 +60,7 @@ def generate(invoice_number, invoice_texts, user, project, discount=0):
6060
<div class="text-left mb-4">
6161
<h1 class="text-2xl font-semibold">Project: {project['meta']['title']}</h1>
6262
<p>Date: {datetime.datetime.fromisoformat(project['created_at']).date()}</p>
63-
<p>Duration: {_get_duration_str(duration)}</p>
63+
<p>Duration: {get_duration_str(duration)}</p>
6464
</div>
6565
<div class="mt-4">
6666
<table class="w-full border-collapse border border-gray-300">

PyTM/core/project_handler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime
1+
import datetime
22

33
from PyTM import settings
44

@@ -10,7 +10,7 @@ def create(data, project_name):
1010
else:
1111
data[project_name] = {
1212
"tasks": {},
13-
"created_at": f"{datetime.now()}",
13+
"created_at": f"{datetime.datetime.now()}",
1414
"status": settings.STARTED,
1515
}
1616
return data
@@ -55,7 +55,7 @@ def remove(data, project_name):
5555

5656

5757
def rename(data, project_name, new_name):
58-
if not new_name in data.keys():
58+
if new_name not in data.keys():
5959
if data.get(project_name):
6060
data[new_name] = data.pop(project_name)
6161
return data

PyTM/core/task_handler.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
1-
from datetime import datetime
1+
import datetime
22

33
from PyTM import settings
44

55

6-
def _calc_duration(date1, date2):
7-
return (
8-
datetime.strptime(date1, "%Y-%m-%d %H:%M:%S.%f")
9-
- datetime.strptime(date2, "%Y-%m-%d %H:%M:%S.%f")
10-
).total_seconds()
6+
def calculate_duration(date1, date2):
7+
return abs(
8+
(
9+
datetime.datetime.fromisoformat(date1)
10+
- datetime.datetime.fromisoformat(date2)
11+
).total_seconds()
12+
)
1113

1214

1315
def create(data, project_name, task_name):
1416
"""Create and/or Start the task"""
1517
if data.get(project_name):
1618
if data.get(project_name)["tasks"].get(task_name):
1719
data.get(project_name)["tasks"][task_name]["status"] = settings.STARTED
18-
data.get(project_name)["tasks"][task_name]["since"] = f"{datetime.now()}"
20+
data.get(project_name)["tasks"][task_name][
21+
"since"
22+
] = f"{datetime.datetime.now()}"
1923

2024
else:
2125
data.get(project_name)["tasks"][task_name] = {
22-
"created_at": f"{datetime.now()}",
26+
"created_at": f"{datetime.datetime.now()}",
2327
"status": settings.STARTED,
2428
"duration": 0,
25-
"since": f"{datetime.now()}",
29+
"since": f"{datetime.datetime.now()}",
2630
}
2731

2832
return data
@@ -33,8 +37,11 @@ def pause(data, project_name, task_name):
3337
if data.get(project_name):
3438
if data.get(project_name)["tasks"].get(task_name):
3539
data.get(project_name)["tasks"][task_name]["status"] = settings.PAUSED
36-
data.get(project_name)["tasks"][task_name]["duration"] += _calc_duration(
37-
f"{datetime.now()}", data.get(project_name)["tasks"][task_name]["since"]
40+
data.get(project_name)["tasks"][task_name][
41+
"duration"
42+
] += calculate_duration(
43+
f"{datetime.datetime.now()}",
44+
data.get(project_name)["tasks"][task_name]["since"],
3845
)
3946
data.get(project_name)["tasks"][task_name]["since"] = ""
4047
return data
@@ -45,13 +52,16 @@ def finish(data, project_name, task_name):
4552
if data.get(project_name):
4653
if data.get(project_name)["tasks"].get(task_name):
4754
data.get(project_name)["tasks"][task_name]["status"] = settings.FINISHED
48-
data.get(project_name)["tasks"][task_name]["duration"] += _calc_duration(
49-
f"{datetime.now()}", data.get(project_name)["tasks"][task_name]["since"]
55+
data.get(project_name)["tasks"][task_name][
56+
"duration"
57+
] += calculate_duration(
58+
f"{datetime.datetime.now()}",
59+
data.get(project_name)["tasks"][task_name]["since"],
5060
)
5161
data.get(project_name)["tasks"][task_name]["since"] = ""
5262
data.get(project_name)["tasks"][task_name][
5363
"finished_at"
54-
] = f"{datetime.now()}"
64+
] = f"{datetime.datetime.now()}"
5565
return data
5666

5767

README.rst

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
-------------------------------------------------------------------
1010

1111

12-
|image1| |image2| |image3| |Contributors| |DownloadStats| |DocsStats|
13-
=====================================================================
12+
|image1| |coverage| |image3| |Contributors| |DownloadStats| |DocsStats| |image2|
13+
================================================================================
1414

1515
.. |image1| image:: https://badge.fury.io/py/python-pytm.png
1616
:target: https://badge.fury.io/py/python-pytm
@@ -28,7 +28,9 @@
2828
.. |DocsStats| image:: https://readthedocs.org/projects/pytm/badge/?version=latest
2929
:target: https://pytm.readthedocs.io/en/latest/?badge=latest
3030
:alt: Documentation Status
31-
31+
.. |coverage| image:: https://img.shields.io/badge/coverage-56%25-blue
32+
:target: https://pytm.readthedocs.io/en/latest/?badge=latest
33+
:alt: Documentation Status
3234

3335
Goals
3436
-----
@@ -125,6 +127,9 @@ Check version::
125127
pytm --version
126128
pytm -v
127129

130+
Check summary of all the projects::
131+
132+
pytm summary
128133

129134
For a list of all the available commands try::
130135

0 commit comments

Comments
 (0)