Replace content CLI with Textual TUI

This commit is contained in:
SirBlob committed 2026-11-01 20:46:34 -05:00
1 parent 0d58ddd430
commit 737700a9a8
28 files changed
+2403 -167

No files matched your search

+204
View File
@@ -0,0 +1,204 @@
from dataclasses import dataclass, field
import jsonfile
from fields import FieldSpec, get_path
@dataclass
class Collection:
id: str
title: str
file: str
fields: list[FieldSpec]
columns: list[tuple[str, str]] = field(default_factory=list)
single: bool = False
@property
def title_field(self) -> FieldSpec:
return self.fields[0]
PROJECT_BASICS = [
FieldSpec("name", "Name", required=True),
FieldSpec("description", "Description", "longtext"),
FieldSpec("featured", "Featured", "flag"),
FieldSpec("tags", "Tags", "tags"),
]
PROJECT_COLUMNS = [("Name", "name"), ("Featured", "featured"), ("Tags", "tags")]
PROFILE = Collection(
"profile",
"Profile",
"profile.json",
[
FieldSpec("name", "Full name", required=True),
FieldSpec("displayName", "Display name"),
FieldSpec("title", "Title"),
FieldSpec("tagline", "Tagline", "longtext"),
FieldSpec("bio", "Bio", "longtext"),
FieldSpec("email", "Email"),
FieldSpec("photo", "Portfolio photo", "image"),
FieldSpec("avatar", "Avatar", "image"),
FieldSpec("education.school", "School"),
FieldSpec("education.degree", "Degree"),
FieldSpec("education.graduation", "Graduation"),
FieldSpec("interests", "Interests", "tags"),
FieldSpec("skills", "Skills", "skill_map"),
FieldSpec("socials", "Socials", "socials"),
FieldSpec("donateUrl", "Donate url"),
FieldSpec("giteaUrl", "Gitea url"),
],
single=True,
)
EXPERIENCE = Collection(
"experience",
"Experience",
"experience.json",
[
FieldSpec("role", "Role", required=True),
FieldSpec("organization", "Organization"),
FieldSpec("school", "School"),
FieldSpec("period", "Period"),
FieldSpec("employmentType", "Employment type"),
FieldSpec("highlights", "Highlights", "lines"),
FieldSpec("tech", "Tech", "tags"),
FieldSpec("awards", "Awards", "lines"),
FieldSpec("links", "Links", "link_list"),
],
[("Role", "role"), ("Organization", "organization"), ("Period", "period")],
)
LEADERSHIP = Collection(
"leadership",
"Leadership",
"leadership.json",
[
FieldSpec("role", "Role", required=True),
FieldSpec("organization", "Organization"),
FieldSpec("url", "Url"),
],
[("Role", "role"), ("Organization", "organization")],
)
PROJECT_COLLECTIONS = [
Collection(
"opensource",
"Open Source",
"projects/opensource.json",
[*PROJECT_BASICS, FieldSpec("version", "Version"), FieldSpec("period", "Period"), FieldSpec("image", "Image", "image"), FieldSpec("links", "Links", "link_map")],
PROJECT_COLUMNS,
),
Collection(
"packages",
"Packages",
"projects/packages.json",
[*PROJECT_BASICS, FieldSpec("deprecated", "Deprecated", "flag"), FieldSpec("links", "Links", "link_map")],
PROJECT_COLUMNS,
),
Collection(
"hackathons",
"Hackathons",
"projects/hackathons.json",
[
*PROJECT_BASICS,
FieldSpec("image", "Image", "image"),
FieldSpec("event.name", "Event"),
FieldSpec("event.university", "University"),
FieldSpec("event.location", "Location"),
FieldSpec("event.year", "Year"),
FieldSpec("awards", "Awards", "awards"),
FieldSpec("links", "Links", "link_map"),
],
[("Name", "name"), ("Event", "event.name"), ("Featured", "featured")],
),
Collection(
"research",
"Research groups",
"projects/research.json",
[
FieldSpec("name", "Name", required=True),
FieldSpec("shortName", "Short name"),
FieldSpec("institution", "Institution"),
FieldSpec("role", "Role"),
FieldSpec("period", "Period"),
FieldSpec("description", "Description", "longtext"),
FieldSpec("lab.name", "Lab name"),
FieldSpec("lab.url", "Lab url"),
FieldSpec("focus", "Focus", "tags"),
FieldSpec("links", "Links", "link_list"),
],
[("Name", "name"), ("Role", "role"), ("Period", "period")],
),
Collection(
"organizations",
"Organizations",
"projects/organizations.json",
[
*PROJECT_BASICS,
FieldSpec("organization", "Roles"),
FieldSpec("awards", "Awards", "awards"),
FieldSpec("gallery", "Gallery", "images"),
FieldSpec("links", "Links", "link_map"),
FieldSpec("socials", "Socials", "socials"),
],
PROJECT_COLUMNS,
),
]
def find_collection(collection_id: str) -> Collection:
for collection in [PROFILE, EXPERIENCE, LEADERSHIP, *PROJECT_COLLECTIONS]:
if collection.id == collection_id:
return collection
raise ValueError(f"Unknown collection {collection_id}")
def load_records(collection: Collection):
return jsonfile.load(collection.file)
def save_records(collection: Collection, records) -> None:
jsonfile.save(collection.file, records)
def record_title(collection: Collection, record: dict) -> str:
return str(get_path(record, collection.title_field.path) or "untitled")
def check_required(collection: Collection, record: dict) -> None:
for spec in collection.fields:
if spec.required and not get_path(record, spec.path):
raise ValueError(f"{spec.label} is required")
def save_record(collection: Collection, index: int | None, record: dict) -> int:
check_required(collection, record)
if collection.single:
save_records(collection, record)
return 0
records = load_records(collection)
if index is None:
records.append(record)
index = len(records) - 1
else:
records[index] = record
save_records(collection, records)
return index
def remove_record(collection: Collection, index: int) -> dict:
records = load_records(collection)
removed = records.pop(index)
save_records(collection, records)
return removed
def move_record(collection: Collection, index: int, step: int) -> int:
records = load_records(collection)
target = index + step
if not 0 <= target < len(records):
return index
records[index], records[target] = records[target], records[index]
save_records(collection, records)
return target