53b4f60da1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
34 lines
1.4 KiB
Swift
34 lines
1.4 KiB
Swift
import Foundation
|
|
|
|
/// First name of the macOS account holder, or nil when the account name is not something
|
|
/// you would greet someone by. "Théodore Riant" gives "Théodore"; a login handle like
|
|
/// "theodoreriant" or "t.riant2" gives nil, because "Hey theodoreriant!" reads worse than
|
|
/// a greeting with no name at all.
|
|
func resolveUserFirstName() -> String? {
|
|
let fullName = NSFullUserName().trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !fullName.isEmpty, fullName.count <= maxUserFullNameLength else { return nil }
|
|
guard !looksLikeLoginHandle(fullName) else { return nil }
|
|
|
|
guard let first = fullName.split(separator: " ").first.map(String.init),
|
|
!first.isEmpty,
|
|
first.allSatisfy(isNamePart) else { return nil }
|
|
return first
|
|
}
|
|
|
|
/// NSFullUserName() falls back to the short account name when the full name is unset, so a
|
|
/// single lowercase word, or one carrying digits or separators, is treated as a handle.
|
|
private func looksLikeLoginHandle(_ name: String) -> Bool {
|
|
guard !name.contains(" ") else { return false }
|
|
return name == name.lowercased() || name.contains(where: isHandleMarker)
|
|
}
|
|
|
|
private func isHandleMarker(_ character: Character) -> Bool {
|
|
character.isNumber || "._-@".contains(character)
|
|
}
|
|
|
|
private func isNamePart(_ character: Character) -> Bool {
|
|
character.isLetter || character == "'" || character == "-"
|
|
}
|
|
|
|
private let maxUserFullNameLength = 32
|