load configs from TOML

This commit is contained in:
jpt 2025-04-11 08:20:13 -05:00
parent 92cc3c5b40
commit f8b5399f2f
7 changed files with 127 additions and 67 deletions

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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 = {}

View File

@ -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()

View File

@ -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:

65
tt.toml
View File

@ -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