load configs from TOML
This commit is contained in:
parent
92cc3c5b40
commit
f8b5399f2f
@ -1,14 +1,29 @@
|
|||||||
import tomlkit
|
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):
|
def get_enum(name):
|
||||||
with open("tt.toml", "r") as f:
|
for enum in _load_config().get("enums", []):
|
||||||
config = tomlkit.load(f)
|
|
||||||
|
|
||||||
for enum in config.get("enums", []):
|
|
||||||
if enum["name"] == name:
|
if enum["name"] == name:
|
||||||
return {v["value"]: v for v in enum["values"]}
|
return {v["value"]: v for v in enum["values"]}
|
||||||
|
|
||||||
raise ValueError(f"no such enum! {name}")
|
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")
|
STATUSES = get_enum("status")
|
||||||
PROJECTS = get_enum("projects")
|
PROJECTS = get_enum("projects")
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
from ..db import db, TaskGenerator, GeneratorType
|
from ..db import db, TaskGenerator
|
||||||
from .tasks import add_task
|
from .tasks import add_task
|
||||||
|
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ def get_generators() -> list[TaskGenerator]:
|
|||||||
|
|
||||||
def add_generator(
|
def add_generator(
|
||||||
template: str,
|
template: str,
|
||||||
type: GeneratorType,
|
type: str,
|
||||||
val: str,
|
val: str,
|
||||||
) -> TaskGenerator:
|
) -> TaskGenerator:
|
||||||
# JSON for future expansion
|
# JSON for future expansion
|
||||||
|
@ -20,10 +20,6 @@ db = SqliteDatabase(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
class GeneratorType(Enum):
|
|
||||||
DAYS_BETWEEN = "days-btwn"
|
|
||||||
MONTHLY = "monthly"
|
|
||||||
|
|
||||||
|
|
||||||
class BaseModel(Model):
|
class BaseModel(Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -76,11 +72,11 @@ class TaskGenerator(BaseModel):
|
|||||||
today = date.today()
|
today = date.today()
|
||||||
val = int(json.loads(self.config)["val"])
|
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:
|
if not self.last_generated_at:
|
||||||
return today
|
return today
|
||||||
return self.last_generated_at + timedelta(days=val)
|
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
|
year, month, day = today.year, today.month, today.day
|
||||||
day_of_month = val
|
day_of_month = val
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ from textual.widgets import (
|
|||||||
)
|
)
|
||||||
from textual.containers import Container
|
from textual.containers import Container
|
||||||
|
|
||||||
|
from .. import config
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
remove_rich_tag,
|
remove_rich_tag,
|
||||||
filter_to_string,
|
filter_to_string,
|
||||||
@ -16,11 +17,11 @@ from ..utils import (
|
|||||||
from .keymodal import KeyModal
|
from .keymodal import KeyModal
|
||||||
from .modals import ChoiceModal, DateModal, ConfirmModal
|
from .modals import ChoiceModal, DateModal, ConfirmModal
|
||||||
|
|
||||||
|
|
||||||
class NotifyValidationError(Exception):
|
class NotifyValidationError(Exception):
|
||||||
"""will notify and continue if raised"""
|
"""will notify and continue if raised"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TableColumnConfig:
|
class TableColumnConfig:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -42,7 +43,7 @@ class TableColumnConfig:
|
|||||||
def preprocess(self, val):
|
def preprocess(self, val):
|
||||||
return val # no-op
|
return val # no-op
|
||||||
|
|
||||||
def start_change(self, app, current_value):
|
def start_change(self, app, current_value):
|
||||||
if current_value.endswith(ELLIPSIS):
|
if current_value.endswith(ELLIPSIS):
|
||||||
app.action_start_edit()
|
app.action_start_edit()
|
||||||
else:
|
else:
|
||||||
@ -51,13 +52,7 @@ class TableColumnConfig:
|
|||||||
|
|
||||||
|
|
||||||
class EnumColumnConfig(TableColumnConfig):
|
class EnumColumnConfig(TableColumnConfig):
|
||||||
def __init__(
|
def __init__(self, field: str, display_name: str, enum, **kwargs):
|
||||||
self,
|
|
||||||
field: str,
|
|
||||||
display_name: str,
|
|
||||||
enum,
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
super().__init__(field, display_name, **kwargs)
|
super().__init__(field, display_name, **kwargs)
|
||||||
self.enum = enum
|
self.enum = enum
|
||||||
|
|
||||||
@ -65,16 +60,13 @@ class EnumColumnConfig(TableColumnConfig):
|
|||||||
if val in self.enum:
|
if val in self.enum:
|
||||||
return val
|
return val
|
||||||
else:
|
else:
|
||||||
raise NotifyValidationError(
|
raise NotifyValidationError(f"Invalid value {val}. Use: {list(self.enum)}")
|
||||||
f"Invalid value {val}. Use: {list(self.enum)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def start_change(self, app, current_value):
|
def start_change(self, app, current_value):
|
||||||
# a weird hack? pass app here and correct modal gets pushed
|
# a weird hack? pass app here and correct modal gets pushed
|
||||||
app.push_screen(ChoiceModal(self.enum, current_value), app.apply_change)
|
app.push_screen(ChoiceModal(self.enum, current_value), app.apply_change)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DateColumnConfig(TableColumnConfig):
|
class DateColumnConfig(TableColumnConfig):
|
||||||
def preprocess(self, val):
|
def preprocess(self, val):
|
||||||
try:
|
try:
|
||||||
@ -84,9 +76,10 @@ class DateColumnConfig(TableColumnConfig):
|
|||||||
|
|
||||||
def start_change(self, app, current_value):
|
def start_change(self, app, current_value):
|
||||||
app.push_screen(DateModal(current_value), app.apply_change)
|
app.push_screen(DateModal(current_value), app.apply_change)
|
||||||
ELLIPSIS = "…"
|
|
||||||
|
|
||||||
|
|
||||||
|
ELLIPSIS = "…"
|
||||||
|
|
||||||
|
|
||||||
class TableEditor(App):
|
class TableEditor(App):
|
||||||
CSS = """
|
CSS = """
|
||||||
@ -167,6 +160,30 @@ class TableEditor(App):
|
|||||||
self.saved_cursor_pos = (1, 0)
|
self.saved_cursor_pos = (1, 0)
|
||||||
self.save_on_move = None
|
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):
|
def compose(self):
|
||||||
self.header = Header()
|
self.header = Header()
|
||||||
self.table = DataTable()
|
self.table = DataTable()
|
||||||
@ -187,7 +204,7 @@ class TableEditor(App):
|
|||||||
yield self.right_status
|
yield self.right_status
|
||||||
|
|
||||||
def on_mount(self):
|
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.table.add_columns(*column_names)
|
||||||
self.refresh_data(restore_cursor=False)
|
self.refresh_data(restore_cursor=False)
|
||||||
|
|
||||||
@ -252,13 +269,13 @@ class TableEditor(App):
|
|||||||
# the new item should use the selected value if filtered
|
# the new item should use the selected value if filtered
|
||||||
# prepopulate with either the field default or the current filter
|
# prepopulate with either the field default or the current filter
|
||||||
prepopulated = {}
|
prepopulated = {}
|
||||||
for fc in self.TABLE_CONFIG:
|
for fc in self.table_config:
|
||||||
if fc.read_only:
|
if fc.read_only:
|
||||||
continue
|
continue
|
||||||
val = self.filters.get(fc.field, fc.default)
|
val = self.filters.get(fc.field, fc.default)
|
||||||
if val is not None:
|
if val is not None:
|
||||||
if "," in val:
|
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
|
prepopulated[fc.field] = val
|
||||||
|
|
||||||
new_item = self.add_item_callback(**prepopulated)
|
new_item = self.add_item_callback(**prepopulated)
|
||||||
@ -268,7 +285,7 @@ class TableEditor(App):
|
|||||||
|
|
||||||
def _active_column_config(self):
|
def _active_column_config(self):
|
||||||
cur_col = self.table.cursor_column
|
cur_col = self.table.cursor_column
|
||||||
return self.TABLE_CONFIG[cur_col]
|
return self.table_config[cur_col]
|
||||||
|
|
||||||
def _active_item_id(self):
|
def _active_item_id(self):
|
||||||
return int(self.table.get_cell_at((self.table.cursor_row, 0)))
|
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):
|
def apply_change(self, new_value):
|
||||||
row, col = self.saved_cursor_pos
|
row, col = self.saved_cursor_pos
|
||||||
item_id = int(self.table.get_cell_at((row, 0)))
|
item_id = int(self.table.get_cell_at((row, 0)))
|
||||||
cconf = self.TABLE_CONFIG[col]
|
cconf = self.table_config[col]
|
||||||
field = cconf.field
|
field = cconf.field
|
||||||
update_data = {}
|
update_data = {}
|
||||||
|
|
||||||
|
@ -7,33 +7,16 @@ from ..controller.generators import (
|
|||||||
update_generator,
|
update_generator,
|
||||||
generate_needed_tasks,
|
generate_needed_tasks,
|
||||||
)
|
)
|
||||||
from ..db import GeneratorType
|
from .editor import ( TableEditor, )
|
||||||
from .editor import (
|
|
||||||
TableEditor,
|
|
||||||
TableColumnConfig,
|
|
||||||
EnumColumnConfig,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TaskGenEditor(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):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.update_item_callback = update_generator
|
self.update_item_callback = update_generator
|
||||||
self.add_item_callback = add_generator
|
self.add_item_callback = add_generator
|
||||||
self.get_item_callback = get_generator
|
self.get_item_callback = get_generator
|
||||||
|
self.table_config = self._load_config("recurring")
|
||||||
|
|
||||||
def refresh_items(self):
|
def refresh_items(self):
|
||||||
generated = generate_needed_tasks()
|
generated = generate_needed_tasks()
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
from textual.widgets import Input
|
from textual.widgets import Input
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from .. import config
|
from .. import config
|
||||||
from ..controller.tasks import (
|
from ..controller.tasks import (
|
||||||
@ -17,27 +16,11 @@ from ..utils import (
|
|||||||
)
|
)
|
||||||
from .editor import (
|
from .editor import (
|
||||||
TableEditor,
|
TableEditor,
|
||||||
TableColumnConfig,
|
|
||||||
EnumColumnConfig,
|
|
||||||
DateColumnConfig,
|
|
||||||
ELLIPSIS,
|
ELLIPSIS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TT(TableEditor):
|
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 = [
|
BINDINGS = [
|
||||||
# saved views
|
# saved views
|
||||||
@ -51,6 +34,7 @@ class TT(TableEditor):
|
|||||||
self.update_item_callback = update_task
|
self.update_item_callback = update_task
|
||||||
self.add_item_callback = add_task
|
self.add_item_callback = add_task
|
||||||
self.get_item_callback = get_task
|
self.get_item_callback = get_task
|
||||||
|
self.table_config = self._load_config("tasks")
|
||||||
|
|
||||||
def _load_view(self, name):
|
def _load_view(self, name):
|
||||||
try:
|
try:
|
||||||
|
65
tt.toml
65
tt.toml
@ -19,3 +19,68 @@ values = [
|
|||||||
{ value = "TT", color = "#00ff00"},
|
{ 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
|
||||||
|
Loading…
Reference in New Issue
Block a user