load configs from TOML
This commit is contained in:
		
							parent
							
								
									92cc3c5b40
								
							
						
					
					
						commit
						f8b5399f2f
					
				
					 7 changed files with 127 additions and 67 deletions
				
			
		|  | @ -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") | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 = {} | ||||
| 
 | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
|  | @ -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
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								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 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue