diff --git a/src/tt/config.py b/src/tt/config.py index 8cede64..5ea50c5 100644 --- a/src/tt/config.py +++ b/src/tt/config.py @@ -1,14 +1,29 @@ import tomlkit +_raw_config = None + +def _load_config(): + global _raw_config + if not _raw_config: + with open("tt.toml", "r") as f: + _raw_config = tomlkit.load(f) + return _raw_config + + def get_enum(name): - with open("tt.toml", "r") as f: - config = tomlkit.load(f) - - for enum in config.get("enums", []): + for enum in _load_config().get("enums", []): if enum["name"] == name: return {v["value"]: v for v in enum["values"]} raise ValueError(f"no such enum! {name}") +def get_view_columns(name): + for view in _load_config().get("views", []): + if view["name"] == name: + return view["columns"] + + raise ValueError(f"no such view! {name}") + + STATUSES = get_enum("status") PROJECTS = get_enum("projects") diff --git a/src/tt/controller/generators.py b/src/tt/controller/generators.py index e066567..0838a7b 100644 --- a/src/tt/controller/generators.py +++ b/src/tt/controller/generators.py @@ -1,6 +1,6 @@ import json from datetime import date, timedelta -from ..db import db, TaskGenerator, GeneratorType +from ..db import db, TaskGenerator from .tasks import add_task @@ -15,7 +15,7 @@ def get_generators() -> list[TaskGenerator]: def add_generator( template: str, - type: GeneratorType, + type: str, val: str, ) -> TaskGenerator: # JSON for future expansion diff --git a/src/tt/db.py b/src/tt/db.py index a67c51e..69b042b 100644 --- a/src/tt/db.py +++ b/src/tt/db.py @@ -20,10 +20,6 @@ db = SqliteDatabase( }, ) -class GeneratorType(Enum): - DAYS_BETWEEN = "days-btwn" - MONTHLY = "monthly" - class BaseModel(Model): class Meta: @@ -76,11 +72,11 @@ class TaskGenerator(BaseModel): today = date.today() val = int(json.loads(self.config)["val"]) - if self.type == GeneratorType.DAYS_BETWEEN.value: + if self.type == "days-btwn": if not self.last_generated_at: return today return self.last_generated_at + timedelta(days=val) - elif self.type == GeneratorType.MONTHLY.value: + elif self.type == "monthly": year, month, day = today.year, today.month, today.day day_of_month = val diff --git a/src/tt/tui/editor.py b/src/tt/tui/editor.py index 4118981..a6e5ad7 100644 --- a/src/tt/tui/editor.py +++ b/src/tt/tui/editor.py @@ -8,6 +8,7 @@ from textual.widgets import ( ) from textual.containers import Container +from .. import config from ..utils import ( remove_rich_tag, filter_to_string, @@ -16,11 +17,11 @@ from ..utils import ( from .keymodal import KeyModal from .modals import ChoiceModal, DateModal, ConfirmModal + class NotifyValidationError(Exception): """will notify and continue if raised""" - class TableColumnConfig: def __init__( self, @@ -42,7 +43,7 @@ class TableColumnConfig: def preprocess(self, val): return val # no-op - def start_change(self, app, current_value): + def start_change(self, app, current_value): if current_value.endswith(ELLIPSIS): app.action_start_edit() else: @@ -51,13 +52,7 @@ class TableColumnConfig: class EnumColumnConfig(TableColumnConfig): - def __init__( - self, - field: str, - display_name: str, - enum, - **kwargs - ): + def __init__(self, field: str, display_name: str, enum, **kwargs): super().__init__(field, display_name, **kwargs) self.enum = enum @@ -65,16 +60,13 @@ class EnumColumnConfig(TableColumnConfig): if val in self.enum: return val else: - raise NotifyValidationError( - f"Invalid value {val}. Use: {list(self.enum)}" - ) + raise NotifyValidationError(f"Invalid value {val}. Use: {list(self.enum)}") def start_change(self, app, current_value): # a weird hack? pass app here and correct modal gets pushed app.push_screen(ChoiceModal(self.enum, current_value), app.apply_change) - class DateColumnConfig(TableColumnConfig): def preprocess(self, val): try: @@ -84,9 +76,10 @@ class DateColumnConfig(TableColumnConfig): def start_change(self, app, current_value): app.push_screen(DateModal(current_value), app.apply_change) -ELLIPSIS = "…" +ELLIPSIS = "…" + class TableEditor(App): CSS = """ @@ -167,6 +160,30 @@ class TableEditor(App): self.saved_cursor_pos = (1, 0) self.save_on_move = None + def _load_config(self, name): + # column config + columns = [] + for col in config.get_view_columns(name): + field_type = col.get("field_type", "text") + field_cls = { + "text": TableColumnConfig, + "enum": EnumColumnConfig, + "date": DateColumnConfig, + }[field_type] + field_name = col["field_name"] + display_name = col.get("display_name", field_name.title()) + default = col.get("default") + extras = {} + enum = col.get("enum") + if enum: + extras["enum"] = config.get_enum(enum) + if col.get("editor"): + extras["enable_editor"] = True + + cc = field_cls(field_name, display_name, default=default, **extras) + columns.append(cc) + return columns + def compose(self): self.header = Header() self.table = DataTable() @@ -187,7 +204,7 @@ class TableEditor(App): yield self.right_status def on_mount(self): - column_names = [c.display_name for c in self.TABLE_CONFIG] + column_names = [c.display_name for c in self.table_config] self.table.add_columns(*column_names) self.refresh_data(restore_cursor=False) @@ -252,13 +269,13 @@ class TableEditor(App): # the new item should use the selected value if filtered # prepopulate with either the field default or the current filter prepopulated = {} - for fc in self.TABLE_CONFIG: + for fc in self.table_config: if fc.read_only: continue val = self.filters.get(fc.field, fc.default) if val is not None: if "," in val: - val = val.split(",")[0] # TODO: fix hack for enums + val = val.split(",")[0] # TODO: fix hack for enums prepopulated[fc.field] = val new_item = self.add_item_callback(**prepopulated) @@ -268,7 +285,7 @@ class TableEditor(App): def _active_column_config(self): cur_col = self.table.cursor_column - return self.TABLE_CONFIG[cur_col] + return self.table_config[cur_col] def _active_item_id(self): return int(self.table.get_cell_at((self.table.cursor_row, 0))) @@ -412,7 +429,7 @@ class TableEditor(App): def apply_change(self, new_value): row, col = self.saved_cursor_pos item_id = int(self.table.get_cell_at((row, 0))) - cconf = self.TABLE_CONFIG[col] + cconf = self.table_config[col] field = cconf.field update_data = {} diff --git a/src/tt/tui/recurring.py b/src/tt/tui/recurring.py index 8723ef1..9d45a47 100644 --- a/src/tt/tui/recurring.py +++ b/src/tt/tui/recurring.py @@ -7,33 +7,16 @@ from ..controller.generators import ( update_generator, generate_needed_tasks, ) -from ..db import GeneratorType -from .editor import ( - TableEditor, - TableColumnConfig, - EnumColumnConfig, -) +from .editor import ( TableEditor, ) class TaskGenEditor(TableEditor): - TABLE_CONFIG = ( - TableColumnConfig("id", "ID"), - TableColumnConfig("template", "Template", default="recur {val}"), - EnumColumnConfig( - "type", - "Type", - default=GeneratorType.DAYS_BETWEEN.value, - enum=GeneratorType, - ), - TableColumnConfig("val", "Value", default="1"), - TableColumnConfig("next_at", "Next @", read_only=True), - ) - def __init__(self): super().__init__() self.update_item_callback = update_generator self.add_item_callback = add_generator self.get_item_callback = get_generator + self.table_config = self._load_config("recurring") def refresh_items(self): generated = generate_needed_tasks() diff --git a/src/tt/tui/tasks.py b/src/tt/tui/tasks.py index 3103cc9..0564213 100644 --- a/src/tt/tui/tasks.py +++ b/src/tt/tui/tasks.py @@ -1,6 +1,5 @@ import json from textual.widgets import Input -from datetime import datetime from .. import config from ..controller.tasks import ( @@ -17,27 +16,11 @@ from ..utils import ( ) from .editor import ( TableEditor, - TableColumnConfig, - EnumColumnConfig, - DateColumnConfig, ELLIPSIS, ) class TT(TableEditor): - TABLE_CONFIG = ( - TableColumnConfig("id", "ID"), - TableColumnConfig("text", "Task", default="new task", enable_editor=True), - EnumColumnConfig( - "status", - "Status", - enum=config.STATUSES, - default="zero", - ), - TableColumnConfig("type", "Type", default=""), - DateColumnConfig("due", "Due", default=""), - TableColumnConfig("category", "Category", default="main"), - ) BINDINGS = [ # saved views @@ -51,6 +34,7 @@ class TT(TableEditor): self.update_item_callback = update_task self.add_item_callback = add_task self.get_item_callback = get_task + self.table_config = self._load_config("tasks") def _load_view(self, name): try: diff --git a/tt.toml b/tt.toml index 3d9b289..803a649 100644 --- a/tt.toml +++ b/tt.toml @@ -19,3 +19,68 @@ values = [ { value = "TT", color = "#00ff00"}, ] +[[enums]] +name = "recurrences" +values = [ + { value = "days-btwn" }, + { value = "monthly" }, +] + +[[views]] +name = "tasks" + +[[views.columns]] +field_name = "id" +read_only = true + +[[views.columns]] +field_name = "text" +display_name = "Task" +default = "new taskz" +editor = true + +[[views.columns]] +field_name = "status" +field_type = "enum" +enum = "status" +default = "zero" + +[[views.columns]] +field_name = "type" +default = "" + +[[views.columns]] +field_name = "due" +field_type = "date" +default = "" + +[[views.columns]] +field_name = "category" +default = "" + +[[views]] +name = "recurring" + +[[views.columns]] +field_name = "id" +read_only = true + +[[views.columns]] +field_name = "template" +default = "recur {val}" + +[[views.columns]] +field_name = "type" +default = "days-btwn" +field_type = "enum" +enum = "generators" + +[[views.columns]] +field_name = "val" +display_name = "Value" +default = "1" + +[[views.columns]] +field_name = "next_at" +display_name = "Next @" +read_only = true