148 lines
4.9 KiB
Python
148 lines
4.9 KiB
Python
from dataclasses import dataclass, field
|
|
|
|
SEPARATOR = "|"
|
|
|
|
FIELD_HINTS = {
|
|
"lines": "one per line",
|
|
"tags": "comma separated",
|
|
"images": "one image path per line",
|
|
"link_map": "one per line: name url",
|
|
"link_list": "one per line: label | url",
|
|
"awards": "one per line: track | place",
|
|
"socials": "one per line: name | icon | url",
|
|
"skill_map": "one per line: Category: skill, skill",
|
|
}
|
|
MULTILINE_KINDS = {"longtext", "lines", "images", "link_map", "link_list", "awards", "socials", "skill_map"}
|
|
|
|
|
|
@dataclass
|
|
class FieldSpec:
|
|
path: str
|
|
label: str
|
|
kind: str = "text"
|
|
required: bool = False
|
|
options: list[str] = field(default_factory=list)
|
|
|
|
@property
|
|
def hint(self) -> str:
|
|
return FIELD_HINTS.get(self.kind, "")
|
|
|
|
|
|
def get_path(record: dict, path: str):
|
|
value = record
|
|
for part in path.split("."):
|
|
if not isinstance(value, dict):
|
|
return None
|
|
value = value.get(part)
|
|
return value
|
|
|
|
|
|
def is_empty(value) -> bool:
|
|
return value is None or value is False or value == "" or value == [] or value == {}
|
|
|
|
|
|
def set_path(record: dict, path: str, value) -> None:
|
|
parts = path.split(".")
|
|
parents = [record]
|
|
for part in parts[:-1]:
|
|
child = parents[-1].get(part)
|
|
if not isinstance(child, dict):
|
|
child = {}
|
|
parents[-1][part] = child
|
|
parents.append(child)
|
|
|
|
if is_empty(value):
|
|
parents[-1].pop(parts[-1], None)
|
|
else:
|
|
parents[-1][parts[-1]] = value
|
|
|
|
for depth in range(len(parts) - 1, 0, -1):
|
|
if not parents[depth]:
|
|
parents[depth - 1].pop(parts[depth - 1], None)
|
|
|
|
|
|
def split_parts(line: str, count: int) -> list[str]:
|
|
parts = [part.strip() for part in line.split(SEPARATOR, count - 1)]
|
|
return parts + [""] * (count - len(parts))
|
|
|
|
|
|
def non_empty_lines(text: str) -> list[str]:
|
|
return [line.strip() for line in text.splitlines() if line.strip()]
|
|
|
|
|
|
def to_form_text(kind: str, value) -> str:
|
|
if value is None:
|
|
return ""
|
|
if kind in ("lines", "images"):
|
|
return "\n".join(value)
|
|
if kind == "tags":
|
|
return ", ".join(value)
|
|
if kind == "link_map":
|
|
return "\n".join(f"{name} {url}" for name, url in value.items())
|
|
if kind == "link_list":
|
|
return "\n".join(f"{link.get('label', '')} {SEPARATOR} {link.get('url', '')}" for link in value)
|
|
if kind == "awards":
|
|
return "\n".join(f"{award.get('track', '')} {SEPARATOR} {award.get('place', '')}" for award in value)
|
|
if kind == "socials":
|
|
return "\n".join(f"{social.get('name', '')} {SEPARATOR} {social.get('icon', '')} {SEPARATOR} {social.get('url', '')}" for social in value)
|
|
if kind == "skill_map":
|
|
return "\n".join(f"{category}: {', '.join(skills)}" for category, skills in value.items())
|
|
return str(value)
|
|
|
|
|
|
def from_form_text(kind: str, text: str):
|
|
text = text.strip()
|
|
if kind in ("lines", "images"):
|
|
return non_empty_lines(text)
|
|
if kind == "tags":
|
|
return [tag.strip() for tag in text.split(",") if tag.strip()]
|
|
if kind == "link_map":
|
|
links = {}
|
|
for line in non_empty_lines(text):
|
|
name, _, url = line.partition(" ")
|
|
if not url.strip():
|
|
raise ValueError(f"Link line needs a name and a url: {line}")
|
|
links[name.strip()] = url.strip()
|
|
return links
|
|
if kind == "link_list":
|
|
links = []
|
|
for line in non_empty_lines(text):
|
|
label, url = split_parts(line, 2)
|
|
if not url:
|
|
raise ValueError(f"Link line needs label | url: {line}")
|
|
links.append({"label": label, "url": url})
|
|
return links
|
|
if kind == "awards":
|
|
awards = []
|
|
for line in non_empty_lines(text):
|
|
track, place = split_parts(line, 2)
|
|
awards.append({"track": track, "place": place} if place else {"track": track})
|
|
return awards
|
|
if kind == "socials":
|
|
socials = []
|
|
for line in non_empty_lines(text):
|
|
name, icon, url = split_parts(line, 3)
|
|
if not url:
|
|
raise ValueError(f"Social line needs name | icon | url: {line}")
|
|
socials.append({"name": name, "icon": icon, "url": url})
|
|
return socials
|
|
if kind == "skill_map":
|
|
skills = {}
|
|
for line in non_empty_lines(text):
|
|
category, _, values = line.partition(":")
|
|
if not values.strip():
|
|
raise ValueError(f"Skill line needs Category: skill, skill: {line}")
|
|
skills[category.strip()] = [skill.strip() for skill in values.split(",") if skill.strip()]
|
|
return skills
|
|
return text
|
|
|
|
|
|
def summary_text(value) -> str:
|
|
if value is None:
|
|
return ""
|
|
if isinstance(value, bool):
|
|
return "yes" if value else ""
|
|
if isinstance(value, list):
|
|
return ", ".join(str(item) for item in value if not isinstance(item, dict))
|
|
return str(value)
|