Compare commits

..

2 Commits

Author SHA1 Message Date
ccd0178de1 generate tasks when needed, still need to consider more cases 2025-02-09 15:57:01 -06:00
3279dd9e8c show next_at() 2025-02-09 15:34:16 -06:00
4 changed files with 92 additions and 49 deletions

View File

@ -1,5 +1,7 @@
from ..db import db, TaskGenerator, GeneratorType
import json
from datetime import date, timedelta
from ..db import db, TaskGenerator, GeneratorType
from .tasks import add_task
def get_generator(item_id: int) -> TaskGenerator:
@ -27,12 +29,36 @@ def add_generator(
return task
def generate_needed_tasks():
to_create = []
for g in get_generators():
next = g.next_at()
if not next:
continue
# TODO: make configurable
if date.today() - next > timedelta(days=14):
to_create.append(
{
"text": g.template.format(next=next),
"category": "recurring",
"due": next,
}
)
for c in to_create:
add_task(**c)
return to_create
def update_generator(
item_id: int,
**kwargs,
) -> TaskGenerator:
config = {"val": kwargs.pop("val")}
kwargs["config"] = json.dumps(config)
# replace "val" with JSON
if "val" in kwargs:
config = {"val": kwargs.pop("val")}
kwargs["config"] = json.dumps(config)
with db.atomic():
query = TaskGenerator.update(kwargs).where(TaskGenerator.id == item_id)
query.execute()

View File

@ -1,4 +1,5 @@
from datetime import datetime, timedelta
import json
from datetime import date, timedelta, datetime
from enum import Enum
from peewee import (
BooleanField,
@ -46,7 +47,6 @@ class Category(BaseModel):
class Task(BaseModel):
# TODO: rename name
text = TextField()
status = CharField(
choices=[(status.value, status.name) for status in TaskStatus],
@ -81,32 +81,48 @@ class TaskGenerator(BaseModel):
last_generated_at = DateTimeField(null=True)
created_at = DateTimeField(default=datetime.now)
def should_generate(self) -> bool:
def next_at(self) -> datetime.date:
if self.deleted:
return False
if not self.last_generated_at:
return True
return None
now = datetime.now()
if self.type == GeneratorType.DAYS_BETWEEN:
days_between = self.config["val"]
days_since = (now - self.last_generated_at).days
return days_since >= days_between
today = date.today()
val = int(json.loads(self.config)["val"])
elif self.type == GeneratorType.MONTHLY:
day_of_month = self.config["val"]
if self.type == GeneratorType.DAYS_BETWEEN.value:
if not self.last_generated_at:
return today
return self.last_generated_at + timedelta(days=val)
elif self.type == GeneratorType.MONTHLY.value:
year, month, day = today.year, today.month, today.day
day_of_month = val
# check each day until now to see if target day occurred
one_day = timedelta(days=1)
check_date = self.last_generated_at + one_day
while check_date <= now:
if check_date.day == day_of_month:
return True
check_date += one_day
# if we need to go forward a month
if day_of_month < day:
month += 1
if month == 13:
month = 1
year += 1
return False
# recurring tasks on 29-31 in Feb will just happen on 28th
if day_of_month >= 29 and month == 2:
maybe_next = date(year, month, 28)
else:
maybe_next = date(year, month, day_of_month)
return False
if not self.last_generated_at or self.last_generated_at < maybe_next:
return maybe_next
# TODO: this doesn't handle if a month was missed somehow, just advances one
# same logic as above, if we're stepping another month forward
month += 1
if month == 13:
month = 1
year += 1
if day_of_month >= 29 and month == 2:
maybe_next = date(year, month, 28)
else:
maybe_next = date(year, month, day_of_month)
return maybe_next
def initialize_db():
@ -115,7 +131,3 @@ def initialize_db():
if not Category.select().exists():
Category.create(name="default")
db.close()
if __name__ == "__main__":
initialize_db()

View File

@ -42,17 +42,19 @@ class TableColumnConfig:
display_name: str,
*,
default=None,
enable_editor=False,
enum=None,
filterable=True,
preprocessor=None,
enable_editor=False,
filterable=True,
read_only=False,
):
self.field = field
self.display_name = display_name
self.default = default
self.enable_editor = enable_editor
self.enum = enum
self.enable_editor = enable_editor
self.filterable = filterable
self.read_only = read_only
if preprocessor:
self.preprocessor = preprocessor
elif self.enum:
@ -223,6 +225,8 @@ class TableEditor(App):
# prepopulate with either the field default or the current filter
prepopulated = {}
for fc in self.TABLE_CONFIG:
if fc.read_only:
continue
val = self.filters.get(fc.field, fc.default)
if val is not None:
# enums use comma separated filters
@ -320,16 +324,21 @@ class TableEditor(App):
self.saved_cursor_pos = (self.table.cursor_row, self.table.cursor_column)
def action_start_change(self):
# invalid position
if self.table.cursor_row is None or self.table.cursor_column == 0:
return
# check if editable
cconf = self._active_column_config()
if cconf.read_only:
return
self._save_cursor()
current_value = self.table.get_cell_at(
(self.table.cursor_row, self.table.cursor_column)
)
if current_value.endswith(ELLIPSIS):
# TODO: flash message?
# need to edit with e
self.notify("multi-line text, use (e)dit")
return
current_value = remove_rich_tag(current_value)
self._show_input("edit", current_value)

View File

@ -1,27 +1,19 @@
import json
from datetime import datetime
from ..controller.generators import (
get_generator,
get_generators,
add_generator,
update_generator,
generate_needed_tasks,
)
from ..db import GeneratorType
from .editor import (
TableEditor,
TableColumnConfig,
NotifyValidationError,
)
def due_preprocessor(val):
try:
return datetime.strptime(val, "%Y-%m-%d")
except ValueError:
raise NotifyValidationError("Invalid date format. Use YYYY-MM-DD")
class TaskGenEditor(TableEditor):
TABLE_CONFIG = (
TableColumnConfig("id", "ID"),
@ -32,7 +24,8 @@ class TaskGenEditor(TableEditor):
default=GeneratorType.DAYS_BETWEEN.value,
enum=GeneratorType,
),
TableColumnConfig("val", "Value", default=""),
TableColumnConfig("val", "Value", default="1"),
TableColumnConfig("next_at", "Next @", default="", read_only=True),
)
def __init__(self):
@ -42,17 +35,20 @@ class TaskGenEditor(TableEditor):
self.get_item_callback = get_generator
def refresh_items(self):
generated = generate_needed_tasks()
if num := len(generated):
self.notify(f"created {num} tasks")
items = get_generators()
for item in items:
self.table.add_row(
str(item.id), item.template, item.type, json.loads(item.config)["val"]
str(item.id),
item.template,
item.type,
json.loads(item.config)["val"],
str(item.next_at()),
)
def run():
app = TaskGenEditor()
app.run()
if __name__ == "__main__":
run()