mirror of
https://github.com/duke-git/lancet.git
synced 2026-03-01 00:35:28 +08:00
Compare commits
4 Commits
5b3a59e785
...
f407e51b24
| Author | SHA1 | Date | |
|---|---|---|---|
| f407e51b24 | |||
| 3c6c3a14cf | |||
| 41bafdef92 | |||
| fc624195c7 |
@@ -1,51 +1,51 @@
|
|||||||
-----BEGIN rsa private key-----
|
-----BEGIN rsa private key-----
|
||||||
MIIJKAIBAAKCAgEAudV/zW+ycOExUja9W3ZyhKWA2TN+FqTzfZKPB+btwe4Md0WJ
|
MIIJKQIBAAKCAgEAv25yPU83rpR+acaNoyEOrIBo3gBcykscNL1vbMDbYc5jYib7
|
||||||
TM0+ZdT8UXujltTEWSUhY/qkOiNIutF2CiFWonDQeNzMobLB/pmq1P0Z+LVH4ERs
|
BvxoOkdfeIhjTIDfMVzkzBXAC0eNbMey1/nCcjoFNPXDShsyig8paDl7PG0IVOYp
|
||||||
bcl9zYCfpvTsnIqzjuPe30iozK0Er03qBxsHnWV3WbIl3+1f17T6OD5CkdT+9RCI
|
uXDQ/vi82F7rrXehESrImIBPPDTrZWeg3jqFVkZt6QnB/j7dMKPMeJcarLvwMDKq
|
||||||
D1EqsQ+9aGIeR6cmoB+rxjPLb0xc5oS1hbb3FkiT7VLI2doeqP8Pmwdohbh7XmgJ
|
TM3xguPAAoCAEy9Lylu2zncI8BPyhvz+++j0jBdRl8RYHUyE60BIEROFcHI7lnWM
|
||||||
Qkok+ALxKQ4bCMJ780k2KigKGjXxKlYJq1ZF301sbhvTo2cSci4ieXP0A4B4swSz
|
oH/Uj8cWu8aTRPtNZdk4l9hBHotfi7qvO9b4jB/75Qko/MY5YyrohAwQN/f9zVuT
|
||||||
LKl0G7IX/UbYACn3qNecvQt5OtFM644mqfSUffFg7PefZVZhaUytvU92W+b0LXF2
|
tlfFxKCb1+5Lxq4v4llwUv9eA7euMJf/nDU29m4Oshu7snkYgwB4Cd+bVqS8z9vW
|
||||||
NbjhtVES5HByDwjjF/KOfV7U/o+YmAjlakieYM7pcggfgfqyZWdF70nSvgPgVt5q
|
+brXtP6ZZTJls6pBX4FFukNkCUgiNBuUVDO04AUNSBfAxiHXS88zLdrTRIfM9Mdg
|
||||||
tOnYPeUrQV2aUmZE+BagOQ/HAIKPbhmyMEA3odgqaALsvD/58iVv9syqEEm5trLY
|
oGQapqHaiieQ4GdFbndKfck0AfoSXy0NtWOX4/IZWUphqnyAKO1w8hzK1iydgv4m
|
||||||
A/p2uo0yv3iHrEggEZkjPXkrgbZ6lZNafGaZHs0ANg+7NIJR9joKvXGJkg8E2thp
|
HpjKr5I3sh1ARh9baNUXLH2A9rTQvk3fXsK7RRkwP8gH9BKvtQiHfVVqAh9MPdPy
|
||||||
g9UKl/Z2astZk7o92trMp5DQW1vjV7JW+mEQdztQxE5NgeE8cI/BdqMSyvG2UA7u
|
gHiOUgGgqTQj2xb1KXS1yzJVcgDFYNv8NrE1M8EdCaItfRWU7MkzecGriQFQ42Av
|
||||||
5LqBHXx35s3gPva+eutKnTcRpO3T01PH2sbaiXiiNG5oFUYjocikhY7f3TUCAwEA
|
IMFLMyD25hXDj/mZgaw1CgGoemMJoAm+yu1NRUB4dW1vHqn/UxhcWHrLhJsCAwEA
|
||||||
AQKCAgAISLmxRUTtnkROF22aia2yNxSG2jJJPSIzm1hv8D3yErQQjxN/Tnj1Hij/
|
AQKCAgApujv5pEJpciHqEac/F04ZCPaS6bZQPDYeQuq+ZH2NoMzmEMQoPi3EAlnL
|
||||||
UuUogKSeGrch11cB1nfUCClcaz8K77+DW8htfuQB/wSsCPpi6WXiW/p/bGeExTKY
|
rsMiYncEThDrcZRGgSbRPuh7jow1lPMcm7OhfDM0h+CJQPpdbhDiXLdcnjxYT8Tu
|
||||||
xTtVASPe/058oqcPtLjMPctsdKqCvDa1U2k30cOfgIxU/IWILbgN4aZHFIW0LfDy
|
TyaFTe3UjaRjJZjf8VMcbA3TUyNMbpa3tPJN5ssVNqcz5BAi9eaBwxF/I2cRFm78
|
||||||
GcmixRNGORM1uzJa7EsJ5amX49+g6Sxa/IFCoOQUAYbHEO36ZA5v13BuOZLrUWpB
|
vKDuTaFyLzK2Z5kRp+92QECejaV8wfp9oAVrAYp5sy3iVwR/wc6+Wco5FBQz8PxC
|
||||||
u8S9v7m5zy4wc+d7YqM1EW/N6QMlYLSwNeJZ2urqFx9nTaF3lH8M7+0y1Pz9jRNf
|
GyNv5m11FXfyFWgAbfX6Qcu/ufi8pp4kSy5dhyHsPWaone6NHKf8swkMNq670RyX
|
||||||
sYxIeZZ2OuJcVQoa8qCcsZIMqoACB8z/GTl3mKQ78zOOGIK+mD6f/tusOPRLaUHN
|
YXCwErkKWF+VH+gQ3GGCYyeLF8huZ1SjY+jWmcpdMQK5MBNUTBbTwOTBmO89pdSJ
|
||||||
nQLBEyWVHvIQA/R3fO+FDDT1C4QHaTE8BC9wRLSRPdNG6HIivFqOP/xBloocwsxu
|
J0bhS1zU3/yQfXO4KdiFHW6gWnT7hsKvKMWNnrlSLhA6BnDLqHn+bj1c6oi8f+6k
|
||||||
xbKVfZLy8o/hrqZFfH30FC/Lbh5SAUSX+pOUSwY/eSs5rBQFa00erEvQKFONkc5z
|
ok5x+2j1Qt/6ms5LeTvnr0so7hw6gBl0ONzTBBEn56c5gAoRxjDZB3G0SGqKUlU0
|
||||||
3+AnBanNi4WAWlxusfejai6l8UvzYVm/CPcNT52Zp7sSSeTuRo/8jrtDKQp/tBZA
|
kvHcNojRBTWnV92cYwa/+5WLou2DaWEpRlifau88j5F25+HfPNyRybGCeQQ+93Ha
|
||||||
u3Z0PCQhHU3ei5k9bjc6ZF5LRPjvhIbe0cUmzZtkFlv/HpNC+Eaq/93mInmBMlXK
|
TC6/DYbah7RIroES8tHRMKd2eL8u9aTaW0djVVmk47MzyVDzp+NAdmyBOjFMYA3+
|
||||||
vCpbTCk+YoqpIyT4JYGDS9q4zGm+suurgynmik5ofcyHfgdHAwKCAQEA0LQUo4w5
|
VFx5o+G+M82YfptjaxeLzfibQYf6OprHdF58qLDDe9Ej8EhepQKCAQEAz+AZ0dOW
|
||||||
RXA6PTEaCluPSlFepllZ2uoBwCo950YH5oaEQIwQzyfAk8EpQeK6lJgbsIQeSecf
|
34E8ONKO/dnpZJe4S+LQ6dVaVroxcT3kCw73DlqgFA863koBaqOHdfPCjcR7pQKN
|
||||||
ISZvW4tTFHDjLfWrVgiktWQA7mTHC+/ktXXy357/U9OGEbMirjpw9UQtyh5ddYwe
|
jxLm/KeeNMbkEer8uxnzo42E2Jee12PxJtudQGfL2TwveqfkVeYeZAmmpHOMy/6M
|
||||||
8VonXeyKWDc/ABoazNdDU36AmzqZw0ADXpOXTSC0J47U03GYQxaFXAZzE1Mb/plB
|
PuSa8Kuq5zBQF0KPPOSE2H6ayEWvKqcHOIpVrJ8WEfPdzOVyHr9Y+FagWhB4coTz
|
||||||
1pHAuM10kbjs9sUqqvnh/D52rOKOGM80bpWz8DGC4Y8GQa1/2VC5dtT/7371ghvY
|
7mE+BQQwvgwpxryXXqE3K0XcqZRCkU53QNjqEfWStr6gzW1s50TgVtHpX4W1v6yX
|
||||||
hyfnEZHeH10rkLUW/BA6OXPst3HP7UYZFvW7llz4QB/GmHFrmFnYJf0IEgOmKH4O
|
2zb+3mSw30SP7EpuRqJiscXzNyFjNWGbKnwcYZhemUQOKxkq6uBbVEqui0tN1k7c
|
||||||
KlYeLzFY0ODiPwKCAQEA4/Kl7Inr3tW0eiCl5Jkw23HY9aP4r1lULi3XwRbQmHfO
|
3l/8M+3+dk6STwKCAQEA67/H63+kNDCRGPPC2m9RgKNA6M1hUM0xMijIeB7wcECk
|
||||||
I7tzQ1sY+GEuvx+rJiayuEE08xAVBmz9anOGXrztJoHcKWVMja8Nha08OXMeroki
|
MsYbNwrNb4WfQxEvf5LvU2wRMO+ewjiZe7SIw6WaL41Hh3Rp243tx043e/QM69Ss
|
||||||
9obgvz26x3v8uBukT7+ckwLc1xwaKlflHkosgUTQYhFZndgN4exzIjSjKPzsccdu
|
OJMe17uRvggSeCBRtGd1QOOlFNO2nl+/CeCBWNMcQCfZPJRRBQQAanBVSeFmGqaK
|
||||||
kgTpzqxmOZ/ZLvZF/1KDTZ85HKXYUxSzZQw2WCaA8xKBoPytFItlimzAucwTGKBf
|
Q8Iv24txsH+NQ7cIY7ddyw7HTE1zNz7WqF602qlCqadfvXxvdCOkOE5Q+EbP5k/b
|
||||||
7FDv5IHHaifFCyFcoBUhYcec4dcX6dubWMMdyaVGveBh/frWdbEUkWm2175trqqD
|
YXIuDpWiovtpml5OongnroRprrNZEb34AAqoFLxq1XtKkmVMFYzbCK1anwfqdnA/
|
||||||
Jr7K4UqyLA3+otlWyBL90Mo+SPHlcSe+NAVPTj+7iwKCAQBYgQV/ladz2vPXn0r7
|
n0n0BqqLVmCj5rDAyr5e25RSEVNLHqX+YfXXmqnR9QKCAQAkMb3tJe1IH0VwE2Fo
|
||||||
uXg6e+c3hAym2TWE2GUH/pq7F7Bd7wfx0VnJTtDAL/YPrbGQWXa+wFRjKnluyNai
|
W8/ifvRM6kI75LUlEqhXMlKJhmKH8kmbFIfIepRCkBSe+gFvE73/njEtrzne1gSa
|
||||||
hHzSsKvIAEJY6d+7OOFwHntOuIYWbsa4NatVNjIu0Hm2iQMiA15+yr0UfLbVDcpd
|
5eKCKCs3HK6qVJLD76ptkG5FuMlplGkO+wa9UYxVVIsIGhIU4jWqsziSHtXHf+xy
|
||||||
PpBo6qkS1PaoIa1IJsGuGydSpCQ1gPjlDZ0TTcjUKmjDbbi/KS9l+HgDFiw0MmyM
|
8puPYTx1esiStYCXzGJSh+Ce/J8sPkrmd1KwQWccaW7BVrv20pVWQ0YBxJa64t7l
|
||||||
n29d9p7xgqZi4dpR1oGL49LIUpPL+DMYlB6DG6Br99+ulQUz+xMB6e0Y48MJoGIh
|
yFJh0yZ8CJAFjdV9BV6N1F65QMuIsvyHqytueVYT8o8pLsV2p15c8F1Egw/fgyK0
|
||||||
ytD+vMzSd885Lf/ki08xv9hD9FFoomRkTRVa8D5AjVksQvF5MjL0WQCI05xZRwPz
|
zUhN+Su4Sr/qei/98mdGvSb6P7/lUlVuVEYvROOPH99nDtXTVRpJbVPRuF6+X/sF
|
||||||
EGrhAoIBAQChGQM06cCeSvBzA5Havn1uCcboy8rcukgpHtMFrscbikhQrpDmgIJk
|
eGrLAoIBAQCSdpeyC57sfa/nnKnWI4qzLLnK6K4pERIbY5MIbdBbLQ3WLZYC4Ec5
|
||||||
P+KWxp3hp6XVXJg8VBhX4z0yN5U2bVU5Sru7MdFprNbkq6sNexOrDFZ+XpKF9e2E
|
nf6QIEEuNQ9S+LTFfllXuOpCHKtLQbtFB7UExqJtsQOk60c1Ty4n5f0JOP3Uccfe
|
||||||
QFc6EqcMiYHx0Csdh8niNR5DSu6rKWQQeuyYBnLBQaeY/BR3ylCclPLLFdfb7bGN
|
FA3Fa+7W8d+67LNG+TFfu4Rokn5JvP60LQ2dwVeEkjPf7OJQACT7MbR83Ev3Te7Y
|
||||||
djA65WhQ6xLLEAV//qGlDdM/TeM2Z3fo0iJ1ET6Nb3sC2ptWdCjm1akVTZpNJ380
|
BKazByfDK8yttwTCtut5yyR5fj1GDBpKZJ3qj0B/GxWPlbhgRz4qjDpzlAhhLz3X
|
||||||
wgibNifNJ0HhZf61CZvn9gGTOMpbkYgud18p7VYV9WFw54KGdRn1QKLSBjNCB9Vm
|
m/LD4QthjNy6Y7F0xB/2EdvdysEaAOUutGZkBWL7kIMUJP9EBhr4ckxUnhFpgtMG
|
||||||
FznoA6w2WF2zasucKAEc+JaPE1WaGqbDAoIBAC4yD/r30E3itMDKyhlHzkRT4NNx
|
Wwv46p8cVqGAMem6sdDdvasJGT9V62NxAoIBAQDBZ8zVRyZAHGPclJPZ8JtDuaI2
|
||||||
X/6gGE2RPwoP8UhMyYiBh1cbtqSZE0zXsO8I02GnJ362boG/LOtMkCBbwH1tfDKU
|
m9tIP2eXefsnAaaSpyQlpEHCr+XmxhkKT0mRIw7y+zR1micmiD2gRu/2ITvcs19x
|
||||||
1iL9obUEf56JGWyPL/OTbJzcUYgiIvH7R2HGRaLd1ybiAdFjM/VNVyV/855mM7J2
|
DIPidRt9gmqicGmQvIJYdYXJ+g+94XPVtC3l8dyIdHVndPsaqs+ADsjw5tQKwgAQ
|
||||||
zWcPLR/KdHv4vlrckZW2kqG4ai/PwY8EG4TjPkhkx90gy6XLtwXnIwdsNAXYsHh4
|
s4JIcc80un4EbHch3Fa0UOKwj79pV5yWeXHN6diudjyF7yYhVkl9TlHG3NeHsepN
|
||||||
dAyQNiHh1Ucr8Id0FVIHuOERjCoaSCttznzQIH+I6RKwFVxNqsRrMQaZBYPMab4X
|
WHsf2qQQgGRjR3D1a3AkjPuaQWxg+3YnVO4KAuem55QGhEvuYATd/elxzN3MVtJC
|
||||||
9G4exHRJ/02wJHTHKMeU7Ew15quV4+v19HgJp5Yu6Ne1Hu1sz7XGMtOhUPM=
|
9MAghroYilaN7RtA4tmXLb9d0LBxHzYpyANNlpCSRvDoJsLgPSbrvwbkjAlL
|
||||||
-----END rsa private key-----
|
-----END rsa private key-----
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
-----BEGIN rsa public key-----
|
-----BEGIN rsa public key-----
|
||||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAudV/zW+ycOExUja9W3Zy
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv25yPU83rpR+acaNoyEO
|
||||||
hKWA2TN+FqTzfZKPB+btwe4Md0WJTM0+ZdT8UXujltTEWSUhY/qkOiNIutF2CiFW
|
rIBo3gBcykscNL1vbMDbYc5jYib7BvxoOkdfeIhjTIDfMVzkzBXAC0eNbMey1/nC
|
||||||
onDQeNzMobLB/pmq1P0Z+LVH4ERsbcl9zYCfpvTsnIqzjuPe30iozK0Er03qBxsH
|
cjoFNPXDShsyig8paDl7PG0IVOYpuXDQ/vi82F7rrXehESrImIBPPDTrZWeg3jqF
|
||||||
nWV3WbIl3+1f17T6OD5CkdT+9RCID1EqsQ+9aGIeR6cmoB+rxjPLb0xc5oS1hbb3
|
VkZt6QnB/j7dMKPMeJcarLvwMDKqTM3xguPAAoCAEy9Lylu2zncI8BPyhvz+++j0
|
||||||
FkiT7VLI2doeqP8Pmwdohbh7XmgJQkok+ALxKQ4bCMJ780k2KigKGjXxKlYJq1ZF
|
jBdRl8RYHUyE60BIEROFcHI7lnWMoH/Uj8cWu8aTRPtNZdk4l9hBHotfi7qvO9b4
|
||||||
301sbhvTo2cSci4ieXP0A4B4swSzLKl0G7IX/UbYACn3qNecvQt5OtFM644mqfSU
|
jB/75Qko/MY5YyrohAwQN/f9zVuTtlfFxKCb1+5Lxq4v4llwUv9eA7euMJf/nDU2
|
||||||
ffFg7PefZVZhaUytvU92W+b0LXF2NbjhtVES5HByDwjjF/KOfV7U/o+YmAjlakie
|
9m4Oshu7snkYgwB4Cd+bVqS8z9vW+brXtP6ZZTJls6pBX4FFukNkCUgiNBuUVDO0
|
||||||
YM7pcggfgfqyZWdF70nSvgPgVt5qtOnYPeUrQV2aUmZE+BagOQ/HAIKPbhmyMEA3
|
4AUNSBfAxiHXS88zLdrTRIfM9MdgoGQapqHaiieQ4GdFbndKfck0AfoSXy0NtWOX
|
||||||
odgqaALsvD/58iVv9syqEEm5trLYA/p2uo0yv3iHrEggEZkjPXkrgbZ6lZNafGaZ
|
4/IZWUphqnyAKO1w8hzK1iydgv4mHpjKr5I3sh1ARh9baNUXLH2A9rTQvk3fXsK7
|
||||||
Hs0ANg+7NIJR9joKvXGJkg8E2thpg9UKl/Z2astZk7o92trMp5DQW1vjV7JW+mEQ
|
RRkwP8gH9BKvtQiHfVVqAh9MPdPygHiOUgGgqTQj2xb1KXS1yzJVcgDFYNv8NrE1
|
||||||
dztQxE5NgeE8cI/BdqMSyvG2UA7u5LqBHXx35s3gPva+eutKnTcRpO3T01PH2sba
|
M8EdCaItfRWU7MkzecGriQFQ42AvIMFLMyD25hXDj/mZgaw1CgGoemMJoAm+yu1N
|
||||||
iXiiNG5oFUYjocikhY7f3TUCAwEAAQ==
|
RUB4dW1vHqn/UxhcWHrLhJsCAwEAAQ==
|
||||||
-----END rsa public key-----
|
-----END rsa public key-----
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
// 验证中文表头是否正确显示
|
||||||
|
}
|
||||||
|
|||||||
+2
-23
@@ -24,10 +24,8 @@ var (
|
|||||||
alphaNumericMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
|
alphaNumericMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
|
||||||
numberRegexMatcher *regexp.Regexp = regexp.MustCompile(`\d`)
|
numberRegexMatcher *regexp.Regexp = regexp.MustCompile(`\d`)
|
||||||
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
|
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
|
||||||
urlMatcher *regexp.Regexp = regexp.MustCompile(`^((ftp|http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`)
|
// dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
|
||||||
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
|
dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)*(?:xn--[a-zA-Z0-9\-]{1,59}|[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)$`)
|
||||||
// emailMatcher *regexp.Regexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
|
|
||||||
emailMatcher *regexp.Regexp = regexp.MustCompile(`\w+(-+.\w+)*@\w+(-.\w+)*.\w+(-.\w+)*`)
|
|
||||||
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
|
chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`)
|
||||||
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])`)
|
chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])`)
|
||||||
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
|
chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]")
|
||||||
@@ -40,7 +38,6 @@ var (
|
|||||||
visaMatcher *regexp.Regexp = regexp.MustCompile(`^4[0-9]{12}(?:[0-9]{3})?$`)
|
visaMatcher *regexp.Regexp = regexp.MustCompile(`^4[0-9]{12}(?:[0-9]{3})?$`)
|
||||||
masterCardMatcher *regexp.Regexp = regexp.MustCompile(`^5[1-5][0-9]{14}$`)
|
masterCardMatcher *regexp.Regexp = regexp.MustCompile(`^5[1-5][0-9]{14}$`)
|
||||||
americanExpressMatcher *regexp.Regexp = regexp.MustCompile(`^3[47][0-9]{13}$`)
|
americanExpressMatcher *regexp.Regexp = regexp.MustCompile(`^3[47][0-9]{13}$`)
|
||||||
unionPayMatcher *regexp.Regexp = regexp.MustCompile(`^62[0-5]\\d{13,16}$`)
|
|
||||||
chinaUnionPayMatcher *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`)
|
chinaUnionPayMatcher *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`)
|
||||||
chineseHMPassportMatcher *regexp.Regexp = regexp.MustCompile(`^[CM]\d{8}$`)
|
chineseHMPassportMatcher *regexp.Regexp = regexp.MustCompile(`^[CM]\d{8}$`)
|
||||||
)
|
)
|
||||||
@@ -635,24 +632,6 @@ func IsChinaUnionPay(cardNo string) bool {
|
|||||||
return chinaUnionPayMatcher.MatchString(cardNo)
|
return chinaUnionPayMatcher.MatchString(cardNo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// luhnCheck checks if the credit card number is valid using the Luhn algorithm.
|
|
||||||
func luhnCheck(card string) bool {
|
|
||||||
var sum int
|
|
||||||
alt := false
|
|
||||||
for i := len(card) - 1; i >= 0; i-- {
|
|
||||||
n := int(card[i] - '0')
|
|
||||||
if alt {
|
|
||||||
n *= 2
|
|
||||||
if n > 9 {
|
|
||||||
n -= 9
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sum += n
|
|
||||||
alt = !alt
|
|
||||||
}
|
|
||||||
return sum%10 == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPassport checks if the passport number is valid for a given country.
|
// IsPassport checks if the passport number is valid for a given country.
|
||||||
// country is a two-letter country code (ISO 3166-1 alpha-2).
|
// country is a two-letter country code (ISO 3166-1 alpha-2).
|
||||||
// Play: todo
|
// Play: todo
|
||||||
|
|||||||
@@ -477,7 +477,8 @@ func TestIsDns(t *testing.T) {
|
|||||||
{"abc.com", true},
|
{"abc.com", true},
|
||||||
{"123.cn", true},
|
{"123.cn", true},
|
||||||
{"a.b.com", true},
|
{"a.b.com", true},
|
||||||
{"a.b.c", false},
|
{"a.b.c", true},
|
||||||
|
{"www.xn--6qq986b3xl.xn--fiqs8s.com", true},
|
||||||
{"a@b.com", false},
|
{"a@b.com", false},
|
||||||
{"http://abc.com", false},
|
{"http://abc.com", false},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user