Skip to content

Commit

Permalink
feat: add csv list format.
Browse files Browse the repository at this point in the history
  • Loading branch information
codito committed Jan 13, 2024
1 parent e41f02f commit 827a9ca
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 16 deletions.
72 changes: 60 additions & 12 deletions habito/commands/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
"""List all habits."""
import logging
import shutil
import os
from datetime import datetime, timedelta
from typing import Literal, Union
from typing import Literal, List, Tuple, Union

import click

Expand Down Expand Up @@ -34,7 +35,6 @@
default="1 week",
help=(
"Duration for the report. Default is 1 week. "
"Use `all` to dump all activities since beginning. "
"If format is table, maximum duration is inferred "
"from the terminal width."
),
Expand All @@ -45,18 +45,47 @@ def list(
duration="1 week",
):
"""List all tracked habits."""
from textwrap import wrap
nr_of_dates = _get_max_duration(format, duration)
if format == "csv":
_show_csv(nr_of_dates)
return

from terminaltables import SingleTable
_show_table(nr_of_dates, long_list)

terminal_width, terminal_height = shutil.get_terminal_size()

nr_of_dates = terminal_width // 10 - 4
def _show_csv(nr_of_dates: int):
"""List habits in csv format grouped by day."""
if nr_of_dates < 1:
click.echo("Invalid duration. Try `1 week`, `30 days` or `2 months`.")
raise SystemExit(1)

data: List[Tuple[int, float, str]] = []
data.append((0, 0, f"id,name,goal,units,date,activity{os.linesep}"))
for habit_data in models.get_daily_activities(nr_of_dates):
habit = habit_data[0]
for activity in habit_data[1]:
for_date = datetime.today() - timedelta(days=activity[0])
data.append(
(
habit.id,
for_date.timestamp(),
(
f"{habit.id},{habit.name},{habit.quantum},{habit.units}"
f",{for_date.date()},{activity[1] or 0.0}"
f"{os.linesep}"
),
)
)
data.sort(key=lambda t: t[1])
click.echo_via_pager(map(lambda d: d[2], data))


def _show_table(nr_of_dates: int, long_list: bool):
"""List habits in tabular format grouped by day."""
from textwrap import wrap
from terminaltables import SingleTable

if nr_of_dates < 1:
logger.debug(
"list: Actual terminal width = {0}.".format(shutil.get_terminal_size()[0])
)
logger.debug("list: Observed terminal width = {0}.".format(terminal_width))
click.echo(
"Your terminal window is too small. Please make it wider and try again"
)
Expand Down Expand Up @@ -106,5 +135,24 @@ def list(
click.echo(table.table)


# def _get_max_duration(format: str, duration: str) -> int:
# return 0
def _get_max_duration(format: str, duration: str) -> int:
if format != "table":
import dateparser

from_date = dateparser.parse(duration)
if from_date is None:
logger.debug(f"list: Cannot parse from date. Input duration = {duration}.")
return -1
days = (datetime.now() - from_date).days
return days

# Calculate duration that fits to the terminal width
terminal_width, _ = shutil.get_terminal_size()

nr_of_dates = terminal_width // 10 - 4
if format == "table" and nr_of_dates < 1:
logger.debug(
"list: Actual terminal width = {0}.".format(shutil.get_terminal_size()[0])
)
logger.debug("list: Observed terminal width = {0}.".format(terminal_width))
return nr_of_dates
8 changes: 4 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
VERSION = (1, 1, 0)

# Dependencies required for execution
REQUIRED = ["click", "peewee>=3.0.15", "terminaltables"]
REQUIRED = ["click", "peewee>=3.0.15", "terminaltables", "dateparser"]

here = path.abspath(path.dirname(__file__))

Expand Down Expand Up @@ -91,9 +91,9 @@ def run(self):
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Utilities",
],
keywords="habits goals track tracking quantified self",
Expand Down
49 changes: 49 additions & 0 deletions tests/commands/test_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,55 @@


class HabitoListCommandTestCase(HabitoCommandTestCase):
def test_habito_list_default_format_is_table(self):
habit = self.create_habit()
self.add_summary(habit)

result = self._run_command(habito.commands.list, ["-l"])

assert "id,name," not in result.output

def test_habito_list_csv_duration(self):
habit_one = self.create_habit()
self.add_summary(habit_one)

result = self._run_command(
habito.commands.list, ["-l", "-f", "csv", "-d", "13 days"]
)

# 1 habit for 13 days = 15 data points incl header and footer
assert 15 == len(result.output.splitlines())

def test_habito_list_csv_invalid_from_date(self):
habit_one = self.create_habit()
self.add_summary(habit_one)

result = self._run_command(
habito.commands.list, ["-l", "-f", "csv", "-d", "13 nights"]
)

assert 1 == result.exit_code

def test_habito_list_csv_headers_and_data(self):
habit_one = self.create_habit()
habit_two = self.create_habit(name="HabitTwo")
self.add_summary(habit_one)
self.add_summary(habit_two)
three_days_back = (datetime.now() - timedelta(days=3)).date()
self._run_command(habito.commands.checkin, ["One", "-q 3.0"])
self._run_command(habito.commands.checkin, ["One", "-q 2.0"])

result = self._run_command(habito.commands.list, ["-l", "-f", "csv"])

# 2 habits for 7 days = 16 data points incl header and footer
assert "id,name,goal,units,date,activity" in result.output
assert (
f"1,HabitOne,0.0,dummy_units,{datetime.now().date()},5.0" in result.output
)
assert f"1,HabitOne,0.0,dummy_units,{three_days_back},0.0" in result.output
assert f"2,HabitTwo,0.0,dummy_units,{three_days_back},0.0" in result.output
assert 16 == len(result.output.splitlines())

def test_habito_list_lists_off_track_habits(self):
habit = self.create_habit()
self.add_summary(habit)
Expand Down

0 comments on commit 827a9ca

Please sign in to comment.