import Foundation /// `profile set` — unlike every other filter or edit in the app, `--link` /// replaces the whole set rather than appending, so every link the form /// wants to keep is sent on every save. The form is always seeded from /// the current profile, so every other field is also sent every time, /// including empty ones: `--description ""` clears, omitting the flag /// would leave the old value alone. nonisolated struct ProfileEdit: Equatable, Sendable { nonisolated struct Link: Equatable, Sendable, Identifiable { var id = UUID() var label: String var url: String } var description: String var website: String var about: String var aboutFormat: String var links: [Link] init( description: String = "", website: String = "", about: String = "", aboutFormat: String = "md", links: [Link] = [] ) { self.description = description self.website = website self.about = about self.aboutFormat = aboutFormat self.links = links } init(from profile: ProfileViewModel.Profile) { description = profile.description ?? "" website = profile.website ?? "" about = profile.about ?? "" aboutFormat = profile.aboutFormat ?? "md" links = (profile.links ?? []).map { Link(label: $0.label ?? "", url: $0.url) } } /// At most 5 links and every link needs a URL — the server enforces /// both, but the form should not let a save reach it that way. A /// label may not contain `|` either: the server splits a link on its /// first pipe, so a label carrying one would silently corrupt the URL /// rather than be rejected. var validationError: String? { if links.count > 5 { return "At most 5 links." } if links.contains(where: { $0.url.trimmingCharacters(in: .whitespaces).isEmpty }) { return "A link needs a URL." } if links.contains(where: { $0.label.contains("|") }) { return "A link label can't contain \"|\" — that's what separates it from the URL." } return nil } /// Everything except the about text, which travels over stdin. func flags() -> [String] { var flags = ["--description", description, "--website", website] if links.isEmpty { flags += ["--link", ""] } else { for link in links { let label = link.label.trimmingCharacters(in: .whitespaces) flags += ["--link", label.isEmpty ? link.url : "\(label)|\(link.url)"] } } flags += ["--about-format", aboutFormat, "--file", "-"] return flags } /// The about text, sent over stdin — never in argv, since it is /// long-form and may hold newlines. var aboutStdin: String { about } }