Skip to content

Commit

Permalink
feat: Vertical histogram to display next reviews (#11)
Browse files Browse the repository at this point in the history
- "summary" displays a vertical histogram of the daily review plan.
- Add test for the histogram and the summary output
- When no next reviews for the day are available display: "No more reviews for today"
- Made a new demo.gif with the new summary output
- Add python freezegun to help testing the histogram (mock date)
  • Loading branch information
ajite authored Jun 1, 2022
1 parent 3fc723f commit 67fc8a8
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 16 deletions.
Binary file modified docs/source/_static/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/source/generated/hebikani.hebikani.AnswerManager.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ hebikani.hebikani.AnswerManager

~AnswerManager.acceptable_answers
~AnswerManager.answer_values
~AnswerManager.hard_mode_acceptable_answers
~AnswerManager.primary
~AnswerManager.question_type
~AnswerManager.unacceptable_answers
Expand Down
1 change: 1 addition & 0 deletions docs/source/generated/hebikani.hebikani.Summary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ hebikani.hebikani.Summary
~Summary.lessons
~Summary.nb_lessons
~Summary.nb_reviews
~Summary.next_reviews_info
~Summary.reviews


2 changes: 2 additions & 0 deletions docs/source/generated/hebikani.hebikani.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@

url_to_ascii

utc_to_local

wanikani_tag_to_color


6 changes: 6 additions & 0 deletions docs/source/generated/hebikani.hebikani.utc_to_local.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
hebikani.hebikani.utc\_to\_local
================================

.. currentmodule:: hebikani.hebikani

.. autofunction:: utc_to_local
75 changes: 75 additions & 0 deletions hebikani/graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""All graph related functions
Inspired by: https://github.com/tammoippen/plotille
Usage:
>>> from hebikani.graph import hist
>>> hist([(datetime(2020, 1, 1, 12, 0, 0), 1), (datetime(2020, 1, 2, 15, 0, 0), 2)])
"""
from datetime import datetime
import os

from colorama import Fore


def hist(
data: list[tuple[datetime, int]],
width: int = 80,
total: int = 0,
linesep: str = os.linesep,
):
"""Create histogram over `data` from left to right
The values on the left are the dates of this bucket.
The values on the right are the number of reviews of this bucket.
Args:
data: list[tuple[datetime, int]] The items to count over.
width: int The number of characters for the width (columns).
total: int The total number of items at the start of the histogram.
linesep: str The requested line seperator. default: os.linesep
Returns:
str: histogram over `data` from left to right.
"""
canvas = []
if data:
# Sort the data by date
data.sort(key=lambda _data: _data[0])

# Extract X and Y values
x_values = [_data[0] for _data in data]
y_values = [_data[1] for _data in data]

y_max = max(y_values)
y_total = total + sum(y_values)

# Calculate the delimiter width
delimiter_width = 2 * 8 + len(str(y_total)) + width

header = "Today | "
canvas += [
linesep + header + "{}".format("_" * (delimiter_width - len(header)))
]
lasts = ["", "⠂", "⠆", "⠇", "⡇", "⡗", "⡷", "⡿"]

current_reviews_nb = total

for i, x_value in enumerate(x_values):
x_value_str = x_value.strftime("%I %p")
current_reviews_nb += y_values[i]
hight = int(width * 8 * y_values[i] / y_max)
spaces = " " * (len(str(y_max)) - len(str(y_values[i])))
canvas += [
"{} | {}{}{}{} +{}{} | {}".format(
x_value_str,
Fore.GREEN,
"⣿" * (hight // 8) + lasts[hight % 8],
Fore.RESET,
"⣿" * (width - (hight // 8) + int(hight % 8 == 0)),
y_values[i],
spaces,
current_reviews_nb,
)
]
canvas += ["‾" * delimiter_width]
return linesep.join(canvas)
50 changes: 49 additions & 1 deletion hebikani/hebikani.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
>>> from hebikani import hebikani
>>> client = hebikani.Client(API_KEY)
"""
import datetime
import os
import random
import re
import tempfile
import threading
import time
from argparse import ArgumentParser, ArgumentTypeError, RawTextHelpFormatter
from difflib import get_close_matches
from io import BytesIO
Expand All @@ -28,6 +30,7 @@
from playsound import playsound

from hebikani import __version__
from hebikani.graph import hist
from hebikani.input import getch, input_kana
from hebikani.typing import (
AnswerType,
Expand Down Expand Up @@ -152,6 +155,22 @@ def chunks(lst, n):
yield lst[i : i + n]


def utc_to_local(utc: datetime.datetime) -> datetime.datetime:
"""Convert a UTC datetime to local datetime.
Args:
utc (datetime.datetime): The UTC datetime to convert.
Returns:
datetime.datetime: The local datetime.
"""
epoch = time.mktime(utc.timetuple())
offset = datetime.datetime.fromtimestamp(
epoch
) - datetime.datetime.utcfromtimestamp(epoch)
return utc + offset


class ClientOptions:
"""Client options."""

Expand Down Expand Up @@ -322,7 +341,8 @@ class Summary(APIObject):
def __str__(self) -> str:
return f"""Summary:
Lessons: {self.nb_lessons}
Reviews: {self.nb_reviews}"""
Reviews: {self.nb_reviews}
Next reviews: {self.next_reviews_info or 'No more reviews for today'}"""

@property
def lessons(self):
Expand All @@ -344,6 +364,34 @@ def nb_reviews(self):
"""Get the number of reviews available."""
return len(self.reviews)

@property
def next_reviews_info(self) -> str:
"""Get the next reviews info."""
hist_data = []
bins = 0
for review in self.data["data"]["reviews"][1:]:
nb_reviews = len(review["subject_ids"])

if nb_reviews == 0:
continue
review_time = datetime.datetime.fromisoformat(
review["available_at"].replace("Z", "+00:00")
)
review_time = utc_to_local(review_time).replace(tzinfo=None)

if datetime.datetime.today().date() != review_time.date():
continue

bins += 1
hist_data += [(review_time, nb_reviews)]

return hist(
hist_data,
width=30,
total=self.nb_reviews,
linesep="\n\t",
)


class ContextSentence(APIObject):
"""Context Setence object from WaniKani"""
Expand Down
32 changes: 31 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ black = "^22.3.0"
Sphinx = "^4.5.0"
sphinxcontrib-napoleon = "^0.7"
sphinx-rtd-theme = "^1.0.0"
freezegun = "^1.2.1"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
15 changes: 10 additions & 5 deletions tests/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@
get_summary = {
"object": "report",
"url": "https://api.wanikani.com/v2/summary",
"data_updated_at": "2018-04-11T21:00:00.000000Z",
"data_updated_at": "2018-04-11T00:00:00.000000Z",
"data": {
"lessons": [
{"available_at": "2018-04-11T21:00:00.000000Z", "subject_ids": [25, 26]}
{"available_at": "2018-04-11T00:00:00.000000Z", "subject_ids": [25, 26]}
],
"next_reviews_at": "2018-04-11T21:00:00.000000Z",
"next_reviews_at": "2018-04-11T11:00:00.000000Z",
"reviews": [
{
"available_at": "2018-04-11T21:00:00.000000Z",
"available_at": "2018-04-11T00:00:00.000000Z",
"subject_ids": [21, 23, 24],
},
{"available_at": "2018-04-11T22:00:00.000000Z", "subject_ids": []},
{
"available_at": "2018-04-11T10:00:00.000000Z",
"subject_ids": [27, 28, 29],
},
{"available_at": "2018-04-11T13:00:00.000000Z", "subject_ids": []},
{"available_at": "2018-04-11T15:00:00.000000Z", "subject_ids": [30, 31]},
],
},
}
Expand Down
22 changes: 22 additions & 0 deletions tests/test_graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from datetime import datetime
from hebikani.graph import hist


def test_hist():
"""Test histogram."""
data = [(datetime(2020, 1, 1, 12, 0, 0), 1), (datetime(2020, 1, 2, 15, 0, 0), 2)]
assert hist(data) == (
"\nToday | ____________________________________"
"______________________________________________"
"_______\n12 PM | \x1b[32m⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿"
"⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿\x1b[39m⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿"
"⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ +1 | 1\n03 PM | "
"\x1b[32m⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿"
"⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿"
"⣿⣿\x1b[39m⣿ +2 | 3\n‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾"
"‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾"
"‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾"
)

# Test empty data
assert hist([]) == ""
Loading

0 comments on commit 67fc8a8

Please sign in to comment.