Skip to content

Commit 2b7e56d

Browse files
committed
feat: vps command implementation
1 parent 161fcc2 commit 2b7e56d

File tree

1 file changed

+220
-31
lines changed

1 file changed

+220
-31
lines changed

output/table.go

Lines changed: 220 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,26 @@ func displayMetaAsTable(meta map[string]interface{}) error {
124124
}
125125

126126
func displayObjectAsKeyValueTable(obj map[string]interface{}) error {
127-
t := table.New(os.Stdout)
128-
t.SetHeaders("Property", "Value")
127+
return displayAsTree(obj, "", true)
128+
}
129+
130+
func displayAsTree(data interface{}, prefix string, isRoot bool) error {
131+
switch v := data.(type) {
132+
case map[string]interface{}:
133+
return displayObjectAsTree(v, prefix, isRoot)
134+
case []interface{}:
135+
return displayArrayAsTree(v, prefix, isRoot)
136+
default:
137+
fmt.Printf("%s%s\n", prefix, formatValue(v))
138+
return nil
139+
}
140+
}
141+
142+
func displayObjectAsTree(obj map[string]interface{}, prefix string, isRoot bool) error {
143+
if len(obj) == 0 {
144+
fmt.Printf("%s%s\n", prefix, tml.Sprintf("<italic>(empty object)</italic>"))
145+
return nil
146+
}
129147

130148
// Get sorted keys
131149
keys := make([]string, 0, len(obj))
@@ -134,41 +152,99 @@ func displayObjectAsKeyValueTable(obj map[string]interface{}) error {
134152
}
135153
sort.Strings(keys)
136154

137-
for _, key := range keys {
155+
for i, key := range keys {
156+
isLast := i == len(keys)-1
138157
value := obj[key]
139158

140-
// Handle nested structures specially
141-
switch v := value.(type) {
142-
case []interface{}:
143-
if len(v) == 0 {
144-
t.AddRow(key, tml.Sprintf("<italic>(empty array)</italic>"))
145-
} else if isArrayOfObjects(v) {
146-
// For arrays of objects, show a summary
147-
t.AddRow(key, fmt.Sprintf("Array[%d objects]", len(v)))
159+
// Determine tree characters
160+
var keyPrefix, childPrefix string
161+
if isRoot {
162+
keyPrefix = ""
163+
childPrefix = ""
164+
} else {
165+
if isLast {
166+
keyPrefix = prefix + "└── "
167+
childPrefix = prefix + " "
148168
} else {
149-
// For arrays of simple values, show them inline if short
150-
if len(v) <= 3 {
151-
values := make([]string, len(v))
152-
for i, item := range v {
153-
values[i] = formatValue(item)
154-
}
155-
t.AddRow(key, fmt.Sprintf("[%s]", strings.Join(values, ", ")))
156-
} else {
157-
t.AddRow(key, fmt.Sprintf("Array[%d items]", len(v)))
158-
}
169+
keyPrefix = prefix + "├── "
170+
childPrefix = prefix + "│ "
159171
}
172+
}
173+
174+
// Display the key
175+
if isRoot {
176+
fmt.Printf("%s:\n", tml.Sprintf("<bold>%s</bold>", key))
177+
} else {
178+
fmt.Printf("%s%s: ", keyPrefix, tml.Sprintf("<bold>%s</bold>", key))
179+
}
180+
181+
// Handle the value
182+
switch val := value.(type) {
160183
case map[string]interface{}:
161-
if len(v) == 0 {
162-
t.AddRow(key, tml.Sprintf("<italic>(empty object)</italic>"))
184+
if !isRoot {
185+
fmt.Print("\n")
186+
}
187+
if err := displayAsTree(val, childPrefix, false); err != nil {
188+
return err
189+
}
190+
case []interface{}:
191+
if !isRoot {
192+
fmt.Print("\n")
193+
}
194+
if err := displayAsTree(val, childPrefix, false); err != nil {
195+
return err
196+
}
197+
default:
198+
if isRoot {
199+
fmt.Printf(" %s\n", formatValue(val))
163200
} else {
164-
t.AddRow(key, fmt.Sprintf("Object[%d properties]", len(v)))
201+
fmt.Printf("%s\n", formatValue(val))
202+
}
203+
}
204+
}
205+
206+
return nil
207+
}
208+
209+
func displayArrayAsTree(arr []interface{}, prefix string, isRoot bool) error {
210+
if len(arr) == 0 {
211+
fmt.Printf("%s%s\n", prefix, tml.Sprintf("<italic>(empty array)</italic>"))
212+
return nil
213+
}
214+
215+
for i, item := range arr {
216+
isLast := i == len(arr)-1
217+
218+
// Determine tree characters
219+
var itemPrefix, childPrefix string
220+
if isLast {
221+
itemPrefix = prefix + "└── "
222+
childPrefix = prefix + " "
223+
} else {
224+
itemPrefix = prefix + "├── "
225+
childPrefix = prefix + "│ "
226+
}
227+
228+
// Display array index
229+
fmt.Printf("%s[%d]: ", itemPrefix, i)
230+
231+
// Handle the item
232+
switch val := item.(type) {
233+
case map[string]interface{}:
234+
fmt.Print("\n")
235+
if err := displayAsTree(val, childPrefix, false); err != nil {
236+
return err
237+
}
238+
case []interface{}:
239+
fmt.Print("\n")
240+
if err := displayAsTree(val, childPrefix, false); err != nil {
241+
return err
165242
}
166243
default:
167-
t.AddRow(key, formatValue(value))
244+
fmt.Printf("%s\n", formatValue(val))
168245
}
169246
}
170247

171-
t.Render()
172248
return nil
173249
}
174250

@@ -280,16 +356,129 @@ func formatCellValue(value interface{}) string {
280356
return tml.Sprintf("<cyan>%s</cyan>", strconv.Itoa(v))
281357
case int64:
282358
return tml.Sprintf("<cyan>%s</cyan>", strconv.FormatInt(v, 10))
359+
case []interface{}, map[string]interface{}:
360+
// For nested structures in table cells, format as tree string
361+
return formatNestedAsTreeString(v, "")
362+
default:
363+
return fmt.Sprintf("%v", v)
364+
}
365+
}
366+
367+
// formatNestedAsTreeString formats nested structures as a compact tree string for table cells
368+
func formatNestedAsTreeString(data interface{}, prefix string) string {
369+
var result strings.Builder
370+
371+
switch v := data.(type) {
372+
case map[string]interface{}:
373+
if len(v) == 0 {
374+
return "{}"
375+
}
376+
377+
// Get sorted keys
378+
keys := make([]string, 0, len(v))
379+
for key := range v {
380+
keys = append(keys, key)
381+
}
382+
sort.Strings(keys)
383+
384+
for i, key := range keys {
385+
isLast := i == len(keys)-1
386+
var keyPrefix, childPrefix string
387+
388+
if isLast {
389+
keyPrefix = prefix + "└── "
390+
childPrefix = prefix + " "
391+
} else {
392+
keyPrefix = prefix + "├── "
393+
childPrefix = prefix + "│ "
394+
}
395+
396+
if i > 0 {
397+
result.WriteString("\n")
398+
}
399+
400+
value := v[key]
401+
switch val := value.(type) {
402+
case map[string]interface{}, []interface{}:
403+
result.WriteString(fmt.Sprintf("%s%s:", keyPrefix, key))
404+
result.WriteString("\n")
405+
result.WriteString(formatNestedAsTreeString(val, childPrefix))
406+
default:
407+
// Format the whole node (key + value)
408+
nodeText := fmt.Sprintf("%s%s: %s", keyPrefix, key, formatValuePlain(val))
409+
result.WriteString(truncateNodeIfNeeded(nodeText, keyPrefix, key, formatValuePlain(val)))
410+
}
411+
}
412+
283413
case []interface{}:
284414
if len(v) == 0 {
285415
return "[]"
286416
}
287-
return fmt.Sprintf("[%d items]", len(v))
288-
case map[string]interface{}:
289-
if len(v) == 0 {
290-
return "{}"
417+
418+
for i, item := range v {
419+
isLast := i == len(v)-1
420+
var itemPrefix, childPrefix string
421+
422+
if isLast {
423+
itemPrefix = prefix + "└── "
424+
childPrefix = prefix + " "
425+
} else {
426+
itemPrefix = prefix + "├── "
427+
childPrefix = prefix + "│ "
428+
}
429+
430+
if i > 0 {
431+
result.WriteString("\n")
432+
}
433+
434+
switch val := item.(type) {
435+
case map[string]interface{}, []interface{}:
436+
result.WriteString(fmt.Sprintf("%s[%d]:", itemPrefix, i))
437+
result.WriteString("\n")
438+
result.WriteString(formatNestedAsTreeString(val, childPrefix))
439+
default:
440+
// Format the whole node (index + value)
441+
nodeText := fmt.Sprintf("%s[%d]: %s", itemPrefix, i, formatValuePlain(val))
442+
result.WriteString(truncateNodeIfNeeded(nodeText, itemPrefix, fmt.Sprintf("[%d]", i), formatValuePlain(val)))
443+
}
291444
}
292-
return fmt.Sprintf("{%d props}", len(v))
445+
}
446+
447+
return result.String()
448+
}
449+
450+
// truncateNodeIfNeeded truncates the whole node if it's longer than 32 chars and the value contains spaces
451+
func truncateNodeIfNeeded(fullNode, prefix, label, value string) string {
452+
// Check if the value (not the whole node) contains spaces
453+
if len(fullNode) > 32 && strings.Contains(value, " ") {
454+
return fullNode[:32] + "..."
455+
}
456+
return fullNode
457+
}
458+
459+
// formatValuePlain formats values without color codes for tree strings
460+
func formatValuePlain(value interface{}) string {
461+
if value == nil {
462+
return "null"
463+
}
464+
465+
switch v := value.(type) {
466+
case string:
467+
if v == "" {
468+
return "(empty)"
469+
}
470+
return v
471+
case bool:
472+
return strconv.FormatBool(v)
473+
case float64:
474+
if v == float64(int64(v)) {
475+
return strconv.FormatInt(int64(v), 10)
476+
}
477+
return strconv.FormatFloat(v, 'f', -1, 64)
478+
case int:
479+
return strconv.Itoa(v)
480+
case int64:
481+
return strconv.FormatInt(v, 10)
293482
default:
294483
return fmt.Sprintf("%v", v)
295484
}

0 commit comments

Comments
 (0)