this repo has no description
1import Foundation
2
3public enum UsageFormatter {
4 public static func usageLine(remaining: Double, used: Double) -> String {
5 String(format: "%.0f%% left", remaining)
6 }
7
8 public static func resetDescription(from date: Date, now: Date = .init()) -> String {
9 // Human-friendly phrasing: today / tomorrow / date+time.
10 let calendar = Calendar.current
11 if calendar.isDate(date, inSameDayAs: now) {
12 return date.formatted(date: .omitted, time: .shortened)
13 }
14 if let tomorrow = calendar.date(byAdding: .day, value: 1, to: now),
15 calendar.isDate(date, inSameDayAs: tomorrow)
16 {
17 return "tomorrow, \(date.formatted(date: .omitted, time: .shortened))"
18 }
19 return date.formatted(date: .abbreviated, time: .shortened)
20 }
21
22 public static func updatedString(from date: Date, now: Date = .init()) -> String {
23 let delta = now.timeIntervalSince(date)
24 if abs(delta) < 60 {
25 return "Updated just now"
26 }
27 if let hours = Calendar.current.dateComponents([.hour], from: date, to: now).hour, hours < 24 {
28 let rel = RelativeDateTimeFormatter()
29 rel.unitsStyle = .abbreviated
30 return "Updated \(rel.localizedString(for: date, relativeTo: now))"
31 } else {
32 return "Updated \(date.formatted(date: .omitted, time: .shortened))"
33 }
34 }
35
36 public static func creditsString(from value: Double) -> String {
37 let number = NumberFormatter()
38 number.numberStyle = .decimal
39 number.maximumFractionDigits = 2
40 let formatted = number.string(from: NSNumber(value: value)) ?? String(Int(value))
41 return "\(formatted) left"
42 }
43
44 public static func creditEventSummary(_ event: CreditEvent) -> String {
45 let formatter = DateFormatter()
46 formatter.dateStyle = .medium
47 let number = NumberFormatter()
48 number.numberStyle = .decimal
49 number.maximumFractionDigits = 2
50 let credits = number.string(from: NSNumber(value: event.creditsUsed)) ?? "0"
51 return "\(formatter.string(from: event.date)) · \(event.service) · \(credits) credits"
52 }
53
54 public static func creditEventCompact(_ event: CreditEvent) -> String {
55 let formatter = DateFormatter()
56 formatter.dateFormat = "MMM d"
57 let number = NumberFormatter()
58 number.numberStyle = .decimal
59 number.maximumFractionDigits = 2
60 let credits = number.string(from: NSNumber(value: event.creditsUsed)) ?? "0"
61 return "\(formatter.string(from: event.date)) — \(event.service): \(credits)"
62 }
63
64 public static func creditShort(_ value: Double) -> String {
65 if value >= 1000 {
66 let k = value / 1000
67 return String(format: "%.1fk", k)
68 }
69 return String(format: "%.0f", value)
70 }
71
72 public static func truncatedSingleLine(_ text: String, max: Int = 80) -> String {
73 let single = text
74 .replacingOccurrences(of: "\n", with: " ")
75 .trimmingCharacters(in: .whitespacesAndNewlines)
76 guard single.count > max else { return single }
77 let idx = single.index(single.startIndex, offsetBy: max)
78 return "\(single[..<idx])…"
79 }
80
81 /// Cleans a provider plan string: strip ANSI/bracket noise, drop boilerplate words, collapse whitespace, and
82 /// ensure a leading capital if the result starts lowercase.
83 public static func cleanPlanName(_ text: String) -> String {
84 let stripped = TextParsing.stripANSICodes(text)
85 let withoutCodes = stripped.replacingOccurrences(
86 of: #"^\s*(?:\[\d{1,3}m\s*)+"#,
87 with: "",
88 options: [.regularExpression])
89 let withoutBoilerplate = withoutCodes.replacingOccurrences(
90 of: #"(?i)\b(claude|codex|account|plan)\b"#,
91 with: "",
92 options: [.regularExpression])
93 var cleaned = withoutBoilerplate
94 .replacingOccurrences(of: #"\s+"#, with: " ", options: .regularExpression)
95 .trimmingCharacters(in: .whitespacesAndNewlines)
96 if cleaned.isEmpty {
97 cleaned = stripped.trimmingCharacters(in: .whitespacesAndNewlines)
98 }
99 return cleaned.capitalized
100 }
101}