| 1 | import datetime
|
|---|
| 2 | import psycopg2
|
|---|
| 3 |
|
|---|
| 4 | DB_CONFIG = {
|
|---|
| 5 | "host": "localhost",
|
|---|
| 6 | "port": 5432,
|
|---|
| 7 | "dbname": "frruas_db",
|
|---|
| 8 | "user": "frruas_user",
|
|---|
| 9 | "password": "frruas_pass",
|
|---|
| 10 | "options": "-c search_path=project,public",
|
|---|
| 11 | }
|
|---|
| 12 |
|
|---|
| 13 |
|
|---|
| 14 | def get_connection():
|
|---|
| 15 | return psycopg2.connect(**DB_CONFIG)
|
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 | def pick_from_list(prompt, items):
|
|---|
| 19 | """Display items as a numbered list, return selected 0-based index or None."""
|
|---|
| 20 | if not items:
|
|---|
| 21 | print(" (no items)")
|
|---|
| 22 | return None
|
|---|
| 23 | for i, item in enumerate(items, start=1):
|
|---|
| 24 | print(f" {i}. {item}")
|
|---|
| 25 | print(f" 0. Cancel / Go back")
|
|---|
| 26 | while True:
|
|---|
| 27 | choice = input(prompt).strip()
|
|---|
| 28 | if choice == "0" or choice == "":
|
|---|
| 29 | return None
|
|---|
| 30 | try:
|
|---|
| 31 | idx = int(choice) - 1
|
|---|
| 32 | if 0 <= idx < len(items):
|
|---|
| 33 | return idx
|
|---|
| 34 | except ValueError:
|
|---|
| 35 | pass
|
|---|
| 36 | print(" Invalid choice. Please try again.")
|
|---|
| 37 |
|
|---|
| 38 |
|
|---|
| 39 | def print_table(headers, rows):
|
|---|
| 40 | """Print a formatted ASCII table."""
|
|---|
| 41 | if not rows:
|
|---|
| 42 | print(" (no data)")
|
|---|
| 43 | return
|
|---|
| 44 | str_rows = [[str(v) for v in row] for row in rows]
|
|---|
| 45 | widths = [len(h) for h in headers]
|
|---|
| 46 | for row in str_rows:
|
|---|
| 47 | for i, val in enumerate(row):
|
|---|
| 48 | widths[i] = max(widths[i], len(val))
|
|---|
| 49 | header_line = " | ".join(h.ljust(widths[i]) for i, h in enumerate(headers))
|
|---|
| 50 | print(f" {header_line}")
|
|---|
| 51 | print(f" {'-+-'.join('-' * w for w in widths)}")
|
|---|
| 52 | for row in str_rows:
|
|---|
| 53 | print(f" {' | '.join(val.ljust(widths[i]) for i, val in enumerate(row))}")
|
|---|
| 54 |
|
|---|
| 55 |
|
|---|
| 56 | def input_date(prompt):
|
|---|
| 57 | """Prompt until valid YYYY-MM-DD entered. Returns datetime.date."""
|
|---|
| 58 | while True:
|
|---|
| 59 | val = input(prompt).strip()
|
|---|
| 60 | try:
|
|---|
| 61 | return datetime.datetime.strptime(val, "%Y-%m-%d").date()
|
|---|
| 62 | except ValueError:
|
|---|
| 63 | print(" Invalid date format. Please use YYYY-MM-DD.")
|
|---|
| 64 |
|
|---|
| 65 |
|
|---|
| 66 | def input_time(prompt):
|
|---|
| 67 | """Prompt until valid HH:MM entered. Returns datetime.time."""
|
|---|
| 68 | while True:
|
|---|
| 69 | val = input(prompt).strip()
|
|---|
| 70 | try:
|
|---|
| 71 | return datetime.datetime.strptime(val, "%H:%M").time()
|
|---|
| 72 | except ValueError:
|
|---|
| 73 | print(" Invalid time format. Please use HH:MM.")
|
|---|