diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3feb78a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.egg-info/ diff --git a/pyproject.toml b/pyproject.toml index ee17f34..7c99962 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,10 @@ requires-python = ">=3.11" dependencies = [] [project.optional-dependencies] -dev = ["pytest>=8.0"] +dev = [ + "pytest>=8.0", + "matplotlib>=3.8", +] [build-system] requires = ["setuptools>=42", "wheel"] diff --git a/src/cli.py b/src/cli.py index 090c619..46bf5d3 100644 --- a/src/cli.py +++ b/src/cli.py @@ -1,6 +1,7 @@ import sys import click from src.mood_logger import log_mood, latest_entries +from src.plot import show as plot_show @click.group() def cli(): @@ -23,5 +24,10 @@ def latest(n): for e in entries: click.echo(f"{e.date}: {e.rating} – {e.note or ''}") +@cli.command() +def plot(): + """Plot the last 30 mood ratings.""" + plot_show() + if __name__ == "__main__": cli() diff --git a/src/mood_logger.py b/src/mood_logger.py index 261130a..29d87b4 100644 --- a/src/mood_logger.py +++ b/src/mood_logger.py @@ -50,3 +50,15 @@ def latest_entries( for row in rows ] return entries[-n:] + +def load_entries(*, file_path: str = "mood.csv") -> List[MoodEntry]: + """Return all mood entries from the CSV file.""" + path = Path(file_path) + if not path.exists(): + return [] + with path.open(newline="") as csvfile: + rows = list(csv.DictReader(csvfile)) + return [ + MoodEntry(row["date"], int(row["rating"]), row.get("note") or None) + for row in rows + ] diff --git a/src/plot.py b/src/plot.py new file mode 100644 index 0000000..d8ac3f7 --- /dev/null +++ b/src/plot.py @@ -0,0 +1,18 @@ +from datetime import datetime +from matplotlib import pyplot as plt +from .mood_logger import load_entries + + +def show(*, file_path: str = "mood.csv") -> None: + """Display a line chart of the last 30 mood entries.""" + entries = load_entries(file_path=file_path)[-30:] + if not entries: + return + dates = [datetime.fromisoformat(e.date) for e in entries] + ratings = [e.rating for e in entries] + plt.plot(dates, ratings, marker="o") + plt.xlabel("Date") + plt.ylabel("Rating") + plt.gcf().autofmt_xdate() + plt.tight_layout() + plt.show() diff --git a/tests/test_plot.py b/tests/test_plot.py new file mode 100644 index 0000000..67a3e00 --- /dev/null +++ b/tests/test_plot.py @@ -0,0 +1,20 @@ +import os +import matplotlib.pyplot as plt +from src.plot import show +from src.mood_logger import log_mood +import pytest + +@pytest.mark.skipif(os.getenv("CI") == "true", reason="Skip on CI") +def test_show_calls_pyplot_show(tmp_path, monkeypatch): + file = tmp_path / "mood.csv" + for rating in range(3): + log_mood(rating + 1, file_path=str(file)) + + called = [] + + def fake_show(): + called.append(True) + + monkeypatch.setattr(plt, "show", fake_show) + show(file_path=str(file)) + assert called