Compare commits

...

2 Commits

Author SHA1 Message Date
jpt
4ed319e22e ruff lint 2025-05-03 18:31:42 -05:00
jpt
8ae7d5ad2d backup command 2025-05-03 18:31:36 -05:00
10 changed files with 53 additions and 23 deletions

View File

@ -1,10 +1,12 @@
import typer
import httpx
import lxml.html
import sqlite3
from typing_extensions import Annotated
from .db import initialize_db
from .tui.things import run as things_tui
#from .tui.overview import run as overview_tui
# from .tui.overview import run as overview_tui
from .tui.recurring import run as recurring_tui
from .controller.things import add_thing
@ -40,6 +42,7 @@ def new(
add_thing(name, category, due)
typer.echo("Created new thing!")
@app.command()
def table(
view: Annotated[str, typer.Option("-v", "--view", help="saved view")] = "default",
@ -60,5 +63,23 @@ def overview():
overview_tui()
@app.command()
def backup(backup_path: str):
"""
Perform a SQLite backup using the .backup dot command
"""
from tt.db import db
conn = db.connection()
backup_conn = None
try:
backup_conn = sqlite3.connect(backup_path)
conn.backup(backup_conn)
finally:
if backup_conn:
backup_conn.close()
if __name__ == "__main__":
app()

View File

@ -2,7 +2,7 @@ import tomlkit
from xdg_base_dirs import xdg_config_home
# This code implements a singleton-like pattern.
#
#
# The global variable _raw_config contains the cached config loaded
# from a TOML config file.
#
@ -13,12 +13,13 @@ from xdg_base_dirs import xdg_config_home
# or not.
#
# Eventually, this approach could also enable a live-reload function.
#
#
# A command could be introduced that would reset _raw_config to None,
# and the next read would reload the fresh data.
_raw_config = None
def _load_config():
global _raw_config
if not _raw_config:
@ -35,6 +36,7 @@ def get_enum(name):
raise ValueError(f"no such enum! {name}")
def get_view(name):
for view in _load_config().get("views", []):
if view["name"] == name:
@ -42,6 +44,7 @@ def get_view(name):
raise ValueError(f"no such view! {name}")
def get_column(name):
if name in ("id", "type"):
return {"field_name": name, "display_name": name, "read_only": True}
@ -52,6 +55,7 @@ def get_column(name):
raise ValueError(f"no such column! {name}")
# Valid statuses & projects are read dynamically from the user's config.
STATUSES = get_enum("status")
PROJECTS = get_enum("projects")

View File

@ -1,10 +1,8 @@
SPECIAL_DATES_PIECES = {
"future": (3000,1,1),
"unclassified": (1999,1,1),
"future": (3000, 1, 1),
"unclassified": (1999, 1, 1),
}
SPECIAL_DATES_DISPLAY = {
"3000-01-01": "[#333333]future[/]",
"1999-01-01": "[#cccccc]unclassified[/]",
}

View File

@ -17,9 +17,7 @@ def add_thing(
Returns the created Thing instance.
"""
with db.atomic():
thing = Thing.create(
type=type, data=kwargs
)
thing = Thing.create(type=type, data=kwargs)
return thing
@ -80,7 +78,7 @@ def get_things(
if search_text:
# TODO: which fields are searchable should by dynamic
query = query.where(fn.Lower(Thing.data['text']).contains(search_text.lower()))
query = query.where(fn.Lower(Thing.data["text"]).contains(search_text.lower()))
for param, val in filters.items():
if val is not None:

View File

@ -12,7 +12,7 @@ from peewee import (
from playhouse.sqlite_ext import JSONField
# This module defines the core data types.
#
#
db = SqliteDatabase(
xdg_data_home() / "tt/tt.db",

View File

@ -5,6 +5,7 @@ from ..utils import (
)
from .modals import ChoiceModal, DateModal
class NotifyValidationError(Exception):
"""will notify and continue if raised"""
@ -82,7 +83,6 @@ class DateColumnConfig(TableColumnConfig):
app.push_screen(DateModal(current_value), app.apply_change)
def get_col_cls(field_type):
return {
"text": TableColumnConfig,

View File

@ -18,7 +18,6 @@ from .modals import ConfirmModal
from .columns import get_col_cls, NotifyValidationError
class TableEditor(App):
CSS = """
#footer {
@ -132,7 +131,7 @@ class TableEditor(App):
self.filters = self.view["filters"]
self.sort_string = self.view["sort"]
self.defaults = self.view["defaults"]
def _db_item_to_row(self, item):
"""
Convert db item to a row for display.
@ -223,9 +222,12 @@ class TableEditor(App):
def action_delete_item(self):
if self.table.cursor_column == 0:
current_value = self.table.get_cell_at(
(self.table.cursor_row, 1) # assumes col 1 is name
(self.table.cursor_row, 1) # assumes col 1 is name
)
self.push_screen(
ConfirmModal(f"delete [green]{current_value}[/]?"),
self._delete_item_callback,
)
self.push_screen(ConfirmModal(f"delete [green]{current_value}[/]?"), self._delete_item_callback)
def _delete_item_callback(self, confirm):
if confirm and self.table.cursor_column == 0:

View File

@ -92,20 +92,25 @@ class ChoiceModal(ModalScreen):
+ get_color_enum(e.value, self._enum),
classes="selected" if idx == self.sel_idx else "",
)
yield Label("""(h/j/k/l) move (0-9) quick select
(enter) confirm (esc) quit""", classes="hints")
yield Label(
"""(h/j/k/l) move (0-9) quick select
(enter) confirm (esc) quit""",
classes="hints",
)
def _move_cursor(self, dir):
labels = self.query(Label)
# reset old
labels[self.sel_idx].update(
f" {self.sel_idx} " + get_color_enum(self.enum_by_idx[self.sel_idx], self._enum)
f" {self.sel_idx} "
+ get_color_enum(self.enum_by_idx[self.sel_idx], self._enum)
)
# move cursor
self.sel_idx = (self.sel_idx + dir) % len(self._enum)
# reset new
labels[self.sel_idx].update(
f"> {self.sel_idx} " + get_color_enum(self.enum_by_idx[self.sel_idx], self._enum)
f"> {self.sel_idx} "
+ get_color_enum(self.enum_by_idx[self.sel_idx], self._enum)
)
def action_cursor_down(self):

View File

@ -7,7 +7,9 @@ from ..controller.generators import (
update_generator,
generate_needed_things,
)
from .editor import ( TableEditor, )
from .editor import (
TableEditor,
)
class GenEditor(TableEditor):

View File

@ -8,7 +8,6 @@ from .editor import TableEditor
class ThingTable(TableEditor):
# BINDINGS = [
# # saved views
# ("ctrl+s", "save_view", "save current view"),
@ -67,6 +66,7 @@ class ThingTable(TableEditor):
else:
return None
def run(default_view):
app = ThingTable(default_view)
app.run()