2023-12-09 23:55:19 +00:00
|
|
|
#!/usr/bin/env python3
|
2023-12-10 00:11:02 +00:00
|
|
|
import curses
|
2023-12-09 23:55:19 +00:00
|
|
|
import datetime
|
|
|
|
from os import path
|
|
|
|
from getpass import getuser
|
|
|
|
import os
|
|
|
|
from datetime import timezone
|
|
|
|
import sqlite3
|
|
|
|
import sys
|
|
|
|
from typing import Optional, Tuple, TypeVar
|
|
|
|
|
|
|
|
BOTANY_DIR = ".botany"
|
2023-12-10 00:11:02 +00:00
|
|
|
MIN_SCREEN_WIDTH = 80
|
2023-12-09 23:55:19 +00:00
|
|
|
|
|
|
|
dt = datetime.datetime
|
|
|
|
|
|
|
|
def now() -> dt:
|
|
|
|
return dt.now(timezone.utc)
|
|
|
|
|
|
|
|
# flavor dict keys
|
|
|
|
# - color
|
|
|
|
# - rarity
|
|
|
|
# - species
|
|
|
|
# - mutation
|
|
|
|
PLOT_SCHEMA = """
|
|
|
|
CREATE TABLE IF NOT EXISTS plot (
|
|
|
|
-- Each row is a plant
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
created TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M', 'now', 'localtime')),
|
|
|
|
watered TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M', 'now', 'localtime')),
|
|
|
|
generation INTEGER,
|
|
|
|
flavor JSON
|
|
|
|
)
|
|
|
|
"""
|
|
|
|
|
|
|
|
VISITORS_SCHEMA = """
|
|
|
|
CREATE TABLE IF NOT EXISTS visitors (
|
|
|
|
-- Each row is a visit from another user
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
name TEXT,
|
|
|
|
when TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M', 'now', 'localtime')),
|
|
|
|
)
|
|
|
|
"""
|
|
|
|
|
|
|
|
# TODO code for generating a global garden database (finds all most recent plants for users on system and fills a sqlite3 db with computed values)
|
|
|
|
|
|
|
|
def mkdir(p: str) -> Optional[Exception]:
|
|
|
|
if not path.isdir(p):
|
|
|
|
try:
|
|
|
|
os.makedirs(p)
|
|
|
|
except Exception as e:
|
|
|
|
return Exception(f"failed to create {p}: {e}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def mkdb(p: str, sql: str) -> Optional[Exception]:
|
|
|
|
try:
|
|
|
|
conn = sqlite3.connect(p)
|
|
|
|
c = conn.cursor()
|
|
|
|
c.execute(sql)
|
|
|
|
conn.close()
|
|
|
|
except Exception as e:
|
|
|
|
return Exception(f"failed to initialize {p}: {e}")
|
|
|
|
return None
|
|
|
|
|
|
|
|
def setup() -> Optional[Exception]:
|
|
|
|
bdir = path.expanduser(path.join("~", BOTANY_DIR))
|
|
|
|
e = mkdir(bdir)
|
|
|
|
if e is not None:
|
|
|
|
return e
|
|
|
|
|
|
|
|
dbdir = path.join(bdir, "db")
|
|
|
|
e = mkdir(dbdir)
|
|
|
|
if e is not None:
|
|
|
|
return e
|
|
|
|
|
2023-12-09 23:58:40 +00:00
|
|
|
e = mkdb(path.join(bdir, "db/plot.db"), PLOT_SCHEMA)
|
2023-12-09 23:55:19 +00:00
|
|
|
if e is not None:
|
|
|
|
return e
|
|
|
|
|
2023-12-09 23:58:40 +00:00
|
|
|
e = mkdb(path.join(bdir, "db/visitors.db"), VISITORS_SCHEMA)
|
2023-12-09 23:55:19 +00:00
|
|
|
if e is not None:
|
|
|
|
return e
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
def main() -> Optional[Exception]:
|
|
|
|
username = getuser()
|
|
|
|
|
|
|
|
e = setup()
|
|
|
|
if e is not None:
|
|
|
|
return e
|
|
|
|
|
2023-12-10 00:11:02 +00:00
|
|
|
# TODO rug sweep curses stuff
|
|
|
|
parentwin = curses.initscr()
|
|
|
|
parentwin.keypad(True)
|
|
|
|
curses.noecho()
|
|
|
|
curses.raw()
|
|
|
|
if curses.has_colors():
|
|
|
|
curses.start_color()
|
|
|
|
try:
|
|
|
|
curses.curs_set(0)
|
|
|
|
except curses.error:
|
|
|
|
# Not all terminals support this functionality.
|
|
|
|
# When the error is ignored the screen will be slightly uglier but functional
|
|
|
|
# so we ignore this error for terminal compatibility.
|
|
|
|
pass
|
|
|
|
if curses.COLS < MIN_SCREEN_WIDTH:
|
|
|
|
return Exception("the terminal window is too narrow")
|
|
|
|
|
2023-12-09 23:55:19 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
ret = 0
|
|
|
|
e = main()
|
|
|
|
if e is not None:
|
2023-12-10 00:11:02 +00:00
|
|
|
print(e, file=sys.stderr)
|
2023-12-09 23:55:19 +00:00
|
|
|
ret = 1
|
|
|
|
sys.exit(ret)
|