(out any, sort string, headers table.Row, filterColumns []string)
| 178 | } |
| 179 | |
| 180 | func renderTable(out any, sort string, headers table.Row, filterColumns []string) (string, error) { |
| 181 | v := reflect.Indirect(reflect.ValueOf(out)) |
| 182 | |
| 183 | // Return empty string for empty data. Callers should check for this |
| 184 | // and provide an appropriate message to the user. |
| 185 | if v.Kind() == reflect.Slice && v.Len() == 0 { |
| 186 | return "", nil |
| 187 | } |
| 188 | |
| 189 | headers = filterHeaders(headers, filterColumns) |
| 190 | columnConfigs := createColumnConfigs(headers, filterColumns) |
| 191 | |
| 192 | // Setup the table formatter. |
| 193 | tw := Table() |
| 194 | tw.AppendHeader(headers) |
| 195 | tw.SetColumnConfigs(columnConfigs) |
| 196 | if sort != "" { |
| 197 | tw.SortBy([]table.SortBy{{ |
| 198 | Name: sort, |
| 199 | }}) |
| 200 | } |
| 201 | |
| 202 | // Write each struct to the table. |
| 203 | for i := 0; i < v.Len(); i++ { |
| 204 | cur := v.Index(i).Interface() |
| 205 | _, ok := cur.(TableSeparator) |
| 206 | if ok { |
| 207 | tw.AppendSeparator() |
| 208 | continue |
| 209 | } |
| 210 | // Format the row as a slice. |
| 211 | // ValueToTableMap does what `reflect.Indirect` does |
| 212 | rowMap, err := valueToTableMap(reflect.ValueOf(cur)) |
| 213 | if err != nil { |
| 214 | return "", xerrors.Errorf("get table row map %v: %w", i, err) |
| 215 | } |
| 216 | |
| 217 | rowSlice := make([]any, len(headers)) |
| 218 | for i, h := range headers { |
| 219 | v, ok := rowMap[h.(string)] |
| 220 | if !ok { |
| 221 | v = nil |
| 222 | } |
| 223 | |
| 224 | // Special type formatting. |
| 225 | switch val := v.(type) { |
| 226 | case time.Time: |
| 227 | v = val.Format(time.RFC3339) |
| 228 | case *time.Time: |
| 229 | if val != nil { |
| 230 | v = val.Format(time.RFC3339) |
| 231 | } |
| 232 | case codersdk.NullTime: |
| 233 | if val.Valid { |
| 234 | v = val.Time.Format(time.RFC3339) |
| 235 | } else { |
| 236 | v = nil |
| 237 | } |
no test coverage detected