@@ -2,12 +2,11 @@ package list
2
2
3
3
import (
4
4
"fmt"
5
- "os"
6
5
"regexp"
7
6
"sort"
8
7
"strings"
9
- "text/tabwriter"
10
8
9
+ "github.com/charmbracelet/lipgloss"
11
10
"github.com/samber/lo"
12
11
13
12
"github.com/cloudposse/atmos/pkg/schema"
@@ -37,7 +36,7 @@ func getStackComponents(stackData any, listFields []string) ([]string, error) {
37
36
data := terraformComponents [dataKey ]
38
37
dataMap , ok := data .(map [string ]any )
39
38
if ! ok {
40
- return nil , fmt .Errorf ("unexpected data type for component '%s'" , dataKey )
39
+ return nil , fmt .Errorf ("unexpected data type for component '%s'" , dataKey )
41
40
}
42
41
rowData := make ([]string , 0 )
43
42
for _ , key := range listFields {
@@ -84,14 +83,18 @@ func resolveKey(data map[string]any, key string) (any, bool) {
84
83
85
84
// FilterAndListComponents filters and lists components based on the given stack
86
85
func FilterAndListComponents (stackFlag string , stacksMap map [string ]any , listConfig schema.ListConfig ) (string , error ) {
87
- components := []string {}
86
+ components := [][]string {}
87
+
88
+ // Define lipgloss styles for headers and rows
89
+ headerStyle := lipgloss .NewStyle ().Bold (true ).Foreground (lipgloss .Color ("#00BFFF" ))
90
+ rowStyle := lipgloss .NewStyle ().Foreground (lipgloss .Color ("#FFFFFF" ))
88
91
89
- writer := tabwriter .NewWriter (os .Stdout , 0 , 0 , 2 , ' ' , tabwriter .AlignRight )
90
92
header := make ([]string , 0 )
91
93
listFields := make ([]string , 0 )
92
94
93
95
re := regexp .MustCompile (`\{\{\s*(.*?)\s*\}\}` )
94
96
97
+ // Extract and format headers
95
98
for _ , v := range listConfig .Columns {
96
99
header = append (header , v .Name )
97
100
match := re .FindStringSubmatch (v .Value )
@@ -102,16 +105,18 @@ func FilterAndListComponents(stackFlag string, stacksMap map[string]any, listCon
102
105
return "" , fmt .Errorf ("invalid value format for column name %s" , v .Name )
103
106
}
104
107
}
105
- fmt .Fprintln (writer , strings .Join (header , "\t \t " ))
106
108
109
+ // Collect components for the table
107
110
if stackFlag != "" {
108
111
// Filter components for the specified stack
109
112
if stackData , ok := stacksMap [stackFlag ]; ok {
110
113
stackComponents , err := getStackComponents (stackData , listFields )
111
114
if err != nil {
112
115
return "" , fmt .Errorf ("error processing stack '%s': %w" , stackFlag , err )
113
116
}
114
- components = append (components , stackComponents ... )
117
+ for _ , c := range stackComponents {
118
+ components = append (components , strings .Fields (c ))
119
+ }
115
120
} else {
116
121
return "" , fmt .Errorf ("stack '%s' not found" , stackFlag )
117
122
}
@@ -122,21 +127,60 @@ func FilterAndListComponents(stackFlag string, stacksMap map[string]any, listCon
122
127
if err != nil {
123
128
continue // Skip invalid stacks
124
129
}
125
- components = append (components , stackComponents ... )
130
+ for _ , c := range stackComponents {
131
+ components = append (components , strings .Fields (c ))
132
+ }
126
133
}
127
134
}
128
135
129
- // Remove duplicates and sort components
130
- components = lo .Uniq (components )
131
- sort .Strings (components )
136
+ // Remove duplicates, sort, and prepare rows
137
+ componentsMap := lo .UniqBy (components , func (item []string ) string {
138
+ return strings .Join (item , "\t " )
139
+ })
140
+ sort .Slice (componentsMap , func (i , j int ) bool {
141
+ return strings .Join (componentsMap [i ], "\t " ) < strings .Join (componentsMap [j ], "\t " )
142
+ })
132
143
133
- if len (components ) == 0 {
144
+ if len (componentsMap ) == 0 {
134
145
return "No components found" , nil
135
146
}
136
147
137
- for _ , com := range components {
138
- fmt .Fprintln (writer , com )
148
+ // Determine column widths
149
+ colWidths := make ([]int , len (header ))
150
+ for i , h := range header {
151
+ colWidths [i ] = len (h )
152
+ }
153
+ for _ , row := range componentsMap {
154
+ for i , field := range row {
155
+ if len (field ) > colWidths [i ] {
156
+ colWidths [i ] = len (field )
157
+ }
158
+ }
159
+ }
160
+
161
+ // Format the headers
162
+ headerRow := make ([]string , len (header ))
163
+ for i , h := range header {
164
+ headerRow [i ] = headerStyle .Render (padToWidth (h , colWidths [i ]))
139
165
}
140
- writer .Flush ()
166
+ fmt .Println (strings .Join (headerRow , " " ))
167
+
168
+ // Format the rows
169
+ for _ , row := range componentsMap {
170
+ formattedRow := make ([]string , len (row ))
171
+ for i , field := range row {
172
+ formattedRow [i ] = rowStyle .Render (padToWidth (field , colWidths [i ]))
173
+ }
174
+ fmt .Println (strings .Join (formattedRow , " " ))
175
+ }
176
+
141
177
return "" , nil
142
178
}
179
+
180
+ // padToWidth ensures a string is padded to the given width
181
+ func padToWidth (str string , width int ) string {
182
+ for len (str ) < width {
183
+ str += " "
184
+ }
185
+ return str
186
+ }
0 commit comments