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)