@@ -11,6 +11,7 @@ import (
1111 "os"
1212 "slices"
1313 "strings"
14+ "time"
1415)
1516
1617// TextFormatter implements Formatter with plain text formatting capabilities.
@@ -53,9 +54,9 @@ func newTextFormatter(config Config) *TextFormatter {
5354 }
5455}
5556
56- // WriteContent formats and outputs structured content.
57- func ( f * TextFormatter ) WriteContent ( content Content ) {
58- // Get appropriate writer based on buffering mode and error state
57+ // selectWriter returns the writer for content based on buffering mode and
58+ // error state.
59+ func ( f * TextFormatter ) selectWriter ( content Content ) io. Writer {
5960 var writer io.Writer
6061
6162 if f .buffering {
@@ -72,6 +73,13 @@ func (f *TextFormatter) WriteContent(content Content) {
7273 }
7374 }
7475
76+ return writer
77+ }
78+
79+ // WriteContent formats and outputs structured content.
80+ func (f * TextFormatter ) WriteContent (content Content ) {
81+ writer := f .selectWriter (content )
82+
7583 // Format and write content based on type, regardless of error state
7684 switch content .Type {
7785 case TypeDeviceList :
@@ -84,6 +92,8 @@ func (f *TextFormatter) WriteContent(content Content) {
8492 f .writeDetailTo (content , writer )
8593 case TypeModuleOutput :
8694 f .writeModuleOutputTo (content , writer )
95+ case TypeLockResult :
96+ f .writeLockResultTo (content , writer )
8797 default :
8898 // For general text or unrecognized types
8999 f .writeGeneralTo (content , writer )
@@ -148,17 +158,79 @@ func (f *TextFormatter) Flush() error {
148158
149159// Helper methods for different content types
150160
161+ // humanDuration renders dur as a compact "1h30m"-style string, rounded to the
162+ // minute. A non-positive duration renders as "0m".
163+ func humanDuration (dur time.Duration ) string {
164+ dur = dur .Round (time .Minute )
165+ if dur <= 0 {
166+ return "0m"
167+ }
168+
169+ hours := dur / time .Hour
170+ minutes := (dur % time .Hour ) / time .Minute
171+
172+ switch {
173+ case hours > 0 && minutes > 0 :
174+ return fmt .Sprintf ("%dh%dm" , hours , minutes )
175+ case hours > 0 :
176+ return fmt .Sprintf ("%dh" , hours )
177+ default :
178+ return fmt .Sprintf ("%dm" , minutes )
179+ }
180+ }
181+
182+ // lockAnnotation renders the bracketed lock note for a locked device, e.g.
183+ // ` [locked by "alice@host" for 25m]`. ExpiresAt of 0 omits the duration.
184+ func lockAnnotation (entry DeviceEntry ) string {
185+ if entry .ExpiresAt == 0 {
186+ return fmt .Sprintf (" [locked by %q]" , entry .Owner )
187+ }
188+
189+ remaining := humanDuration (time .Until (time .Unix (entry .ExpiresAt , 0 )))
190+
191+ return fmt .Sprintf (" [locked by %q for %s]" , entry .Owner , remaining )
192+ }
193+
151194// writeDeviceListTo formats and writes a list of devices with bullet points.
152195func (f * TextFormatter ) writeDeviceListTo (content Content , writer io.Writer ) {
153- if devices , ok := content .Data .([]string ); ok {
154- // Print metadata before content
155- f .writeMetadata (content , writer )
196+ devices , ok := content .Data .([]DeviceEntry )
197+ if ! ok {
198+ f .writeGeneralTo (content , writer )
199+
200+ return
201+ }
156202
157- for _ , device := range devices {
158- fmt .Fprintf (writer , "- %s\n " , device )
203+ // Print metadata before content
204+ f .writeMetadata (content , writer )
205+
206+ for _ , device := range devices {
207+ if device .Locked {
208+ fmt .Fprintf (writer , "- %s%s\n " , device .Name , lockAnnotation (device ))
209+ } else {
210+ fmt .Fprintf (writer , "- %s\n " , device .Name )
159211 }
160- } else {
212+ }
213+ }
214+
215+ // writeLockResultTo formats and writes the result of a lock or unlock operation.
216+ func (f * TextFormatter ) writeLockResultTo (content Content , writer io.Writer ) {
217+ entry , ok := content .Data .(DeviceEntry )
218+ if ! ok {
161219 f .writeGeneralTo (content , writer )
220+
221+ return
222+ }
223+
224+ f .writeMetadata (content , writer )
225+
226+ switch {
227+ case ! entry .Locked :
228+ fmt .Fprintf (writer , "Device %q unlocked\n " , entry .Name )
229+ case entry .ExpiresAt == 0 :
230+ fmt .Fprintf (writer , "Device %q locked by %q\n " , entry .Name , entry .Owner )
231+ default :
232+ remaining := humanDuration (time .Until (time .Unix (entry .ExpiresAt , 0 )))
233+ fmt .Fprintf (writer , "Device %q locked by %q for %s\n " , entry .Name , entry .Owner , remaining )
162234 }
163235}
164236
0 commit comments