Skip to content

Commit

Permalink
Merge pull request #259 from wasi0013/v0.0.16
Browse files Browse the repository at this point in the history
V0.0.16
  • Loading branch information
wasi0013 committed Nov 10, 2023
2 parents 14e36db + 8971231 commit 8ec524d
Show file tree
Hide file tree
Showing 16 changed files with 494 additions and 59 deletions.
12 changes: 11 additions & 1 deletion HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,14 @@ History
* Simplified task start command.
* New tasks can be started without task name.
* Task started without a name will have a default name which can be renamed later at ease.
* Bug fix
* Bug fix

0.0.16 (2023-11-11)
-------------------

* added new command summary.
* fixed bug and refactored invoice handler.
* added tests for project handler.
* added tests for task handler.
* added tests for invoice handler.
* updated make command to update README coverage percentage while testing.
11 changes: 6 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ clean-pyc:
lint:
flake8 PyTM test

test:
py.test

test-all:
tox
covtest:
py.test --cov-report json --cov

test: covtest
sed -i "s/coverage-[0-9]\+%25/coverage-$(shell jq -r .totals.percent_covered_display coverage.json)%25/g" README.rst
rm -rf coverage.json

coverage:
coverage run --source PyTM setup.py test
Expand Down
2 changes: 1 addition & 1 deletion PyTM/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__author__ = "Wasi"
__email__ = "[email protected]"
__version__ = "0.0.15"
__version__ = "0.0.16"
64 changes: 56 additions & 8 deletions PyTM/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
import click
from rich.prompt import Confirm, Prompt
from rich.table import Table
from rich.tree import Tree
from rich.panel import Panel
from rich.layout import Layout

from PyTM import __version__, settings
from PyTM.commands.project import project
from PyTM.commands.project import project, get_duration_str
from PyTM.commands.task import task
from PyTM.console import console
from PyTM.core import data_handler, invoice_handler
Expand All @@ -25,7 +28,7 @@ def init_data_store(show_messages=False):
try:
os.makedirs(settings.data_folder)
messages.append(f"Created data folder: {settings.data_folder}")
except:
except Exception as _:
messages.append(f"Data folder already exists: {settings.data_folder}")
if not os.path.exists(settings.data_filepath):
data_handler.init_data(settings.data_filepath)
Expand Down Expand Up @@ -59,7 +62,7 @@ def print_version(ctx, param, value):
return
console.print("\n[bold green]✨ PyTM ✨")
console.print(f"version {__version__}")
console.print(f"docs: https://pytm.rtfd.org")
console.print("docs: https://pytm.rtfd.org")
ctx.exit()


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

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

invoice_texts["title"] = Prompt.ask(
Expand Down Expand Up @@ -329,7 +332,7 @@ def auto(project_name):
)
try:
os.makedirs(os.path.join(settings.data_folder, "invoices"))
except:
except Exception as _:
pass

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

invoice_texts["title"] = Prompt.ask(
Expand Down Expand Up @@ -420,7 +423,7 @@ def manual():
)
try:
os.makedirs(os.path.join(settings.data_folder, "invoices"))
except:
except Exception as _:
pass
html_file = os.path.join(
settings.data_folder, "invoices", f"{invoice_texts['title']}.html"
Expand All @@ -431,12 +434,57 @@ def manual():
webbrowser.open(f"file:///{html_file}", autoraise=True)


@click.command()
def summary():
"""
- shows summary of all projects.
"""
data = data_handler.load_data()
layout = Layout()

count = 0
left, right = [], []
for project_name in data:
project_data = data.get(project_name, {}).get("tasks", {})
tree = Tree(
f'[bold blue]{project_name}[/bold blue] ([i]{data.get(project_name, {})["status"]}[/i])'
)
if project_data == {}:
tree.add("[red] No tasks yet. [/red]")
right.append(Panel(tree, title=f"{project_name}"))
continue
duration = 0
for task_name, t in project_data.items():
task_duration = int(round(t["duration"]))
duration += task_duration
tree.add(
f"[green]{task_name}[/green]: {get_duration_str(task_duration)} ([i]{t['status']}[/i])"
)
left.append(
Panel(
tree,
title=f"{project_name}",
subtitle=f"[blue bold]Total time[/blue bold]: {get_duration_str(duration)}",
expand=False,
)
)
count += 1
layout.split_row( # *p)
Layout(name="left", size=45),
Layout(name="right", size=55),
)
layout["left"].split_column(*left)
layout["right"].split_column(*right)
console.print(layout)


cli.add_command(init)
cli.add_command(project)
cli.add_command(task)
cli.add_command(show)
cli.add_command(config)
cli.add_command(invoice)
cli.add_command(summary)

if __name__ == "__main__":
cli()
10 changes: 5 additions & 5 deletions PyTM/commands/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from PyTM.core import project_handler


def _get_duration_str(sum_of_durations):
def get_duration_str(sum_of_durations):
m, s = divmod(sum_of_durations, 60)
duration = ""
if m > 60:
Expand Down Expand Up @@ -168,18 +168,18 @@ def summary(project_name):
"""
- shows total time of the project with task and duration.
"""
project = project_handler.summary(data_handler.load_data(), project_name)
project_data = project["tasks"]
project_data = project_handler.summary(data_handler.load_data(), project_name)
project_data = project_data.get("tasks", {})
tree = Tree(f'[bold blue]{project_name}[/bold blue] ([i]{project["status"]}[/i])')
duration = 0
for task, t in project_data.items():
task_duration = int(round(t["duration"]))
duration += task_duration
tree.add(
f"[green]{task}[/green]: {_get_duration_str(task_duration)} ([i]{t['status']}[/i])"
f"[green]{task}[/green]: {get_duration_str(task_duration)} ([i]{t['status']}[/i])"
)
console.print(Panel.fit(tree))
console.print(f"[blue bold]Total time[/blue bold]: {_get_duration_str(duration)}")
console.print(f"[blue bold]Total time[/blue bold]: {get_duration_str(duration)}")


@project.command()
Expand Down
11 changes: 6 additions & 5 deletions PyTM/core/data_handler.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import json
from PyTM.settings import data_filepath

from PyTM import settings

def init_data(path=data_filepath, data={}):

def init_data(path=settings.data_filepath, data={}):
"""
Creates the data to the given path.
"""
with open(path, "w") as f:
json.dump(data, f)


def load_data(path=data_filepath):
def load_data(path=settings.data_filepath):
"""
Loads the data from the given path.
"""
Expand All @@ -21,7 +22,7 @@ def load_data(path=data_filepath):
return {}


def save_data(data, path=data_filepath):
def save_data(data, path=settings.data_filepath):
"""
Saves the data to the given path.
"""
Expand All @@ -30,7 +31,7 @@ def save_data(data, path=data_filepath):
json.dump(data, f)


def update(func, path=data_filepath):
def update(func, path=settings.data_filepath):
"""
Decorator for updating the data.
"""
Expand Down
8 changes: 4 additions & 4 deletions PyTM/core/invoice_handler.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from PyTM import settings
from PyTM.core import data_handler
from PyTM.commands.project import _get_duration_str
import datetime

from PyTM import settings
from PyTM.commands.project import get_duration_str


def generate(invoice_number, invoice_texts, user, project, discount=0):
title, logo, foot_note = (
Expand Down Expand Up @@ -60,7 +60,7 @@ def generate(invoice_number, invoice_texts, user, project, discount=0):
<div class="text-left mb-4">
<h1 class="text-2xl font-semibold">Project: {project['meta']['title']}</h1>
<p>Date: {datetime.datetime.fromisoformat(project['created_at']).date()}</p>
<p>Duration: {_get_duration_str(duration)}</p>
<p>Duration: {get_duration_str(duration)}</p>
</div>
<div class="mt-4">
<table class="w-full border-collapse border border-gray-300">
Expand Down
6 changes: 3 additions & 3 deletions PyTM/core/project_handler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime
import datetime

from PyTM import settings

Expand All @@ -10,7 +10,7 @@ def create(data, project_name):
else:
data[project_name] = {
"tasks": {},
"created_at": f"{datetime.now()}",
"created_at": f"{datetime.datetime.now()}",
"status": settings.STARTED,
}
return data
Expand Down Expand Up @@ -55,7 +55,7 @@ def remove(data, project_name):


def rename(data, project_name, new_name):
if not new_name in data.keys():
if new_name not in data.keys():
if data.get(project_name):
data[new_name] = data.pop(project_name)
return data
38 changes: 24 additions & 14 deletions PyTM/core/task_handler.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
from datetime import datetime
import datetime

from PyTM import settings


def _calc_duration(date1, date2):
return (
datetime.strptime(date1, "%Y-%m-%d %H:%M:%S.%f")
- datetime.strptime(date2, "%Y-%m-%d %H:%M:%S.%f")
).total_seconds()
def calculate_duration(date1, date2):
return abs(
(
datetime.datetime.fromisoformat(date1)
- datetime.datetime.fromisoformat(date2)
).total_seconds()
)


def create(data, project_name, task_name):
"""Create and/or Start the task"""
if data.get(project_name):
if data.get(project_name)["tasks"].get(task_name):
data.get(project_name)["tasks"][task_name]["status"] = settings.STARTED
data.get(project_name)["tasks"][task_name]["since"] = f"{datetime.now()}"
data.get(project_name)["tasks"][task_name][
"since"
] = f"{datetime.datetime.now()}"

else:
data.get(project_name)["tasks"][task_name] = {
"created_at": f"{datetime.now()}",
"created_at": f"{datetime.datetime.now()}",
"status": settings.STARTED,
"duration": 0,
"since": f"{datetime.now()}",
"since": f"{datetime.datetime.now()}",
}

return data
Expand All @@ -33,8 +37,11 @@ def pause(data, project_name, task_name):
if data.get(project_name):
if data.get(project_name)["tasks"].get(task_name):
data.get(project_name)["tasks"][task_name]["status"] = settings.PAUSED
data.get(project_name)["tasks"][task_name]["duration"] += _calc_duration(
f"{datetime.now()}", data.get(project_name)["tasks"][task_name]["since"]
data.get(project_name)["tasks"][task_name][
"duration"
] += calculate_duration(
f"{datetime.datetime.now()}",
data.get(project_name)["tasks"][task_name]["since"],
)
data.get(project_name)["tasks"][task_name]["since"] = ""
return data
Expand All @@ -45,13 +52,16 @@ def finish(data, project_name, task_name):
if data.get(project_name):
if data.get(project_name)["tasks"].get(task_name):
data.get(project_name)["tasks"][task_name]["status"] = settings.FINISHED
data.get(project_name)["tasks"][task_name]["duration"] += _calc_duration(
f"{datetime.now()}", data.get(project_name)["tasks"][task_name]["since"]
data.get(project_name)["tasks"][task_name][
"duration"
] += calculate_duration(
f"{datetime.datetime.now()}",
data.get(project_name)["tasks"][task_name]["since"],
)
data.get(project_name)["tasks"][task_name]["since"] = ""
data.get(project_name)["tasks"][task_name][
"finished_at"
] = f"{datetime.now()}"
] = f"{datetime.datetime.now()}"
return data


Expand Down
11 changes: 8 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
-------------------------------------------------------------------


|image1| |image2| |image3| |Contributors| |DownloadStats| |DocsStats|
=====================================================================
|image1| |coverage| |image3| |Contributors| |DownloadStats| |DocsStats| |image2|
================================================================================

.. |image1| image:: https://badge.fury.io/py/python-pytm.png
:target: https://badge.fury.io/py/python-pytm
Expand All @@ -28,7 +28,9 @@
.. |DocsStats| image:: https://readthedocs.org/projects/pytm/badge/?version=latest
:target: https://pytm.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status

.. |coverage| image:: https://img.shields.io/badge/coverage-56%25-blue
:target: https://pytm.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status

Goals
-----
Expand Down Expand Up @@ -125,6 +127,9 @@ Check version::
pytm --version
pytm -v

Check summary of all the projects::

pytm summary

For a list of all the available commands try::

Expand Down
Loading

0 comments on commit 8ec524d

Please sign in to comment.