Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ format:
poetry run isort $(SOURCE_DIRS)

.PHONY: test
test: test.format test.integration test.unit
test: test.format test.integration test.types test.unit

.PHONY: test.format
test.format:
Expand All @@ -77,7 +77,10 @@ test.integration: clean build
docker build --tag $(TEST_DOCKER_IMAGE) --file $(GENERATED_DOCKERFILE) $(INTEGRATION_DIR)
docker run --rm $(TEST_DOCKER_IMAGE) $(INTEGRATION_CMD)

.PHONY: test.types
test.types:
poetry run pyright

.PHONY: test.unit
test.unit:
poetry run pytest --verbose

2 changes: 1 addition & 1 deletion docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ To run utt from local source:

This section is very important as most code changes need tests.

You can run all tests (including format checks, unit tests, and integration tests) with this command:
You can run all tests with this command:

`$ make test`

Expand Down
57 changes: 55 additions & 2 deletions poetry.lock

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

5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ line-length = 120
profile = "black"
line_length = 120

[tool.pyright]
typeCheckingMode = "standard"

[tool.poetry]
authors = ["Mathieu Larose <mathieu@mathieularose.com>"]
description = "A simple command-line time tracker"
Expand All @@ -31,5 +34,7 @@ black = "^23.12.1"
ddt = "^1.7.1"
flake8 = "^7.0.0"
isort = "^5.13.2"
pyright = "^1.1.407"
pytest = "^7.4.4"
requests = "^2.31.0"
setuptools = "^80.9.0"
8 changes: 2 additions & 6 deletions scripts/update_version_in_pyproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,10 @@ def main():
version = get_version(changelog_filename)
print(f"Version: {version}")

if version == UNRELEASED_VERSION_NAME:
if version is None or version == UNRELEASED_VERSION_NAME:
version = "0"

print(
subprocess.check_output(
["poetry", "version", version], stderr=sys.stderr
).decode()
)
print(subprocess.check_output(["poetry", "version", version]).decode())


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion test/integration/utt_example_plugin/setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from distutils.core import setup
from setuptools import setup

setup(
name="utt_foo",
Expand Down
3 changes: 3 additions & 0 deletions test/unit/test_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ class ValidEntry(unittest.TestCase):
def test(self, name, expected_datetime, expected_name, expected_comment):
entry_parser = EntryParser()
entry = entry_parser.parse(name)
if entry is None:
self.fail("EntryParser returned None for valid entry")

self.assertEqual(entry.datetime, expected_datetime)
self.assertEqual(entry.name, expected_name)
self.assertEqual(entry.comment, expected_comment)
Expand Down
3 changes: 2 additions & 1 deletion utt/command.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import argparse
import typing
from dataclasses import dataclass
from typing import Callable

Expand All @@ -7,5 +8,5 @@
class Command:
name: str
description: str
handler_class: Callable[..., Callable[[None], None]]
handler_class: typing.Type
add_args: Callable[[argparse.ArgumentParser], None]
2 changes: 1 addition & 1 deletion utt/components/activities.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def get_current_activity(
if not now_is_between_last_activity_and_end_report_range:
return

return Activity(current_activity_name, last_activity_end, now, True)
return Activity(current_activity_name, last_activity_end, now, True, comment=None)


def remove_hello_activities(activities):
Expand Down
5 changes: 3 additions & 2 deletions utt/components/config_dirname.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import os
import typing

from ..constants import DATA_CONFIG_DEFAULT_DIRNAME, DATA_CONFIG_ENV_VAR_NAME, DATA_CONFIG_SUB_DIRNAME

ConfigDirname = typing.NewType("ConfigDirname", str)

class ConfigDirname(str):
pass


def config_dirname() -> ConfigDirname:
Expand Down
5 changes: 3 additions & 2 deletions utt/components/config_filename.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import os
import typing

from ..constants import CONFIG_FILENAME
from .config_dirname import ConfigDirname

ConfigFilename = typing.NewType("ConfigFilename", str)

class ConfigFilename(str):
pass


def config_filename(config_dirname: ConfigDirname) -> ConfigFilename:
Expand Down
5 changes: 3 additions & 2 deletions utt/components/data_dirname.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import os
import typing

from ..constants import DATA_HOME_DEFAULT_DIRNAME, DATA_HOME_ENV_VAR_NAME, DATA_HOME_SUB_DIRNAME

DataDirname = typing.NewType("DataDirname", str)

class DataDirname(str):
pass


def data_dirname() -> DataDirname:
Expand Down
5 changes: 3 additions & 2 deletions utt/components/data_filename.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import argparse
import os
import typing

from ..constants import ENTRY_FILENAME
from .data_dirname import DataDirname

DataFilename = typing.NewType("DataFilename", str)

class DataFilename(str):
pass


def data_filename(args: argparse.Namespace, data_dirname: DataDirname) -> DataFilename:
Expand Down
7 changes: 4 additions & 3 deletions utt/components/entries.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Generator, List, Tuple
from typing import Generator, List, Optional, Tuple

from ..data_structures.entry import Entry
from .entry_lines import EntryLines
Expand All @@ -21,7 +21,7 @@ def _parse_log(lines: List[Tuple[int, str]], entry_parser: EntryParser) -> Gener
yield entry


def _parse_line(previous_entry: Entry, line_number: int, line: str, entry_parser: EntryParser):
def _parse_line(previous_entry: Optional[Entry], line_number: int, line: str, entry_parser: EntryParser):
# Ignore empty lines
if not line:
return None
Expand All @@ -30,7 +30,8 @@ def _parse_line(previous_entry: Entry, line_number: int, line: str, entry_parser
if new_entry is None:
raise SyntaxError("Invalid syntax at line %d: %s" % (line_number, line))

if previous_entry and previous_entry.datetime > new_entry.datetime:
if previous_entry is not None and previous_entry.datetime > new_entry.datetime:
raise Exception("Error line %d. Not in chronological order: %s > %s" % (line_number, previous_entry, new_entry))

previous_entry = new_entry
return previous_entry, new_entry
10 changes: 6 additions & 4 deletions utt/components/now.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import argparse
import datetime
import typing

Now = typing.NewType("Now", datetime.datetime)

class Now(datetime.datetime):
pass


def now(args: argparse.Namespace) -> Now:
if args.now:
return Now(args.now)
return Now.fromtimestamp(args.now.timestamp())

return Now(datetime.datetime.now())
dt = datetime.datetime.now()
return Now.fromtimestamp(dt.timestamp())
5 changes: 3 additions & 2 deletions utt/components/output.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import io
import typing

Output = typing.NewType("Output", io.TextIOWrapper)

class Output(io.TextIOWrapper):
pass
9 changes: 6 additions & 3 deletions utt/components/report_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def parse_report_range_arguments(
if unparsed_report_date is None:
report_date = today
else:
report_date = parse_date(today, unparsed_report_date)
report_date = parse_date(today, unparsed_report_date, is_past=True)

if unparsed_month:
report_start_date, report_end_date = parse_month(report_date, unparsed_month)
Expand All @@ -65,7 +65,7 @@ def parse_report_range_arguments(
return DateRange(start=report_start_date, end=report_end_date)


def parse_date(today, datestring, is_past=True):
def parse_date(today: datetime.date, datestring: str, is_past: bool):
day = parse_relative_day(today, datestring)
if day is not None:
return day
Expand Down Expand Up @@ -217,10 +217,13 @@ def parse_week_number(today, weekstring):
return datetime.date.fromisocalendar(year, weeknum, 1)


def parse_week(today, weekstring):
def parse_week(today: datetime.date, weekstring: str):
week = parse_relative_week(today, weekstring)
if week is None:
week = parse_week_number(today, weekstring)
if week is None:
raise ValueError(f"Invalid week string: {weekstring}")

start = week
end = week + datetime.timedelta(days=6)

Expand Down
6 changes: 4 additions & 2 deletions utt/data_structures/activity.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import copy
from datetime import datetime
from typing import Optional

from .name import Name

Expand All @@ -10,20 +11,21 @@ class Type:
BREAK = 1
IGNORED = 2

@staticmethod
def name(type: int) -> str:
return {
Activity.Type.WORK: "WORK",
Activity.Type.BREAK: "BREAK",
Activity.Type.IGNORED: "IGNORED",
}.get(type)
}[type]

def __init__(
self,
name: str,
start: datetime,
end: datetime,
is_current_activity: bool,
comment: str = None,
comment: Optional[str],
):
self.name = Name(name)
self.start = start
Expand Down
3 changes: 2 additions & 1 deletion utt/data_structures/entry.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime
from typing import Optional


class Entry:
Expand All @@ -7,7 +8,7 @@ def __init__(
entry_datetime: datetime,
name: str,
is_current_entry: bool,
comment: str = None,
comment: Optional[str],
):
self.datetime = entry_datetime
self.name = name
Expand Down
2 changes: 1 addition & 1 deletion utt/plugins/0_hello.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def __init__(
self._add_entry = add_entry

def __call__(self):
self._add_entry(_v1.Entry(self._now, _v1.HELLO_ENTRY_NAME, False))
self._add_entry(_v1.Entry(self._now, _v1.HELLO_ENTRY_NAME, False, comment=None))


hello_command = _v1.Command(
Expand Down
4 changes: 2 additions & 2 deletions utt/report/activities/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ def key(act):

result = []
sorted_activities = sorted(activities, key=key)
for _, activities in itertools.groupby(sorted_activities, key):
activities = list(activities)
for _, _activities in itertools.groupby(sorted_activities, key):
activities = list(_activities)
project = activities[0].name.project
result.append(
{
Expand Down
Loading