|
8 | 8 |
|
9 | 9 | "github.com/SpechtLabs/CalendarAPI/pkg/api" |
10 | 10 | pb "github.com/SpechtLabs/CalendarAPI/pkg/protos" |
| 11 | + "github.com/charmbracelet/lipgloss" |
11 | 12 | "github.com/spechtlabs/go-otel-utils/otelzap" |
12 | 13 | "github.com/spf13/cobra" |
13 | 14 | "go.uber.org/zap" |
@@ -97,29 +98,124 @@ var getCalendarCmd = &cobra.Command{ |
97 | 98 | } |
98 | 99 |
|
99 | 100 | func formatText(resp *pb.CalendarResponse) string { |
100 | | - outStr := fmt.Sprintf("Got Calendar (last refreshed: %s)\n\n", time.Unix(resp.LastUpdated, 0).Format(time.RFC822)) |
| 101 | + now := time.Now() |
| 102 | + |
| 103 | + // Styles |
| 104 | + headerStyle := lipgloss.NewStyle().Bold(true).Underline(true) |
| 105 | + contextStyle := lipgloss.NewStyle().Italic(true).Foreground(lipgloss.Color("#999999")) |
| 106 | + importantStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000")).Bold(true) |
| 107 | + freeStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#999999")) |
| 108 | + tentativeStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500")).Italic(true) |
| 109 | + outOfOfficeStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#800080")).Bold(true) |
| 110 | + defaultStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFFFF")) |
| 111 | + strikeThroughStyle := lipgloss.NewStyle().Strikethrough(true).Foreground(lipgloss.Color("#666666")) |
| 112 | + |
| 113 | + outStr := "" |
| 114 | + outStr += contextStyle.Render(fmt.Sprintf("(last refreshed: %s)", time.Unix(resp.LastUpdated, 0).Format(time.TimeOnly))) |
| 115 | + outStr += "\n\n" |
| 116 | + |
| 117 | + outStr += fmt.Sprintf("Calendar: %s Date: %s", |
| 118 | + headerStyle.Render(resp.CalendarName), |
| 119 | + headerStyle.Render(time.Unix(resp.LastUpdated, 0).Format(time.DateOnly)), |
| 120 | + ) |
| 121 | + |
| 122 | + outStr += "\n" |
| 123 | + |
| 124 | + // Separate all-day from timed events |
| 125 | + allDayEntries := []*pb.CalendarEntry{} |
| 126 | + normalEntries := []*pb.CalendarEntry{} |
| 127 | + showCalendarName := false |
| 128 | + for _, e := range resp.Entries { |
| 129 | + if e.CalendarName != resp.CalendarName { |
| 130 | + showCalendarName = true |
| 131 | + } |
| 132 | + if e.AllDay { |
| 133 | + allDayEntries = append(allDayEntries, e) |
| 134 | + } else { |
| 135 | + normalEntries = append(normalEntries, e) |
| 136 | + } |
| 137 | + } |
101 | 138 |
|
102 | | - for idx, item := range resp.Entries { |
103 | | - outStr += fmt.Sprintf("%d) ", idx) |
| 139 | + idx := 1 |
| 140 | + // Show all-day first |
| 141 | + for _, item := range allDayEntries { |
| 142 | + outStr += renderEntry(item, idx, now, showCalendarName, strikeThroughStyle, importantStyle, |
| 143 | + freeStyle, tentativeStyle, outOfOfficeStyle, defaultStyle, contextStyle) |
| 144 | + idx++ |
| 145 | + } |
104 | 146 |
|
105 | | - if item.Important { |
106 | | - outStr += "!" |
107 | | - } |
| 147 | + // Then timed events |
| 148 | + for _, item := range normalEntries { |
| 149 | + outStr += renderEntry(item, idx, now, showCalendarName, strikeThroughStyle, importantStyle, |
| 150 | + freeStyle, tentativeStyle, outOfOfficeStyle, defaultStyle, contextStyle) |
| 151 | + idx++ |
| 152 | + } |
108 | 153 |
|
109 | | - outStr += fmt.Sprintf("%s: [%s to %s] - %s", item.Title, time.Unix(item.Start, 0).Format(time.RFC822), time.Unix(item.End, 0).Format(time.RFC822), item.Busy.String()) |
| 154 | + return outStr |
| 155 | +} |
110 | 156 |
|
111 | | - if item.AllDay { |
112 | | - outStr += " (all day)" |
113 | | - } |
| 157 | +func renderEntry( |
| 158 | + item *pb.CalendarEntry, |
| 159 | + idx int, |
| 160 | + now time.Time, |
| 161 | + showCalendarName bool, |
| 162 | + strikeThroughStyle, importantStyle, freeStyle, |
| 163 | + tentativeStyle, outOfOfficeStyle, defaultStyle, |
| 164 | + contextStyle lipgloss.Style, |
| 165 | +) string { |
| 166 | + start := time.Unix(item.Start, 0) |
| 167 | + end := time.Unix(item.End, 0) |
| 168 | + |
| 169 | + // Base line (without styling yet) |
| 170 | + var line string |
| 171 | + |
| 172 | + // 1. Add Index |
| 173 | + line = fmt.Sprintf("%2d) ", idx) |
| 174 | + |
| 175 | + // 2. Add status (only for tentative, OOO, or working elsewhere) |
| 176 | + switch item.Busy { |
| 177 | + case pb.BusyState_Tentative: |
| 178 | + fallthrough |
| 179 | + case pb.BusyState_OutOfOffice: |
| 180 | + fallthrough |
| 181 | + case pb.BusyState_WorkingElsewhere: |
| 182 | + line += fmt.Sprintf("[%s]", item.Busy.String()) |
| 183 | + } |
114 | 184 |
|
115 | | - if len(item.Message) > 0 { |
116 | | - outStr += fmt.Sprintf(": %s", item.Message) |
117 | | - } |
| 185 | + if item.AllDay { |
| 186 | + line += fmt.Sprintf("%s (all day)", item.Title) |
| 187 | + } else { |
| 188 | + line += fmt.Sprintf("%s: <%s - %s>", item.Title, start.Format(time.Kitchen), end.Format(time.Kitchen)) |
| 189 | + } |
118 | 190 |
|
119 | | - outStr += "\n" |
| 191 | + if len(item.Message) > 0 { |
| 192 | + line += fmt.Sprintf(" - %s", item.Message) |
120 | 193 | } |
121 | 194 |
|
122 | | - return outStr |
| 195 | + // Past event? Strike through |
| 196 | + if end.Before(now) { |
| 197 | + return strikeThroughStyle.Render(line) + strikeThroughStyle.Italic(true).Render(fmt.Sprintf(" (%s)", item.CalendarName)) + "\n" |
| 198 | + } |
| 199 | + |
| 200 | + // Apply styles based on attributes |
| 201 | + switch { |
| 202 | + case item.Important: |
| 203 | + line = importantStyle.Render(line) |
| 204 | + case item.Busy == pb.BusyState_Free: |
| 205 | + line = freeStyle.Render(line) |
| 206 | + case item.Busy == pb.BusyState_Tentative: |
| 207 | + line = tentativeStyle.Render(line) |
| 208 | + case item.Busy == pb.BusyState_OutOfOffice || item.Busy == pb.BusyState_WorkingElsewhere: |
| 209 | + line = outOfOfficeStyle.Render(line) |
| 210 | + default: |
| 211 | + line = defaultStyle.Render(line) |
| 212 | + } |
| 213 | + |
| 214 | + if showCalendarName { |
| 215 | + line += contextStyle.Render(fmt.Sprintf(" (%s)", item.CalendarName)) |
| 216 | + } |
| 217 | + |
| 218 | + return line + "\n" |
123 | 219 | } |
124 | 220 |
|
125 | 221 | func init() { |
|
0 commit comments