diff --git a/docs/api/packages/maputil.md b/docs/api/packages/maputil.md index daa8b1a..ce6dc03 100644 --- a/docs/api/packages/maputil.md +++ b/docs/api/packages/maputil.md @@ -79,6 +79,7 @@ import ( - [SortByKey](#SortByKey) - [GetOrDefault](#GetOrDefault) - [FindValuesBy](#FindValuesBy) +- [ToMarkdownTable](#ToMarkdownTable)
@@ -2345,3 +2346,68 @@ func main() { // [b d] } ``` + +### ToMarkdownTable + +将一个 map 切片数据转换为 Markdown 表格字符串。支持自定义表头显示名称和列的显示顺序。
+ +函数签名: + +```go +编辑 +func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string +``` +示例:[运行]() + + +```go +package main + +import ( + "fmt" + "github.com/duke-git/lancet/v2/maputil" +) + +func main() { + // 基本用法:自动从数据中提取列名作为表头 + data := []map[string]interface{}{ + {"name": "Alice", "age": 25, "salary": 50000}, + {"name": "Bob", "age": 30, "salary": 60000}, + } + + result := maputil.ToMarkdownTable(data, nil, nil) + fmt.Println(result) + + // 输出: + // |name|age|salary| + // |---|---|---| + // |Alice|25|50000| + // |Bob|30|60000| + + // 自定义表头显示名称 + headerMap := map[string]string{ + "name": "姓名", + "age": "年龄", + "salary": "薪资", + } + result = maputil.ToMarkdownTable(data, headerMap, nil) + fmt.Println(result) + + // 输出: + // |姓名|年龄|薪资| + // |---|---|---| + // |Alice|25|50000| + // |Bob|30|60000| + + // 自定义列顺序 + columnOrder := []string{"salary", "name"} + result = maputil.ToMarkdownTable(data, nil, columnOrder) + fmt.Println(result) + + // 输出: + // |salary|name| + // |---|---| + // |50000|Alice| + // |60000|Bob| +} +``` \ No newline at end of file diff --git a/maputil/map.go b/maputil/map.go index a033cf0..7598116 100644 --- a/maputil/map.go +++ b/maputil/map.go @@ -8,6 +8,7 @@ import ( "fmt" "reflect" "sort" + "strconv" "strings" "golang.org/x/exp/constraints" @@ -680,3 +681,149 @@ func FindValuesBy[K comparable, V any](m map[K]V, predicate func(key K, value V) return result } + +// ToMarkdownTable converts a slice of maps to a Markdown table. +func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string { + if len(data) == 0 { + return "| |\n|---|\n" + } + + var headers []string + + // 如果提供了columnOrder,则按指定顺序排列 + if len(columnOrder) > 0 { + headers = make([]string, len(columnOrder)) + copy(headers, columnOrder) + } else { + // 否则按自然顺序提取headers + seen := make(map[string]bool) + for _, row := range data { + for k := range row { + if !seen[k] { + seen[k] = true + headers = append(headers, k) + } + } + } + } + + var builder strings.Builder + + // Header row - 使用映射的中文标题 + builder.WriteString("| ") + for i, h := range headers { + // 如果有映射则使用中文标题,否则使用原字段名 + displayHeader := h + if headerMap != nil && headerMap[h] != "" { + displayHeader = headerMap[h] + } + builder.WriteString(displayHeader) + if i < len(headers)-1 { + builder.WriteString(" | ") + } + } + builder.WriteString(" |\n") + + // Separator + builder.WriteString("|") + for i := range headers { + if i > 0 { + builder.WriteString("|") + } + builder.WriteString("---") + } + builder.WriteString("|\n") + + // Data rows + for _, row := range data { + builder.WriteString("| ") + for i, h := range headers { + val, exists := row[h] + var cell string + if !exists { + cell = "" + } else { + cell = formatValue(val) + } + builder.WriteString(cell) + if i < len(headers)-1 { + builder.WriteString(" | ") + } + } + builder.WriteString(" |\n") + } + + return builder.String() +} + +// formatValue formats any value for display in Markdown table +func formatValue(v interface{}) string { + switch val := v.(type) { + case int: + return commaInt64(int64(val)) + case int8: + return commaInt64(int64(val)) + case int16: + return commaInt64(int64(val)) + case int32: + return commaInt64(int64(val)) + case int64: + return commaInt64(val) + case uint: + return commaUint64(uint64(val)) + case uint8: + return commaUint64(uint64(val)) + case uint16: + return commaUint64(uint64(val)) + case uint32: + return commaUint64(uint64(val)) + case uint64: + return commaUint64(val) + case float32: + return fmt.Sprintf("%.2f", val) + case float64: + return fmt.Sprintf("%.2f", val) + case string: + return val + case bool: + return fmt.Sprintf("%t", val) + case nil: + return "" + default: + return fmt.Sprintf("%v", val) + } +} + +// commaInt64 adds comma separators to int64 +func commaInt64(n int64) string { + if n == 0 { + return "0" + } + neg := n < 0 + if neg { + n = -n + } + s := strconv.FormatInt(n, 10) + return addCommas(s) +} + +// commaUint64 adds comma separators to uint64 +func commaUint64(n uint64) string { + if n == 0 { + return "0" + } + s := strconv.FormatUint(n, 10) + return addCommas(s) +} + +// addCommas inserts commas every 3 digits +func addCommas(s string) string { + var result strings.Builder + for i, c := range s { + if i > 0 && (len(s)-i)%3 == 0 { + result.WriteRune(',') + } + result.WriteRune(c) + } + return result.String() +} diff --git a/maputil/map_test.go b/maputil/map_test.go index b85fc47..4a2512e 100644 --- a/maputil/map_test.go +++ b/maputil/map_test.go @@ -1,6 +1,7 @@ package maputil import ( + "fmt" "math/cmplx" "sort" "strconv" @@ -926,3 +927,46 @@ func TestFindValuesBy(t *testing.T) { assert.Equal(tt.expected, result) } } + +func TestToMarkdownTable(t *testing.T) { + // 测试空数据 + emptyResult := ToMarkdownTable([]map[string]interface{}{}, nil, nil) + expectedEmpty := "| |\n|---|\n" + if emptyResult != expectedEmpty { + t.Errorf("Expected empty table, got: %s", emptyResult) + } + + // 测试基本数据 + data := []map[string]interface{}{ + {"name": "Alice", "age": 25, "salary": 50000}, + {"name": "Bob", "age": 30, "salary": 60000}, + } + result := ToMarkdownTable(data, nil, nil) + fmt.Printf("%s", result) + // 验证结果包含预期的表头和数据行 +} + +// 测试自定义列顺序 +func TestToMarkdownTableWithColumnOrder(t *testing.T) { + data := []map[string]interface{}{ + {"name": "Alice", "age": 25, "salary": 50000}, + } + columnOrder := []string{"salary", "name", "age"} + result := ToMarkdownTable(data, nil, columnOrder) + fmt.Printf("%s", result) + // 验证列顺序是否正确 +} + +// 测试自定义表头 +func TestToMarkdownTableWithHeaderMap(t *testing.T) { + data := []map[string]interface{}{ + {"name": "Alice", "age": 25}, + } + headerMap := map[string]string{ + "name": "姓名", + "age": "年龄", + } + result := ToMarkdownTable(data, headerMap, nil) + fmt.Printf("%s", result) + // 验证中文表头是否正确显示 +}