mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-04 12:52:28 +08:00
Merge branch 'rc' into v2
This commit is contained in:
@@ -79,6 +79,7 @@ import (
|
|||||||
- [SortByKey](#SortByKey)
|
- [SortByKey](#SortByKey)
|
||||||
- [GetOrDefault](#GetOrDefault)
|
- [GetOrDefault](#GetOrDefault)
|
||||||
- [FindValuesBy](#FindValuesBy)
|
- [FindValuesBy](#FindValuesBy)
|
||||||
|
- [ToMarkdownTable](#ToMarkdownTable)
|
||||||
|
|
||||||
<div STYLE="page-break-after: always;"></div>
|
<div STYLE="page-break-after: always;"></div>
|
||||||
|
|
||||||
@@ -2345,3 +2346,68 @@ func main() {
|
|||||||
// [b d]
|
// [b d]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### <span id="ToMarkdownTable">ToMarkdownTable</span>
|
||||||
|
|
||||||
|
<p>将一个 map 切片数据转换为 Markdown 表格字符串。支持自定义表头显示名称和列的显示顺序。</p>
|
||||||
|
|
||||||
|
<b>函数签名:</b>
|
||||||
|
|
||||||
|
```go
|
||||||
|
编辑
|
||||||
|
func ToMarkdownTable(data []map[string]interface{}, headerMap map[string]string, columnOrder []string) string
|
||||||
|
```
|
||||||
|
<b>示例:<span style="float:right;display:inline-block;">[运行]()</span></b>
|
||||||
|
|
||||||
|
|
||||||
|
```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|
|
||||||
|
}
|
||||||
|
```
|
||||||
147
maputil/map.go
147
maputil/map.go
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/exp/constraints"
|
"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
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package maputil
|
package maputil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/cmplx"
|
"math/cmplx"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -926,3 +927,46 @@ func TestFindValuesBy(t *testing.T) {
|
|||||||
assert.Equal(tt.expected, result)
|
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)
|
||||||
|
// 验证中文表头是否正确显示
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user