Replace content CLI with Textual TUI
This commit is contained in:
1 parent
0d58ddd430
commit
737700a9a8
28 files changed
+2403
-167
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user