add room id to !pm mailboxes, fixes #8; update deps
This commit is contained in:
parent
0fe6c7573d
commit
83d839e561
293 changed files with 64080 additions and 38758 deletions
48
go.mod
48
go.mod
|
|
@ -6,18 +6,18 @@ require (
|
|||
github.com/archdx/zerolog-sentry v1.8.5
|
||||
github.com/emersion/go-msgauth v0.7.0
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6
|
||||
github.com/emersion/go-smtp v0.22.0
|
||||
github.com/emersion/go-smtp v0.24.0
|
||||
github.com/etkecc/go-env v1.2.1
|
||||
github.com/etkecc/go-fswatcher v1.0.1
|
||||
github.com/etkecc/go-healthchecks/v2 v2.2.2
|
||||
github.com/etkecc/go-kit v1.7.0
|
||||
github.com/etkecc/go-linkpearl v0.0.0-20250617213914-419fd498ee39
|
||||
github.com/etkecc/go-kit v1.7.5
|
||||
github.com/etkecc/go-linkpearl v0.0.0-20250916123206-78ebb36aa071
|
||||
github.com/etkecc/go-mxidwc v1.0.1
|
||||
github.com/etkecc/go-secgen v1.4.0
|
||||
github.com/etkecc/go-validator/v2 v2.2.4
|
||||
github.com/etkecc/go-validator/v2 v2.2.6
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.9
|
||||
github.com/getsentry/sentry-go v0.34.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.10
|
||||
github.com/getsentry/sentry-go v0.35.3
|
||||
github.com/jhillyerd/enmime/v2 v2.2.0
|
||||
github.com/kvannotten/mailstrip v0.0.0-20200711213611-0002f5c0467e
|
||||
github.com/lib/pq v1.10.9
|
||||
|
|
@ -26,9 +26,9 @@ require (
|
|||
github.com/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/swaggo/swag v1.16.3
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
|
||||
maunium.net/go/mautrix v0.24.1
|
||||
modernc.org/sqlite v1.38.0
|
||||
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9
|
||||
maunium.net/go/mautrix v0.25.1
|
||||
modernc.org/sqlite v1.39.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
@ -39,6 +39,7 @@ require (
|
|||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/etkecc/go-trysmtp v1.1.5 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
|
|
@ -54,31 +55,32 @@ require (
|
|||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
|
||||
github.com/olekukonko/errors v1.1.0 // indirect
|
||||
github.com/olekukonko/ll v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v1.0.7 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb // indirect
|
||||
github.com/olekukonko/ll v0.1.1 // indirect
|
||||
github.com/olekukonko/tablewriter v1.1.0 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/yuin/goldmark v1.7.12 // indirect
|
||||
go.mau.fi/util v0.8.8 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/net v0.41.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
github.com/yuin/goldmark v1.7.13 // indirect
|
||||
go.mau.fi/util v0.9.1 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
modernc.org/libc v1.66.0 // indirect
|
||||
modernc.org/libc v1.66.10 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
||||
|
|
|
|||
128
go.sum
128
go.sum
|
|
@ -16,6 +16,8 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU
|
|||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
|
@ -27,34 +29,34 @@ github.com/emersion/go-msgauth v0.7.0 h1:vj2hMn6KhFtW41kshIBTXvp6KgYSqpA/ZN9Pv4g
|
|||
github.com/emersion/go-msgauth v0.7.0/go.mod h1:mmS9I6HkSovrNgq0HNXTeu8l3sRAAuQ9RMvbM4KU7Ck=
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk=
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.22.0 h1:/d3HWxkZZ4riB+0kzfoODh9X+xyCrLEezMnAAa1LEMU=
|
||||
github.com/emersion/go-smtp v0.22.0/go.mod h1:ZtRRkbTyp2XTHCA+BmyTFTrj8xY4I+b4McvHxCU2gsQ=
|
||||
github.com/emersion/go-smtp v0.24.0 h1:g6AfoF140mvW0vLNPD/LuCBLEAdlxOjIXqbIkJIS6Wk=
|
||||
github.com/emersion/go-smtp v0.24.0/go.mod h1:ZtRRkbTyp2XTHCA+BmyTFTrj8xY4I+b4McvHxCU2gsQ=
|
||||
github.com/etkecc/go-env v1.2.1 h1:b/mIa8D1d9hc3rI8h5bEtBHsnKkBZ6UbmYPog3QIPTU=
|
||||
github.com/etkecc/go-env v1.2.1/go.mod h1:yTs1DWEsllAZYA417r4V+OmMuYinGXtBzKkLGNvCv5c=
|
||||
github.com/etkecc/go-fswatcher v1.0.1 h1:n9hqtjzTS3ETb9hcZe1EqYA1lkkhlzZlztu3hXwFDQc=
|
||||
github.com/etkecc/go-fswatcher v1.0.1/go.mod h1:O5TODJ9z6Qb7X+snqHbB+F0Pah6G497Wdg0SFE/UYpE=
|
||||
github.com/etkecc/go-healthchecks/v2 v2.2.2 h1:YV7e+Ga8JY3aZX4Qf6Q1Ca+DnqbT5Drjli3PufB4J1g=
|
||||
github.com/etkecc/go-healthchecks/v2 v2.2.2/go.mod h1:IowWGN4F6By6z0eh63+639OscylgtdvT9ITjfN6hnZA=
|
||||
github.com/etkecc/go-kit v1.7.0 h1:jxJ+tWfWwYqgNgfdbZ2va/M0AHyDdKwc8lYGXrFuWsc=
|
||||
github.com/etkecc/go-kit v1.7.0/go.mod h1:yikghi8YaYbTjRXNtx82g0LFv90YqZi2vLf5Chw0ysg=
|
||||
github.com/etkecc/go-linkpearl v0.0.0-20250617213914-419fd498ee39 h1:wZ+Iv47qoBy3jzzVgzS52KkQWcddUuDsO5s59J2q+1g=
|
||||
github.com/etkecc/go-linkpearl v0.0.0-20250617213914-419fd498ee39/go.mod h1:v4QqBXgDZ4Hz235SqyaHde1FL+dNpdnjX4tTR4kxcaI=
|
||||
github.com/etkecc/go-kit v1.7.5 h1:DbHTbJ69Jnt2d8mjOyKA9f2ARxvi8xgnYgVMlMZd3gI=
|
||||
github.com/etkecc/go-kit v1.7.5/go.mod h1:ZFeQrvlMIV6OeZ4XJ090kkyNYRRN4+5wcg1vjnOKEGI=
|
||||
github.com/etkecc/go-linkpearl v0.0.0-20250916123206-78ebb36aa071 h1:wcvjEg8togYMrICjibOrmt0ShBKM2vpwO3Tnt3uxq88=
|
||||
github.com/etkecc/go-linkpearl v0.0.0-20250916123206-78ebb36aa071/go.mod h1:pTOgDOHhro5wOzu1pUYLJMCeyH4CEhijj6tohY5Q2H4=
|
||||
github.com/etkecc/go-mxidwc v1.0.1 h1:t4Kq3FxSlQjt1i7RpzE5q3cOWjJ0vrTzzGZRSpgh8mg=
|
||||
github.com/etkecc/go-mxidwc v1.0.1/go.mod h1:WFlntcH4mdual/gNi6X7a6rSJERNuLjdrwM3K/tucQA=
|
||||
github.com/etkecc/go-secgen v1.4.0 h1:PapLpen3aIqG2LDu+U6KWDb4SMW2UGuvetpJjd86yUc=
|
||||
github.com/etkecc/go-secgen v1.4.0/go.mod h1:6dkfdOtmtVxkj1hIJM6U6yA7frSNpr1H5brz5t18Y3I=
|
||||
github.com/etkecc/go-trysmtp v1.1.5 h1:MQ4l4bE9nWUqRKoJOalGHGLkb/hRmzbcBMlClufEOng=
|
||||
github.com/etkecc/go-trysmtp v1.1.5/go.mod h1:J4zQu+kgM37xQY148wUz0Dm6y+YhtzMghAv/AaXt0sQ=
|
||||
github.com/etkecc/go-validator/v2 v2.2.4 h1:E4sE4FpYPVP6VRvnndni+8qUXbeSuHxkniFsi6uCRFQ=
|
||||
github.com/etkecc/go-validator/v2 v2.2.4/go.mod h1:5VX2VICgMALoFBiGc02p/6RjQ8ifRxZ0KOuLAH5fTe0=
|
||||
github.com/etkecc/go-validator/v2 v2.2.6 h1:ybP+zBcXiVf9bvx4BR5OAyjFNYPedYFIN+oGglek/1k=
|
||||
github.com/etkecc/go-validator/v2 v2.2.6/go.mod h1:1jerRLtl69ugBSoGZJJreyzIIYUoHtp3Xj/fmK+12Rc=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/getsentry/sentry-go v0.34.0 h1:1FCHBVp8TfSc8L10zqSwXUZNiOSF+10qw4czjarTiY4=
|
||||
github.com/getsentry/sentry-go v0.34.0/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/getsentry/sentry-go v0.35.3 h1:u5IJaEqZyPdWqe/hKlBKBBnMTSxB/HenCqF3QLabeds=
|
||||
github.com/getsentry/sentry-go v0.35.3/go.mod h1:mdL49ixwT2yi57k5eh7mpnDyPybixPzlzEJFu0Z76QA=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
|
|
@ -109,10 +111,10 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
|||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mcnijman/go-emailaddress v1.1.1 h1:AGhgVDG3tCDaL0/Vc6erlPQjDuDN3dAT7rRdgFtetr0=
|
||||
github.com/mcnijman/go-emailaddress v1.1.1/go.mod h1:5whZrhS8Xp5LxO8zOD35BC+b76kROtsh+dPomeRt/II=
|
||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
|
||||
|
|
@ -122,14 +124,16 @@ github.com/mileusna/crontab v1.2.0/go.mod h1:dbns64w/u3tUnGZGf8pAa76ZqOfeBX4olW4
|
|||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
|
||||
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
|
||||
github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
|
||||
github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
|
||||
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
||||
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw=
|
||||
github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
|
||||
github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb h1:3PrKuO92dUTMrQ9dx0YNejC6U/Si6jqKmyQ9vWjwqR4=
|
||||
github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/olekukonko/ll v0.1.1 h1:9Dfeed5/Mgaxb9lHRAftLK9pVfYETvHn+If6lywVhJc=
|
||||
github.com/olekukonko/ll v0.1.1/go.mod h1:2dJo+hYZcJMLMbKwHEWvxCUbAOLc/CXWS9noET22Mdo=
|
||||
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
|
||||
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
||||
github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490 h1:QTvNkZ5ylY0PGgA+Lih+GdboMLY/G9SEGLMEGVjTVA4=
|
||||
github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
|
@ -140,7 +144,6 @@ github.com/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39 h1:2by0+lF6NfaNWhl
|
|||
github.com/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39/go.mod h1:idX/fPqwjX31YMTF2iIpEpNApV2YbQhSFr4iIhJaqp4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
|
|
@ -153,55 +156,56 @@ github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02n
|
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
|
||||
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY=
|
||||
github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c=
|
||||
go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
go.mau.fi/util v0.9.1 h1:A+XKHRsjKkFi2qOm4RriR1HqY2hoOXNS3WFHaC89r2Y=
|
||||
go.mau.fi/util v0.9.1/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A=
|
||||
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
@ -214,20 +218,20 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
maunium.net/go/mautrix v0.24.1 h1:09/xi4qTeA03g1n/DPmmqAlT8Cx4QrgwiPlmLVzA9AU=
|
||||
maunium.net/go/mautrix v0.24.1/go.mod h1:Xy6o+pXmbqmgWsUWh15EQ1eozjC+k/VT/7kloByv9PI=
|
||||
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
|
||||
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
|
||||
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||
modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA=
|
||||
modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
maunium.net/go/mautrix v0.25.1 h1:+xe3eXtQNcDPU/HoWzvSOA5YX57iqlYI1TXf/fM0KWs=
|
||||
maunium.net/go/mautrix v0.25.1/go.mod h1:iSueLJ/2fBaNrsTObGqi1j0cl/loxrtAjmjay1scYD8=
|
||||
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
|
||||
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
|
||||
modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/goabi0 v0.0.3 h1:y81b9r3asCh6Xtse6Nz85aYGB0cG3M3U6222yap1KWI=
|
||||
modernc.org/goabi0 v0.0.3/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.66.0 h1:eoFuDb1ozurUY5WSWlgvxHp0FuL+AncMwNjFqGYMJPQ=
|
||||
modernc.org/libc v1.66.0/go.mod h1:AiZxInURfEJx516LqEaFcrC+X38rt9G7+8ojIXQKHbo=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
|
||||
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
|
|
@ -236,8 +240,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
|||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI=
|
||||
modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE=
|
||||
modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY=
|
||||
modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
func (b *Bot) sendMailboxes(ctx context.Context) {
|
||||
evt := eventFromContext(ctx)
|
||||
mailboxes := map[string]config.Room{}
|
||||
rooms := map[string]id.RoomID{}
|
||||
slice := []string{}
|
||||
b.rooms.Range(func(key, value any) bool {
|
||||
if key == nil {
|
||||
|
|
@ -43,6 +44,7 @@ func (b *Bot) sendMailboxes(ctx context.Context) {
|
|||
}
|
||||
|
||||
mailboxes[mailbox] = cfg
|
||||
rooms[mailbox] = roomID
|
||||
slice = append(slice, mailbox)
|
||||
return true
|
||||
})
|
||||
|
|
@ -61,7 +63,11 @@ func (b *Bot) sendMailboxes(ctx context.Context) {
|
|||
msg.WriteString(utils.EmailsList(mailbox, cfg.Domain()))
|
||||
msg.WriteString("` by ")
|
||||
msg.WriteString(cfg.Owner())
|
||||
msg.WriteString("\n")
|
||||
msg.WriteString(" in [")
|
||||
msg.WriteString(rooms[mailbox].String())
|
||||
msg.WriteString("](https://matrix.to/#/")
|
||||
msg.WriteString(rooms[mailbox].String())
|
||||
msg.WriteString(")\n")
|
||||
}
|
||||
|
||||
b.lp.SendNotice(ctx, evt.RoomID, msg.String(), linkpearl.RelatesTo(evt.ID))
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func NewPlainAuthServer(ctx context.Context, bot matrixbot, conn *smtp.Conn, aut
|
|||
func (a *PlainAuthServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
||||
if a.done {
|
||||
err = sasl.ErrUnexpectedClientResponse
|
||||
return
|
||||
return challenge, done, err
|
||||
}
|
||||
// No initial response, send an empty challenge
|
||||
if response == nil {
|
||||
|
|
@ -51,7 +51,7 @@ func (a *PlainAuthServer) Next(response []byte) (challenge []byte, done bool, er
|
|||
if len(parts) != 3 {
|
||||
a.bot.BanAuth(a.ctx, a.conn.Conn().RemoteAddr())
|
||||
err = errors.New("sasl: invalid response. Don't bother me anymore, kupo")
|
||||
return
|
||||
return challenge, done, err
|
||||
}
|
||||
|
||||
identity := string(parts[0])
|
||||
|
|
@ -60,5 +60,5 @@ func (a *PlainAuthServer) Next(response []byte) (challenge []byte, done bool, er
|
|||
|
||||
err = a.authenticate(identity, username, password)
|
||||
done = true
|
||||
return
|
||||
return challenge, done, err
|
||||
}
|
||||
|
|
|
|||
21
vendor/github.com/clipperhouse/uax29/v2/LICENSE
generated
vendored
Normal file
21
vendor/github.com/clipperhouse/uax29/v2/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Matt Sherman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
82
vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md
generated
vendored
Normal file
82
vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
An implementation of grapheme cluster boundaries from [Unicode text segmentation](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) (UAX 29), for Unicode version 15.0.0.
|
||||
|
||||
## Quick start
|
||||
|
||||
```
|
||||
go get "github.com/clipperhouse/uax29/v2/graphemes"
|
||||
```
|
||||
|
||||
```go
|
||||
import "github.com/clipperhouse/uax29/v2/graphemes"
|
||||
|
||||
text := "Hello, 世界. Nice dog! 👍🐶"
|
||||
|
||||
tokens := graphemes.FromString(text)
|
||||
|
||||
for tokens.Next() { // Next() returns true until end of data
|
||||
fmt.Println(tokens.Value()) // Do something with the current grapheme
|
||||
}
|
||||
```
|
||||
|
||||
[](https://pkg.go.dev/github.com/clipperhouse/uax29/v2/graphemes)
|
||||
|
||||
_A grapheme is a “single visible character”, which might be a simple as a single letter, or a complex emoji that consists of several Unicode code points._
|
||||
|
||||
## Conformance
|
||||
|
||||
We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Tests29). Status:
|
||||
|
||||

|
||||
|
||||
## APIs
|
||||
|
||||
### If you have a `string`
|
||||
|
||||
```go
|
||||
text := "Hello, 世界. Nice dog! 👍🐶"
|
||||
|
||||
tokens := graphemes.FromString(text)
|
||||
|
||||
for tokens.Next() { // Next() returns true until end of data
|
||||
fmt.Println(tokens.Value()) // Do something with the current grapheme
|
||||
}
|
||||
```
|
||||
|
||||
### If you have an `io.Reader`
|
||||
|
||||
`FromReader` embeds a [`bufio.Scanner`](https://pkg.go.dev/bufio#Scanner), so just use those methods.
|
||||
|
||||
```go
|
||||
r := getYourReader() // from a file or network maybe
|
||||
tokens := graphemes.FromReader(r)
|
||||
|
||||
for tokens.Scan() { // Scan() returns true until error or EOF
|
||||
fmt.Println(tokens.Text()) // Do something with the current grapheme
|
||||
}
|
||||
|
||||
if tokens.Err() != nil { // Check the error
|
||||
log.Fatal(tokens.Err())
|
||||
}
|
||||
```
|
||||
|
||||
### If you have a `[]byte`
|
||||
|
||||
```go
|
||||
b := []byte("Hello, 世界. Nice dog! 👍🐶")
|
||||
|
||||
tokens := graphemes.FromBytes(b)
|
||||
|
||||
for tokens.Next() { // Next() returns true until end of data
|
||||
fmt.Println(tokens.Value()) // Do something with the current grapheme
|
||||
}
|
||||
```
|
||||
|
||||
### Performance
|
||||
|
||||
On a Mac M2 laptop, we see around 200MB/s, or around 100 million graphemes per second. You should see ~constant memory, and no allocations.
|
||||
|
||||
### Invalid inputs
|
||||
|
||||
Invalid UTF-8 input is considered undefined behavior. We test to ensure that bad inputs will not cause pathological outcomes, such as a panic or infinite loop. Callers should expect “garbage-in, garbage-out”.
|
||||
|
||||
Your pipeline should probably include a call to [`utf8.Valid()`](https://pkg.go.dev/unicode/utf8#Valid).
|
||||
28
vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go
generated
vendored
Normal file
28
vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package graphemes
|
||||
|
||||
import "github.com/clipperhouse/uax29/v2/internal/iterators"
|
||||
|
||||
type Iterator[T iterators.Stringish] struct {
|
||||
*iterators.Iterator[T]
|
||||
}
|
||||
|
||||
var (
|
||||
splitFuncString = splitFunc[string]
|
||||
splitFuncBytes = splitFunc[[]byte]
|
||||
)
|
||||
|
||||
// FromString returns an iterator for the grapheme clusters in the input string.
|
||||
// Iterate while Next() is true, and access the grapheme via Value().
|
||||
func FromString(s string) Iterator[string] {
|
||||
return Iterator[string]{
|
||||
iterators.New(splitFuncString, s),
|
||||
}
|
||||
}
|
||||
|
||||
// FromBytes returns an iterator for the grapheme clusters in the input bytes.
|
||||
// Iterate while Next() is true, and access the grapheme via Value().
|
||||
func FromBytes(b []byte) Iterator[[]byte] {
|
||||
return Iterator[[]byte]{
|
||||
iterators.New(splitFuncBytes, b),
|
||||
}
|
||||
}
|
||||
25
vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go
generated
vendored
Normal file
25
vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Package graphemes implements Unicode grapheme cluster boundaries: https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries
|
||||
package graphemes
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Scanner struct {
|
||||
*bufio.Scanner
|
||||
}
|
||||
|
||||
// FromReader returns a Scanner, to split graphemes per
|
||||
// https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries.
|
||||
//
|
||||
// It embeds a [bufio.Scanner], so you can use its methods.
|
||||
//
|
||||
// Iterate through graphemes by calling Scan() until false, then check Err().
|
||||
func FromReader(r io.Reader) *Scanner {
|
||||
sc := bufio.NewScanner(r)
|
||||
sc.Split(SplitFunc)
|
||||
return &Scanner{
|
||||
Scanner: sc,
|
||||
}
|
||||
}
|
||||
174
vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go
generated
vendored
Normal file
174
vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
package graphemes
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
|
||||
"github.com/clipperhouse/uax29/v2/internal/iterators"
|
||||
)
|
||||
|
||||
// is determines if lookup intersects propert(ies)
|
||||
func (lookup property) is(properties property) bool {
|
||||
return (lookup & properties) != 0
|
||||
}
|
||||
|
||||
const _Ignore = _Extend
|
||||
|
||||
// SplitFunc is a bufio.SplitFunc implementation of Unicode grapheme cluster segmentation, for use with bufio.Scanner.
|
||||
//
|
||||
// See https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries.
|
||||
var SplitFunc bufio.SplitFunc = splitFunc[[]byte]
|
||||
|
||||
func splitFunc[T iterators.Stringish](data T, atEOF bool) (advance int, token T, err error) {
|
||||
var empty T
|
||||
if len(data) == 0 {
|
||||
return 0, empty, nil
|
||||
}
|
||||
|
||||
// These vars are stateful across loop iterations
|
||||
var pos int
|
||||
var lastExIgnore property = 0 // "last excluding ignored categories"
|
||||
var lastLastExIgnore property = 0 // "last one before that"
|
||||
var regionalIndicatorCount int
|
||||
|
||||
// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property
|
||||
// to the right of the ×, from which we look back or forward
|
||||
|
||||
current, w := lookup(data[pos:])
|
||||
if w == 0 {
|
||||
if !atEOF {
|
||||
// Rune extends past current data, request more
|
||||
return 0, empty, nil
|
||||
}
|
||||
pos = len(data)
|
||||
return pos, data[:pos], nil
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB1
|
||||
// Start of text always advances
|
||||
pos += w
|
||||
|
||||
for {
|
||||
eot := pos == len(data) // "end of text"
|
||||
|
||||
if eot {
|
||||
if !atEOF {
|
||||
// Token extends past current data, request more
|
||||
return 0, empty, nil
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB2
|
||||
break
|
||||
}
|
||||
|
||||
/*
|
||||
We've switched the evaluation order of GB1↓ and GB2↑. It's ok:
|
||||
because we've checked for len(data) at the top of this function,
|
||||
sot and eot are mutually exclusive, order doesn't matter.
|
||||
*/
|
||||
|
||||
// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property
|
||||
// to the right of the ×, from which we look back or forward
|
||||
|
||||
// Remember previous properties to avoid lookups/lookbacks
|
||||
last := current
|
||||
if !last.is(_Ignore) {
|
||||
lastLastExIgnore = lastExIgnore
|
||||
lastExIgnore = last
|
||||
}
|
||||
|
||||
current, w = lookup(data[pos:])
|
||||
if w == 0 {
|
||||
if atEOF {
|
||||
// Just return the bytes, we can't do anything with them
|
||||
pos = len(data)
|
||||
break
|
||||
}
|
||||
// Rune extends past current data, request more
|
||||
return 0, empty, nil
|
||||
}
|
||||
|
||||
// Optimization: no rule can possibly apply
|
||||
if current|last == 0 { // i.e. both are zero
|
||||
break
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB3
|
||||
if current.is(_LF) && last.is(_CR) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB4
|
||||
// https://unicode.org/reports/tr29/#GB5
|
||||
if (current | last).is(_Control | _CR | _LF) {
|
||||
break
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB6
|
||||
if current.is(_L|_V|_LV|_LVT) && last.is(_L) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB7
|
||||
if current.is(_V|_T) && last.is(_LV|_V) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB8
|
||||
if current.is(_T) && last.is(_LVT|_T) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB9
|
||||
if current.is(_Extend | _ZWJ) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB9a
|
||||
if current.is(_SpacingMark) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB9b
|
||||
if last.is(_Prepend) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB9c
|
||||
// TODO(clipperhouse):
|
||||
// It appears to be added in Unicode 15.1.0:
|
||||
// https://unicode.org/versions/Unicode15.1.0/#Migration
|
||||
// This package currently supports Unicode 15.0.0, so
|
||||
// out of scope for now
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB11
|
||||
if current.is(_ExtendedPictographic) && last.is(_ZWJ) && lastLastExIgnore.is(_ExtendedPictographic) {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
|
||||
// https://unicode.org/reports/tr29/#GB12
|
||||
// https://unicode.org/reports/tr29/#GB13
|
||||
if (current & last).is(_RegionalIndicator) {
|
||||
regionalIndicatorCount++
|
||||
|
||||
odd := regionalIndicatorCount%2 == 1
|
||||
if odd {
|
||||
pos += w
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If we fall through all the above rules, it's a grapheme cluster break
|
||||
break
|
||||
}
|
||||
|
||||
// Return token
|
||||
return pos, data[:pos], nil
|
||||
}
|
||||
1409
vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go
generated
vendored
Normal file
1409
vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
85
vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go
generated
vendored
Normal file
85
vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go
generated
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package iterators
|
||||
|
||||
type Stringish interface {
|
||||
[]byte | string
|
||||
}
|
||||
|
||||
type SplitFunc[T Stringish] func(T, bool) (int, T, error)
|
||||
|
||||
// Iterator is a generic iterator for words that are either []byte or string.
|
||||
// Iterate while Next() is true, and access the word via Value().
|
||||
type Iterator[T Stringish] struct {
|
||||
split SplitFunc[T]
|
||||
data T
|
||||
start int
|
||||
pos int
|
||||
}
|
||||
|
||||
// New creates a new Iterator for the given data and SplitFunc.
|
||||
func New[T Stringish](split SplitFunc[T], data T) *Iterator[T] {
|
||||
return &Iterator[T]{
|
||||
split: split,
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// SetText sets the text for the iterator to operate on, and resets all state.
|
||||
func (iter *Iterator[T]) SetText(data T) {
|
||||
iter.data = data
|
||||
iter.start = 0
|
||||
iter.pos = 0
|
||||
}
|
||||
|
||||
// Split sets the SplitFunc for the Iterator.
|
||||
func (iter *Iterator[T]) Split(split SplitFunc[T]) {
|
||||
iter.split = split
|
||||
}
|
||||
|
||||
// Next advances the iterator to the next token. It returns false when there
|
||||
// are no remaining tokens or an error occurred.
|
||||
func (iter *Iterator[T]) Next() bool {
|
||||
if iter.pos == len(iter.data) {
|
||||
return false
|
||||
}
|
||||
if iter.pos > len(iter.data) {
|
||||
panic("SplitFunc advanced beyond the end of the data")
|
||||
}
|
||||
|
||||
iter.start = iter.pos
|
||||
|
||||
advance, _, err := iter.split(iter.data[iter.pos:], true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if advance <= 0 {
|
||||
panic("SplitFunc returned a zero or negative advance")
|
||||
}
|
||||
|
||||
iter.pos += advance
|
||||
if iter.pos > len(iter.data) {
|
||||
panic("SplitFunc advanced beyond the end of the data")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Value returns the current token.
|
||||
func (iter *Iterator[T]) Value() T {
|
||||
return iter.data[iter.start:iter.pos]
|
||||
}
|
||||
|
||||
// Start returns the byte position of the current token in the original data.
|
||||
func (iter *Iterator[T]) Start() int {
|
||||
return iter.start
|
||||
}
|
||||
|
||||
// End returns the byte position after the current token in the original data.
|
||||
func (iter *Iterator[T]) End() int {
|
||||
return iter.pos
|
||||
}
|
||||
|
||||
// Reset resets the iterator to the beginning of the data.
|
||||
func (iter *Iterator[T]) Reset() {
|
||||
iter.start = 0
|
||||
iter.pos = 0
|
||||
}
|
||||
27
vendor/github.com/emersion/go-smtp/client.go
generated
vendored
27
vendor/github.com/emersion/go-smtp/client.go
generated
vendored
|
|
@ -499,7 +499,7 @@ func (c *Client) Rcpt(to string, opts *RcptOptions) error {
|
|||
sb.Grow(2048)
|
||||
fmt.Fprintf(&sb, "RCPT TO:<%s>", to)
|
||||
if _, ok := c.ext["DSN"]; ok && opts != nil {
|
||||
if opts.Notify != nil && len(opts.Notify) != 0 {
|
||||
if len(opts.Notify) != 0 {
|
||||
sb.WriteString(" NOTIFY=")
|
||||
if err := checkNotifySet(opts.Notify); err != nil {
|
||||
return errors.New("smtp: Malformed NOTIFY parameter value")
|
||||
|
|
@ -534,6 +534,22 @@ func (c *Client) Rcpt(to string, opts *RcptOptions) error {
|
|||
if _, ok := c.ext["RRVS"]; ok && opts != nil && !opts.RequireRecipientValidSince.IsZero() {
|
||||
sb.WriteString(fmt.Sprintf(" RRVS=%s", opts.RequireRecipientValidSince.Format(time.RFC3339)))
|
||||
}
|
||||
if _, ok := c.ext["DELIVERBY"]; ok && opts != nil && opts.DeliverBy != nil {
|
||||
if opts.DeliverBy.Mode == DeliverByReturn && opts.DeliverBy.Time < 1 {
|
||||
return errors.New("smtp: DELIVERBY mode must be greater than zero with return mode")
|
||||
}
|
||||
arg := fmt.Sprintf(" BY=%d;%s", int(opts.DeliverBy.Time.Seconds()), opts.DeliverBy.Mode)
|
||||
if opts.DeliverBy.Trace {
|
||||
arg += "T"
|
||||
}
|
||||
sb.WriteString(arg)
|
||||
}
|
||||
if _, ok := c.ext["MT-PRIORITY"]; ok && opts != nil && opts.MTPriority != nil {
|
||||
if *opts.MTPriority < -9 || *opts.MTPriority > 9 {
|
||||
return errors.New("smtp: MT-PRIORITY must be between -9 and 9")
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(" MT-PRIORITY=%d", *opts.MTPriority))
|
||||
}
|
||||
if _, _, err := c.cmd(25, "%s", sb.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -728,15 +744,6 @@ func (c *Client) SendMail(from string, to []string, r io.Reader) error {
|
|||
var testHookStartTLS func(*tls.Config) // nil, except for tests
|
||||
|
||||
func sendMail(addr string, implicitTLS bool, a sasl.Client, from string, to []string, r io.Reader) error {
|
||||
if err := validateLine(from); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, recp := range to {
|
||||
if err := validateLine(recp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
c *Client
|
||||
err error
|
||||
|
|
|
|||
46
vendor/github.com/emersion/go-smtp/conn.go
generated
vendored
46
vendor/github.com/emersion/go-smtp/conn.go
generated
vendored
|
|
@ -294,6 +294,20 @@ func (c *Conn) handleGreet(enhanced bool, arg string) {
|
|||
if c.server.EnableRRVS {
|
||||
caps = append(caps, "RRVS")
|
||||
}
|
||||
if c.server.EnableDELIVERBY {
|
||||
if c.server.MinimumDeliverByTime == 0 {
|
||||
caps = append(caps, "DELIVERBY")
|
||||
} else {
|
||||
caps = append(caps, fmt.Sprintf("DELIVERBY %d", int(c.server.MinimumDeliverByTime.Seconds())))
|
||||
}
|
||||
}
|
||||
if c.server.EnableMTPRIORITY {
|
||||
if c.server.MtPriorityProfile == PriorityUnspecified {
|
||||
caps = append(caps, "MT-PRIORITY")
|
||||
} else {
|
||||
caps = append(caps, fmt.Sprintf("MT-PRIORITY %s", c.server.MtPriorityProfile))
|
||||
}
|
||||
}
|
||||
|
||||
args := []string{"Hello " + domain}
|
||||
args = append(args, caps...)
|
||||
|
|
@ -731,6 +745,38 @@ func (c *Conn) handleRcpt(arg string) {
|
|||
return
|
||||
}
|
||||
opts.RequireRecipientValidSince = rrvsTime
|
||||
case "BY":
|
||||
if !c.server.EnableDELIVERBY {
|
||||
c.writeResponse(504, EnhancedCode{5, 5, 4}, "DELIVERBY is not implemented")
|
||||
return
|
||||
}
|
||||
deliverBy := parseDeliverByArgument(value)
|
||||
if deliverBy == nil {
|
||||
c.writeResponse(501, EnhancedCode{5, 5, 4}, "Malformed BY parameter value")
|
||||
return
|
||||
}
|
||||
if c.server.MinimumDeliverByTime != 0 &&
|
||||
deliverBy.Mode == DeliverByReturn &&
|
||||
deliverBy.Time < c.server.MinimumDeliverByTime {
|
||||
c.writeResponse(501, EnhancedCode{5, 5, 4}, "BY parameter is below server minimum")
|
||||
return
|
||||
}
|
||||
opts.DeliverBy = deliverBy
|
||||
case "MT-PRIORITY":
|
||||
if !c.server.EnableMTPRIORITY {
|
||||
c.writeResponse(504, EnhancedCode{5, 5, 4}, "MT-PRIORITY is not implemented")
|
||||
return
|
||||
}
|
||||
mtPriority, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
c.writeResponse(501, EnhancedCode{5, 5, 4}, "Malformed MT-PRIORITY parameter value")
|
||||
return
|
||||
}
|
||||
if mtPriority < -9 || mtPriority > 9 {
|
||||
c.writeResponse(501, EnhancedCode{5, 5, 4}, "MT-PRIORITY is outside valid range")
|
||||
return
|
||||
}
|
||||
opts.MTPriority = &mtPriority
|
||||
default:
|
||||
c.writeResponse(500, EnhancedCode{5, 5, 4}, "Unknown RCPT TO argument")
|
||||
return
|
||||
|
|
|
|||
25
vendor/github.com/emersion/go-smtp/parse.go
generated
vendored
25
vendor/github.com/emersion/go-smtp/parse.go
generated
vendored
|
|
@ -2,7 +2,9 @@ package smtp
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// cutPrefixFold is a version of strings.CutPrefix which is case-insensitive.
|
||||
|
|
@ -74,6 +76,29 @@ func parseHelloArgument(arg string) (string, error) {
|
|||
return domain, nil
|
||||
}
|
||||
|
||||
// Parses the BY argument defined in RFC2852 section 4.
|
||||
// Returns pointer to options or nil if invalid.
|
||||
func parseDeliverByArgument(arg string) *DeliverByOptions {
|
||||
secondsStr, modeStr, ok := strings.Cut(arg, ";")
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
modeStr, traceValue := strings.CutSuffix(modeStr, "T")
|
||||
if modeStr != string(DeliverByNotify) && modeStr != string(DeliverByReturn) {
|
||||
return nil
|
||||
}
|
||||
modeValue := DeliverByMode(modeStr)
|
||||
secondsValue, err := strconv.Atoi(secondsStr)
|
||||
if err != nil || (modeValue == DeliverByReturn && secondsValue < 1) {
|
||||
return nil
|
||||
}
|
||||
return &DeliverByOptions{
|
||||
Time: time.Duration(secondsValue) * time.Second,
|
||||
Mode: modeValue,
|
||||
Trace: traceValue,
|
||||
}
|
||||
}
|
||||
|
||||
// parser parses command arguments defined in RFC 5321 section 4.1.2.
|
||||
type parser struct {
|
||||
s string
|
||||
|
|
|
|||
18
vendor/github.com/emersion/go-smtp/server.go
generated
vendored
18
vendor/github.com/emersion/go-smtp/server.go
generated
vendored
|
|
@ -61,6 +61,24 @@ type Server struct {
|
|||
// Should be used only if backend supports it.
|
||||
EnableRRVS bool
|
||||
|
||||
// Advertise DELIVERBY (RFC 2852) capability.
|
||||
// Should be used only if backend supports it.
|
||||
EnableDELIVERBY bool
|
||||
// The minimum time, with seconds precision, that a client
|
||||
// may specify in the BY argument with return mode.
|
||||
// A zero value indicates no set minimum.
|
||||
// Only use if DELIVERBY is enabled.
|
||||
MinimumDeliverByTime time.Duration
|
||||
|
||||
// Advertise MT-PRIORITY (RFC 6710) capability.
|
||||
// Should only be used if backend supports it.
|
||||
EnableMTPRIORITY bool
|
||||
// The priority profile mapping as defined
|
||||
// in RFC 6710 section 10.2.
|
||||
//
|
||||
// Default value of NONE to advertise no specific profile.
|
||||
MtPriorityProfile PriorityProfile
|
||||
|
||||
// The server backend.
|
||||
Backend Backend
|
||||
|
||||
|
|
|
|||
45
vendor/github.com/emersion/go-smtp/smtp.go
generated
vendored
45
vendor/github.com/emersion/go-smtp/smtp.go
generated
vendored
|
|
@ -3,21 +3,26 @@
|
|||
// It also implements the following extensions:
|
||||
//
|
||||
// - 8BITMIME (RFC 1652)
|
||||
// - AUTH (RFC 2554)
|
||||
// - STARTTLS (RFC 3207)
|
||||
// - ENHANCEDSTATUSCODES (RFC 2034)
|
||||
// - SMTPUTF8 (RFC 6531)
|
||||
// - REQUIRETLS (RFC 8689)
|
||||
// - AUTH (RFC 2554)
|
||||
// - DELIVERBY (RFC 2852)
|
||||
// - CHUNKING (RFC 3030)
|
||||
// - BINARYMIME (RFC 3030)
|
||||
// - STARTTLS (RFC 3207)
|
||||
// - DSN (RFC 3461, RFC 6533)
|
||||
// - SMTPUTF8 (RFC 6531)
|
||||
// - MT-PRIORITY (RFC 6710)
|
||||
// - RRVS (RFC 7293)
|
||||
// - REQUIRETLS (RFC 8689)
|
||||
//
|
||||
// LMTP (RFC 2033) is also supported.
|
||||
//
|
||||
// Additional extensions may be handled by other packages.
|
||||
package smtp
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type BodyType string
|
||||
|
||||
|
|
@ -84,6 +89,28 @@ const (
|
|||
DSNAddressTypeUTF8 DSNAddressType = "UTF-8"
|
||||
)
|
||||
|
||||
type DeliverByMode string
|
||||
|
||||
const (
|
||||
DeliverByNotify DeliverByMode = "N"
|
||||
DeliverByReturn DeliverByMode = "R"
|
||||
)
|
||||
|
||||
type DeliverByOptions struct {
|
||||
Time time.Duration
|
||||
Mode DeliverByMode
|
||||
Trace bool
|
||||
}
|
||||
|
||||
type PriorityProfile string
|
||||
|
||||
const (
|
||||
PriorityUnspecified PriorityProfile = ""
|
||||
PriorityMIXER PriorityProfile = "MIXER"
|
||||
PrioritySTANAG4406 PriorityProfile = "STANAG4406"
|
||||
PriorityNSEP PriorityProfile = "NSEP"
|
||||
)
|
||||
|
||||
// RcptOptions contains parameters for the RCPT command.
|
||||
type RcptOptions struct {
|
||||
// Value of NOTIFY= argument, NEVER or a combination of either of
|
||||
|
|
@ -95,6 +122,12 @@ type RcptOptions struct {
|
|||
OriginalRecipient string
|
||||
|
||||
// Time value of the RRVS= argument
|
||||
// Left as the zero time if unset.
|
||||
// or the zero time if unset.
|
||||
RequireRecipientValidSince time.Time
|
||||
|
||||
// Value of BY= argument or nil if unset.
|
||||
DeliverBy *DeliverByOptions
|
||||
|
||||
// Value of MT-PRIORITY= or nil if unset.
|
||||
MTPriority *int
|
||||
}
|
||||
|
|
|
|||
19
vendor/github.com/etkecc/go-kit/ip.go
generated
vendored
19
vendor/github.com/etkecc/go-kit/ip.go
generated
vendored
|
|
@ -26,9 +26,18 @@ func AnonymizeIP(ip string) string {
|
|||
|
||||
// IPv6
|
||||
ipParts := strings.Split(parsedIP.String(), ":")
|
||||
if len(ipParts) > 0 {
|
||||
ipParts[len(ipParts)-1] = "0"
|
||||
return strings.Join(ipParts, ":")
|
||||
}
|
||||
return ip // not an ip
|
||||
ipParts[len(ipParts)-1] = "0"
|
||||
return strings.Join(ipParts, ":")
|
||||
}
|
||||
|
||||
// IsValidIP checks if the given string is a valid IPv4 or IPv6 address by:
|
||||
// - ensuring the format is correct
|
||||
// - ensuring the IP is not an unspecified, loopback, private, multicast, or link-local address
|
||||
func IsValidIP(ipStr string) bool {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return !ip.IsUnspecified() && !ip.IsPrivate() && !ip.IsLoopback() && !ip.IsMulticast() && !ip.IsLinkLocalUnicast() && !ip.IsLinkLocalMulticast()
|
||||
}
|
||||
|
|
|
|||
11
vendor/github.com/etkecc/go-kit/maps.go
generated
vendored
11
vendor/github.com/etkecc/go-kit/maps.go
generated
vendored
|
|
@ -6,6 +6,17 @@ import (
|
|||
"sort"
|
||||
)
|
||||
|
||||
// MapFromSlice creates a map from slice elements as keys
|
||||
// The map values are set to true, indicating the presence of the key.
|
||||
// This is useful for quickly checking if a key exists in the map.
|
||||
func MapFromSlice[T cmp.Ordered](slice []T) map[T]bool {
|
||||
data := make(map[T]bool, len(slice))
|
||||
for _, k := range slice {
|
||||
data[k] = true
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// MapKeys returns map keys only
|
||||
func MapKeys[T cmp.Ordered, V any](data map[T]V) []T {
|
||||
keys := make([]T, 0, len(data))
|
||||
|
|
|
|||
79
vendor/github.com/etkecc/go-kit/stringsbuilder.go
generated
vendored
Normal file
79
vendor/github.com/etkecc/go-kit/stringsbuilder.go
generated
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package kit
|
||||
|
||||
import "strings"
|
||||
|
||||
// StringsBuilder is a wrapper around strings.Builder with short syntax,
|
||||
// it also exposes all methods of strings.Builder, so you can use it as a drop-in replacement
|
||||
type StringsBuilder struct {
|
||||
b strings.Builder
|
||||
}
|
||||
|
||||
// NewStringsBuilder creates a new StringsBuilder instance
|
||||
func NewStringsBuilder() StringsBuilder {
|
||||
return StringsBuilder{}
|
||||
}
|
||||
|
||||
// S is shortcut for WriteString
|
||||
func (sb *StringsBuilder) S(s string) *StringsBuilder {
|
||||
sb.b.WriteString(s)
|
||||
return sb
|
||||
}
|
||||
|
||||
// B is shortcut for WriteByte
|
||||
func (sb *StringsBuilder) B(b byte) *StringsBuilder {
|
||||
sb.b.WriteByte(b)
|
||||
return sb
|
||||
}
|
||||
|
||||
// R is shortcut for WriteRune
|
||||
func (sb *StringsBuilder) R(r rune) *StringsBuilder {
|
||||
sb.b.WriteRune(r)
|
||||
return sb
|
||||
}
|
||||
|
||||
// String returns the accumulated string
|
||||
func (sb *StringsBuilder) String() string {
|
||||
return sb.b.String()
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying strings.Builder
|
||||
func (sb *StringsBuilder) Unwrap() strings.Builder {
|
||||
return sb.b
|
||||
}
|
||||
|
||||
// WriteString appends the contents of s to sb's buffer
|
||||
func (sb *StringsBuilder) WriteString(s string) (int, error) {
|
||||
return sb.b.WriteString(s)
|
||||
}
|
||||
|
||||
// Write appends the contents of p to sb's buffer
|
||||
func (sb *StringsBuilder) Write(p []byte) (int, error) {
|
||||
return sb.b.Write(p)
|
||||
}
|
||||
|
||||
// WriteRune appends the UTF-8 encoding of Unicode code point r to sb's buffer
|
||||
func (sb *StringsBuilder) WriteRune(r rune) (int, error) {
|
||||
return sb.b.WriteRune(r)
|
||||
}
|
||||
|
||||
// Cap returns the current capacity of the accumulated string
|
||||
func (sb *StringsBuilder) Cap() int {
|
||||
return sb.b.Cap()
|
||||
}
|
||||
|
||||
// Grow grows sb's capacity, if necessary, to guarantee space for another n bytes
|
||||
func (sb *StringsBuilder) Grow(n int) *StringsBuilder {
|
||||
sb.b.Grow(n)
|
||||
return sb
|
||||
}
|
||||
|
||||
// Len returns the current length of the accumulated string
|
||||
func (sb *StringsBuilder) Len() int {
|
||||
return sb.b.Len()
|
||||
}
|
||||
|
||||
// Reset resets the StringsBuilder to be empty and returns itself
|
||||
func (sb *StringsBuilder) Reset() *StringsBuilder {
|
||||
sb.b.Reset()
|
||||
return sb
|
||||
}
|
||||
35
vendor/github.com/etkecc/go-kit/waitgroup.go
generated
vendored
Normal file
35
vendor/github.com/etkecc/go-kit/waitgroup.go
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package kit
|
||||
|
||||
import "sync"
|
||||
|
||||
// WaitGroup is a wrapper around sync.WaitGroup to have some syntax sugar
|
||||
// It does not provide any additional functionality
|
||||
type WaitGroup struct {
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewWaitGroup creates a new WaitGroup
|
||||
func NewWaitGroup() *WaitGroup {
|
||||
return &WaitGroup{wg: &sync.WaitGroup{}}
|
||||
}
|
||||
|
||||
// Do runs the given functions in separate goroutines and waits for them to complete
|
||||
func (w *WaitGroup) Do(f ...func()) {
|
||||
w.wg.Add(len(f))
|
||||
for _, fn := range f {
|
||||
go func() {
|
||||
defer w.wg.Done()
|
||||
fn()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the underlying sync.WaitGroup
|
||||
func (w *WaitGroup) Get() *sync.WaitGroup {
|
||||
return w.wg
|
||||
}
|
||||
|
||||
// Wait waits for all functions to complete
|
||||
func (w *WaitGroup) Wait() {
|
||||
w.wg.Wait()
|
||||
}
|
||||
196
vendor/github.com/etkecc/go-linkpearl/.golangci.yml
generated
vendored
196
vendor/github.com/etkecc/go-linkpearl/.golangci.yml
generated
vendored
|
|
@ -1,73 +1,16 @@
|
|||
version: "2"
|
||||
run:
|
||||
concurrency: 4
|
||||
timeout: 30m
|
||||
modules-download-mode: readonly
|
||||
issues-exit-code: 1
|
||||
tests: true
|
||||
build-tags: []
|
||||
modules-download-mode: readonly
|
||||
|
||||
output:
|
||||
formats:
|
||||
- format: colored-line-number
|
||||
print-issued-lines: true
|
||||
print-linter-name: true
|
||||
sort-results: true
|
||||
|
||||
linters-settings:
|
||||
decorder:
|
||||
dec-order:
|
||||
- const
|
||||
- var
|
||||
- type
|
||||
- func
|
||||
dogsled:
|
||||
max-blank-identifiers: 3
|
||||
errcheck:
|
||||
check-type-assertions: true
|
||||
check-blank: true
|
||||
errchkjson:
|
||||
report-no-exported: true
|
||||
exhaustive:
|
||||
check:
|
||||
- switch
|
||||
- map
|
||||
default-signifies-exhaustive: true
|
||||
gocognit:
|
||||
min-complexity: 15
|
||||
nestif:
|
||||
min-complexity: 5
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- style
|
||||
- performance
|
||||
gofmt:
|
||||
simplify: true
|
||||
rewrite-rules:
|
||||
- pattern: 'interface{}'
|
||||
replacement: 'any'
|
||||
- pattern: 'a[b:len(a)]'
|
||||
replacement: 'a[b:]'
|
||||
gofumpt:
|
||||
extra-rules: true
|
||||
grouper:
|
||||
const-require-single-const: true
|
||||
import-require-single-import: true
|
||||
var-require-single-var: true
|
||||
misspell:
|
||||
locale: US
|
||||
usestdlibvars:
|
||||
time-month: true
|
||||
time-layout: true
|
||||
crypto-hash: true
|
||||
default-rpc-path: true
|
||||
sql-isolation-level: true
|
||||
tls-signature-scheme: true
|
||||
constant-kind: true
|
||||
unparam:
|
||||
check-exported: true
|
||||
text:
|
||||
path: stdout
|
||||
print-linter-name: true
|
||||
print-issued-lines: true
|
||||
linters:
|
||||
disable-all: false
|
||||
enable:
|
||||
- asasalint
|
||||
- asciicheck
|
||||
|
|
@ -80,7 +23,6 @@ linters:
|
|||
- dupl
|
||||
- dupword
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- errname
|
||||
- errorlint
|
||||
|
|
@ -89,14 +31,8 @@ linters:
|
|||
- gocognit
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gosec
|
||||
- gosimple
|
||||
- gosmopolitan
|
||||
- govet
|
||||
- ineffassign
|
||||
- makezero
|
||||
- mirror
|
||||
- misspell
|
||||
|
|
@ -106,36 +42,106 @@ linters:
|
|||
- predeclared
|
||||
- revive
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- usestdlibvars
|
||||
- wastedassign
|
||||
fast: false
|
||||
|
||||
|
||||
settings:
|
||||
decorder:
|
||||
dec-order:
|
||||
- const
|
||||
- var
|
||||
- type
|
||||
- func
|
||||
dogsled:
|
||||
max-blank-identifiers: 3
|
||||
errcheck:
|
||||
check-type-assertions: true
|
||||
check-blank: true
|
||||
errchkjson:
|
||||
report-no-exported: true
|
||||
exhaustive:
|
||||
check:
|
||||
- switch
|
||||
- map
|
||||
default-signifies-exhaustive: true
|
||||
gocognit:
|
||||
min-complexity: 15
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- style
|
||||
- performance
|
||||
grouper:
|
||||
const-require-single-const: true
|
||||
import-require-single-import: true
|
||||
var-require-single-var: true
|
||||
misspell:
|
||||
locale: US
|
||||
nestif:
|
||||
min-complexity: 5
|
||||
unparam:
|
||||
check-exported: true
|
||||
usestdlibvars:
|
||||
time-month: true
|
||||
time-layout: true
|
||||
crypto-hash: true
|
||||
default-rpc-path: true
|
||||
sql-isolation-level: true
|
||||
tls-signature-scheme: true
|
||||
constant-kind: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- dupl
|
||||
- errcheck
|
||||
- gocognit
|
||||
- gocyclo
|
||||
- gosec
|
||||
path: _test\.go
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: 'SA9003:'
|
||||
- linters:
|
||||
- lll
|
||||
source: '^//go:generate '
|
||||
- linters:
|
||||
- revive
|
||||
text: returns unexported type
|
||||
paths:
|
||||
- mocks
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
issues:
|
||||
exclude-dirs-use-default: true
|
||||
exclude-dirs:
|
||||
- mocks
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gocyclo
|
||||
- gocognit
|
||||
- errcheck
|
||||
- dupl
|
||||
- gosec
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "SA9003:"
|
||||
- linters:
|
||||
- lll
|
||||
source: "^//go:generate "
|
||||
- linters:
|
||||
- revive
|
||||
text: "returns unexported type"
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
new: false
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
settings:
|
||||
gofmt:
|
||||
simplify: true
|
||||
rewrite-rules:
|
||||
- pattern: interface{}
|
||||
replacement: any
|
||||
- pattern: a[b:len(a)]
|
||||
replacement: a[b:]
|
||||
gofumpt:
|
||||
extra-rules: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- mocks
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
|
|
|||
1
vendor/github.com/etkecc/go-validator/v2/.gitignore
generated
vendored
Normal file
1
vendor/github.com/etkecc/go-validator/v2/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/cover.out
|
||||
200
vendor/github.com/etkecc/go-validator/v2/.golangci.yml
generated
vendored
200
vendor/github.com/etkecc/go-validator/v2/.golangci.yml
generated
vendored
|
|
@ -1,75 +1,16 @@
|
|||
version: "2"
|
||||
run:
|
||||
concurrency: 4
|
||||
timeout: 30m
|
||||
modules-download-mode: readonly
|
||||
issues-exit-code: 1
|
||||
tests: true
|
||||
build-tags: []
|
||||
skip-dirs-use-default: true
|
||||
skip-files: []
|
||||
modules-download-mode: readonly
|
||||
|
||||
output:
|
||||
formats:
|
||||
- format: colored-line-number
|
||||
print-issued-lines: true
|
||||
print-linter-name: true
|
||||
sort-results: true
|
||||
|
||||
linters-settings:
|
||||
decorder:
|
||||
dec-order:
|
||||
- const
|
||||
- var
|
||||
- type
|
||||
- func
|
||||
dogsled:
|
||||
max-blank-identifiers: 3
|
||||
errcheck:
|
||||
check-type-assertions: true
|
||||
check-blank: true
|
||||
errchkjson:
|
||||
report-no-exported: true
|
||||
exhaustive:
|
||||
check:
|
||||
- switch
|
||||
- map
|
||||
default-signifies-exhaustive: true
|
||||
gocognit:
|
||||
min-complexity: 15
|
||||
nestif:
|
||||
min-complexity: 5
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- style
|
||||
- performance
|
||||
gofmt:
|
||||
simplify: true
|
||||
rewrite-rules:
|
||||
- pattern: 'interface{}'
|
||||
replacement: 'any'
|
||||
- pattern: 'a[b:len(a)]'
|
||||
replacement: 'a[b:]'
|
||||
gofumpt:
|
||||
extra-rules: true
|
||||
grouper:
|
||||
const-require-single-const: true
|
||||
import-require-single-import: true
|
||||
var-require-single-var: true
|
||||
misspell:
|
||||
locale: US
|
||||
usestdlibvars:
|
||||
time-month: true
|
||||
time-layout: true
|
||||
crypto-hash: true
|
||||
default-rpc-path: true
|
||||
sql-isolation-level: true
|
||||
tls-signature-scheme: true
|
||||
constant-kind: true
|
||||
unparam:
|
||||
check-exported: true
|
||||
text:
|
||||
path: stdout
|
||||
print-linter-name: true
|
||||
print-issued-lines: true
|
||||
linters:
|
||||
disable-all: false
|
||||
enable:
|
||||
- asasalint
|
||||
- asciicheck
|
||||
|
|
@ -82,7 +23,6 @@ linters:
|
|||
- dupl
|
||||
- dupword
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- errname
|
||||
- errorlint
|
||||
|
|
@ -91,14 +31,8 @@ linters:
|
|||
- gocognit
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gosec
|
||||
- gosimple
|
||||
- gosmopolitan
|
||||
- govet
|
||||
- ineffassign
|
||||
- makezero
|
||||
- mirror
|
||||
- misspell
|
||||
|
|
@ -108,35 +42,109 @@ linters:
|
|||
- predeclared
|
||||
- revive
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- usestdlibvars
|
||||
- wastedassign
|
||||
fast: false
|
||||
|
||||
|
||||
settings:
|
||||
decorder:
|
||||
dec-order:
|
||||
- const
|
||||
- var
|
||||
- type
|
||||
- func
|
||||
dogsled:
|
||||
max-blank-identifiers: 3
|
||||
errcheck:
|
||||
check-type-assertions: true
|
||||
check-blank: true
|
||||
errchkjson:
|
||||
report-no-exported: true
|
||||
exhaustive:
|
||||
check:
|
||||
- switch
|
||||
- map
|
||||
default-signifies-exhaustive: true
|
||||
gocognit:
|
||||
min-complexity: 15
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- style
|
||||
- performance
|
||||
grouper:
|
||||
const-require-single-const: true
|
||||
import-require-single-import: true
|
||||
var-require-single-var: true
|
||||
misspell:
|
||||
locale: US
|
||||
nestif:
|
||||
min-complexity: 5
|
||||
unparam:
|
||||
check-exported: true
|
||||
usestdlibvars:
|
||||
time-month: true
|
||||
time-layout: true
|
||||
crypto-hash: true
|
||||
default-rpc-path: true
|
||||
sql-isolation-level: true
|
||||
tls-signature-scheme: true
|
||||
constant-kind: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- dupl
|
||||
- errcheck
|
||||
- gocognit
|
||||
- gocyclo
|
||||
- gosec
|
||||
path: _test\.go
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: 'SA9003:'
|
||||
- linters:
|
||||
- lll
|
||||
source: '^//go:generate '
|
||||
- linters:
|
||||
- revive
|
||||
text: returns unexported type
|
||||
- linters:
|
||||
- revive
|
||||
text: 'var-naming: avoid meaningless package names'
|
||||
paths:
|
||||
- mocks
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- mocks
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gocyclo
|
||||
- gocognit
|
||||
- errcheck
|
||||
- dupl
|
||||
- gosec
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "SA9003:"
|
||||
- linters:
|
||||
- lll
|
||||
source: "^//go:generate "
|
||||
- linters:
|
||||
- revive
|
||||
text: "returns unexported type"
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
new: false
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
settings:
|
||||
gofmt:
|
||||
simplify: true
|
||||
rewrite-rules:
|
||||
- pattern: interface{}
|
||||
replacement: any
|
||||
- pattern: a[b:len(a)]
|
||||
replacement: a[b:]
|
||||
gofumpt:
|
||||
extra-rules: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- mocks
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
|
|
|||
14
vendor/github.com/etkecc/go-validator/v2/domains.go
generated
vendored
14
vendor/github.com/etkecc/go-validator/v2/domains.go
generated
vendored
|
|
@ -1,6 +1,7 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
|
|
@ -12,15 +13,22 @@ var domainRegex = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-
|
|||
|
||||
// Domain checks if domain is valid
|
||||
func (v *V) Domain(domain string) bool {
|
||||
return v.DomainWithError(domain) == nil
|
||||
}
|
||||
|
||||
func (v *V) DomainWithError(domain string) error {
|
||||
if domain == "" {
|
||||
return !v.cfg.Domain.Enforce
|
||||
if v.cfg.Domain.Enforce {
|
||||
return errors.New("domain is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if !v.DomainString(domain) {
|
||||
return false
|
||||
return errors.New("domain is invalid")
|
||||
}
|
||||
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
// DomainString checks if domain string / value is valid using string checks like length and regexp
|
||||
|
|
|
|||
21
vendor/github.com/etkecc/go-validator/v2/emails.go
generated
vendored
21
vendor/github.com/etkecc/go-validator/v2/emails.go
generated
vendored
|
|
@ -1,6 +1,8 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/mail"
|
||||
|
|
@ -9,7 +11,6 @@ import (
|
|||
|
||||
"blitiri.com.ar/go/spf"
|
||||
"github.com/etkecc/go-trysmtp"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -22,15 +23,23 @@ var (
|
|||
// Email checks if email is valid
|
||||
// returnPath and optionalSenderIP are optional fields
|
||||
func (v *V) Email(email, returnPath string, optionalSenderIP ...net.IP) bool {
|
||||
return v.EmailWithError(email, returnPath, optionalSenderIP...) == nil
|
||||
}
|
||||
|
||||
// EmailWithError checks if email is valid and returns an error if not
|
||||
func (v *V) EmailWithError(email, returnPath string, optionalSenderIP ...net.IP) error {
|
||||
// edge case: email may be optional
|
||||
if email == "" {
|
||||
return !v.cfg.Email.Enforce
|
||||
if v.cfg.Email.Enforce {
|
||||
return errors.New("email is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
address, err := mail.ParseAddress(email)
|
||||
if err != nil {
|
||||
v.cfg.Log("email %s invalid, reason: %v", email, err)
|
||||
return false
|
||||
return err
|
||||
}
|
||||
if returnPath != "" {
|
||||
rpAddress, err := mail.ParseAddress(returnPath)
|
||||
|
|
@ -46,7 +55,7 @@ func (v *V) Email(email, returnPath string, optionalSenderIP ...net.IP) bool {
|
|||
return v.emailChecks(email, returnPath, optionalSenderIP...)
|
||||
}
|
||||
|
||||
func (v *V) emailChecks(email, returnPath string, optionalSenderIP ...net.IP) bool {
|
||||
func (v *V) emailChecks(email, returnPath string, optionalSenderIP ...net.IP) error {
|
||||
maxChecks := 4
|
||||
var senderIP net.IP
|
||||
if len(optionalSenderIP) > 0 {
|
||||
|
|
@ -67,10 +76,10 @@ func (v *V) emailChecks(email, returnPath string, optionalSenderIP ...net.IP) bo
|
|||
err := <-errchan
|
||||
if err != nil {
|
||||
v.cfg.Log("email %q is invalid, reason: %v", email, err)
|
||||
return false
|
||||
return err
|
||||
}
|
||||
if checks >= maxChecks {
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
vendor/github.com/etkecc/go-validator/v2/justfile
generated
vendored
2
vendor/github.com/etkecc/go-validator/v2/justfile
generated
vendored
|
|
@ -4,7 +4,7 @@ default:
|
|||
|
||||
# update go deps
|
||||
update *flags:
|
||||
go get {{flags}} .
|
||||
go get {{ flags }} .
|
||||
go mod tidy
|
||||
|
||||
# run linter
|
||||
|
|
|
|||
76
vendor/github.com/gabriel-vasile/mimetype/CODE_OF_CONDUCT.md
generated
vendored
76
vendor/github.com/gabriel-vasile/mimetype/CODE_OF_CONDUCT.md
generated
vendored
|
|
@ -1,76 +0,0 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at vasile.gabriel@email.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
12
vendor/github.com/gabriel-vasile/mimetype/CONTRIBUTING.md
generated
vendored
12
vendor/github.com/gabriel-vasile/mimetype/CONTRIBUTING.md
generated
vendored
|
|
@ -1,12 +0,0 @@
|
|||
## Contribute
|
||||
Contributions to **mimetype** are welcome. If you find an issue and you consider
|
||||
contributing, you can use the [Github issues tracker](https://github.com/gabriel-vasile/mimetype/issues)
|
||||
in order to report it, or better yet, open a pull request.
|
||||
|
||||
Code contributions must respect these rules:
|
||||
- code must be test covered
|
||||
- code must be formatted using gofmt tool
|
||||
- exported names must be documented
|
||||
|
||||
**Important**: By submitting a pull request, you agree to allow the project
|
||||
owner to license your work under the same license as that used by the project.
|
||||
35
vendor/github.com/gabriel-vasile/mimetype/README.md
generated
vendored
35
vendor/github.com/gabriel-vasile/mimetype/README.md
generated
vendored
|
|
@ -27,6 +27,7 @@
|
|||
- possibility to [extend](https://pkg.go.dev/github.com/gabriel-vasile/mimetype#example-package-Extend) with other file formats
|
||||
- common file formats are prioritized
|
||||
- [text vs. binary files differentiation](https://pkg.go.dev/github.com/gabriel-vasile/mimetype#example-package-TextVsBinary)
|
||||
- no external dependencies
|
||||
- safe for concurrent usage
|
||||
|
||||
## Install
|
||||
|
|
@ -45,8 +46,7 @@ fmt.Println(mtype.String(), mtype.Extension())
|
|||
```
|
||||
See the [runnable Go Playground examples](https://pkg.go.dev/github.com/gabriel-vasile/mimetype#pkg-overview).
|
||||
|
||||
## Usage'
|
||||
Only use libraries like **mimetype** as a last resort. Content type detection
|
||||
Caution: only use libraries like **mimetype** as a last resort. Content type detection
|
||||
using magic numbers is slow, inaccurate, and non-standard. Most of the times
|
||||
protocols have methods for specifying such metadata; e.g., `Content-Type` header
|
||||
in HTTP and SMTP.
|
||||
|
|
@ -67,6 +67,18 @@ mimetype.DetectFile("file.doc")
|
|||
If increasing the limit does not help, please
|
||||
[open an issue](https://github.com/gabriel-vasile/mimetype/issues/new?assignees=&labels=&template=mismatched-mime-type-detected.md&title=).
|
||||
|
||||
## Tests
|
||||
In addition to unit tests,
|
||||
[mimetype_tests](https://github.com/gabriel-vasile/mimetype_tests) compares the
|
||||
library with the [Unix file utility](https://en.wikipedia.org/wiki/File_(command))
|
||||
for around 50 000 sample files. Check the latest comparison results
|
||||
[here](https://github.com/gabriel-vasile/mimetype_tests/actions).
|
||||
|
||||
## Benchmarks
|
||||
Benchmarks for each file format are performed when a PR is open. The results can
|
||||
be seen on the [workflows page](https://github.com/gabriel-vasile/mimetype/actions/workflows/benchmark.yml).
|
||||
Performance improvements are welcome but correctness is prioritized.
|
||||
|
||||
## Structure
|
||||
**mimetype** uses a hierarchical structure to keep the MIME type detection logic.
|
||||
This reduces the number of calls needed for detecting the file type. The reason
|
||||
|
|
@ -84,19 +96,8 @@ or from a [file](https://pkg.go.dev/github.com/gabriel-vasile/mimetype#DetectFil
|
|||
<img alt="how project is structured" src="https://raw.githubusercontent.com/gabriel-vasile/mimetype/master/testdata/gif.gif" width="88%">
|
||||
</div>
|
||||
|
||||
## Performance
|
||||
Thanks to the hierarchical structure, searching for common formats first,
|
||||
and limiting itself to file headers, **mimetype** matches the performance of
|
||||
stdlib `http.DetectContentType` while outperforming the alternative package.
|
||||
|
||||
```bash
|
||||
mimetype http.DetectContentType filetype
|
||||
BenchmarkMatchTar-24 250 ns/op 400 ns/op 3778 ns/op
|
||||
BenchmarkMatchZip-24 524 ns/op 351 ns/op 4884 ns/op
|
||||
BenchmarkMatchJpeg-24 103 ns/op 228 ns/op 839 ns/op
|
||||
BenchmarkMatchGif-24 139 ns/op 202 ns/op 751 ns/op
|
||||
BenchmarkMatchPng-24 165 ns/op 221 ns/op 1176 ns/op
|
||||
```
|
||||
|
||||
## Contributing
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
Contributions are unexpected but welcome. When submitting a PR for detection of
|
||||
a new file format, please make sure to add a record to the list of testcases
|
||||
from [mimetype_test.go](mimetype_test.go). For complex files a record can be added
|
||||
in the [testdata](testdata) directory.
|
||||
|
|
|
|||
248
vendor/github.com/gabriel-vasile/mimetype/internal/charset/charset.go
generated
vendored
248
vendor/github.com/gabriel-vasile/mimetype/internal/charset/charset.go
generated
vendored
|
|
@ -2,11 +2,10 @@ package charset
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
"github.com/gabriel-vasile/mimetype/internal/markup"
|
||||
"github.com/gabriel-vasile/mimetype/internal/scan"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -141,20 +140,31 @@ func FromXML(content []byte) string {
|
|||
}
|
||||
return FromPlain(content)
|
||||
}
|
||||
func fromXML(content []byte) string {
|
||||
content = trimLWS(content)
|
||||
dec := xml.NewDecoder(bytes.NewReader(content))
|
||||
rawT, err := dec.RawToken()
|
||||
if err != nil {
|
||||
return ""
|
||||
func fromXML(s scan.Bytes) string {
|
||||
xml := []byte("<?XML")
|
||||
lxml := len(xml)
|
||||
for {
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
for scan.ByteIsWS(s.Peek()) {
|
||||
s.Advance(1)
|
||||
}
|
||||
if len(s) <= lxml {
|
||||
return ""
|
||||
}
|
||||
if !s.Match(xml, scan.IgnoreCase) {
|
||||
s = s[1:] // safe to slice instead of s.Advance(1) because bounds are checked
|
||||
continue
|
||||
}
|
||||
aName, aVal, hasMore := "", "", true
|
||||
for hasMore {
|
||||
aName, aVal, hasMore = markup.GetAnAttribute(&s)
|
||||
if aName == "encoding" && aVal != "" {
|
||||
return aVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t, ok := rawT.(xml.ProcInst)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.ToLower(xmlEncoding(string(t.Inst)))
|
||||
}
|
||||
|
||||
// FromHTML returns the charset of an HTML document. It first looks if a BOM is
|
||||
|
|
@ -171,139 +181,103 @@ func FromHTML(content []byte) string {
|
|||
return FromPlain(content)
|
||||
}
|
||||
|
||||
func fromHTML(content []byte) string {
|
||||
z := html.NewTokenizer(bytes.NewReader(content))
|
||||
func fromHTML(s scan.Bytes) string {
|
||||
const (
|
||||
dontKnow = iota
|
||||
doNeedPragma
|
||||
doNotNeedPragma
|
||||
)
|
||||
meta := []byte("<META")
|
||||
body := []byte("<BODY")
|
||||
lmeta := len(meta)
|
||||
for {
|
||||
switch z.Next() {
|
||||
case html.ErrorToken:
|
||||
return ""
|
||||
|
||||
case html.StartTagToken, html.SelfClosingTagToken:
|
||||
tagName, hasAttr := z.TagName()
|
||||
if !bytes.Equal(tagName, []byte("meta")) {
|
||||
continue
|
||||
}
|
||||
attrList := make(map[string]bool)
|
||||
gotPragma := false
|
||||
|
||||
const (
|
||||
dontKnow = iota
|
||||
doNeedPragma
|
||||
doNotNeedPragma
|
||||
)
|
||||
needPragma := dontKnow
|
||||
|
||||
name := ""
|
||||
for hasAttr {
|
||||
var key, val []byte
|
||||
key, val, hasAttr = z.TagAttr()
|
||||
ks := string(key)
|
||||
if attrList[ks] {
|
||||
continue
|
||||
}
|
||||
attrList[ks] = true
|
||||
for i, c := range val {
|
||||
if 'A' <= c && c <= 'Z' {
|
||||
val[i] = c + 0x20
|
||||
}
|
||||
}
|
||||
|
||||
switch ks {
|
||||
case "http-equiv":
|
||||
if bytes.Equal(val, []byte("content-type")) {
|
||||
gotPragma = true
|
||||
}
|
||||
|
||||
case "content":
|
||||
name = fromMetaElement(string(val))
|
||||
if name != "" {
|
||||
needPragma = doNeedPragma
|
||||
}
|
||||
|
||||
case "charset":
|
||||
name = string(val)
|
||||
needPragma = doNotNeedPragma
|
||||
}
|
||||
}
|
||||
|
||||
if needPragma == dontKnow || needPragma == doNeedPragma && !gotPragma {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "utf-16") {
|
||||
name = "utf-8"
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fromMetaElement(s string) string {
|
||||
for s != "" {
|
||||
csLoc := strings.Index(s, "charset")
|
||||
if csLoc == -1 {
|
||||
return ""
|
||||
}
|
||||
s = s[csLoc+len("charset"):]
|
||||
s = strings.TrimLeft(s, " \t\n\f\r")
|
||||
if !strings.HasPrefix(s, "=") {
|
||||
if markup.SkipAComment(&s) {
|
||||
continue
|
||||
}
|
||||
s = s[1:]
|
||||
s = strings.TrimLeft(s, " \t\n\f\r")
|
||||
if s == "" {
|
||||
if len(s) <= lmeta {
|
||||
return ""
|
||||
}
|
||||
if q := s[0]; q == '"' || q == '\'' {
|
||||
s = s[1:]
|
||||
closeQuote := strings.IndexRune(s, rune(q))
|
||||
if closeQuote == -1 {
|
||||
return ""
|
||||
// Abort when <body is reached.
|
||||
if s.Match(body, scan.IgnoreCase) {
|
||||
return ""
|
||||
}
|
||||
if !s.Match(meta, scan.IgnoreCase) {
|
||||
s = s[1:] // safe to slice instead of s.Advance(1) because bounds are checked
|
||||
continue
|
||||
}
|
||||
s = s[lmeta:]
|
||||
c := s.Pop()
|
||||
if c == 0 || (!scan.ByteIsWS(c) && c != '/') {
|
||||
return ""
|
||||
}
|
||||
attrList := make(map[string]bool)
|
||||
gotPragma := false
|
||||
needPragma := dontKnow
|
||||
|
||||
charset := ""
|
||||
aName, aVal, hasMore := "", "", true
|
||||
for hasMore {
|
||||
aName, aVal, hasMore = markup.GetAnAttribute(&s)
|
||||
if attrList[aName] {
|
||||
continue
|
||||
}
|
||||
// processing step
|
||||
if len(aName) == 0 && len(aVal) == 0 {
|
||||
if needPragma == dontKnow {
|
||||
continue
|
||||
}
|
||||
if needPragma == doNeedPragma && !gotPragma {
|
||||
continue
|
||||
}
|
||||
}
|
||||
attrList[aName] = true
|
||||
if aName == "http-equiv" && scan.Bytes(aVal).Match([]byte("CONTENT-TYPE"), scan.IgnoreCase) {
|
||||
gotPragma = true
|
||||
} else if aName == "content" {
|
||||
charset = string(extractCharsetFromMeta(scan.Bytes(aVal)))
|
||||
if len(charset) != 0 {
|
||||
needPragma = doNeedPragma
|
||||
}
|
||||
} else if aName == "charset" {
|
||||
charset = aVal
|
||||
needPragma = doNotNeedPragma
|
||||
}
|
||||
return s[:closeQuote]
|
||||
}
|
||||
|
||||
end := strings.IndexAny(s, "; \t\n\f\r")
|
||||
if end == -1 {
|
||||
end = len(s)
|
||||
if needPragma == dontKnow || needPragma == doNeedPragma && !gotPragma {
|
||||
continue
|
||||
}
|
||||
return s[:end]
|
||||
|
||||
return charset
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func xmlEncoding(s string) string {
|
||||
param := "encoding="
|
||||
idx := strings.Index(s, param)
|
||||
if idx == -1 {
|
||||
return ""
|
||||
}
|
||||
v := s[idx+len(param):]
|
||||
if v == "" {
|
||||
return ""
|
||||
}
|
||||
if v[0] != '\'' && v[0] != '"' {
|
||||
return ""
|
||||
}
|
||||
idx = strings.IndexRune(v[1:], rune(v[0]))
|
||||
if idx == -1 {
|
||||
return ""
|
||||
}
|
||||
return v[1 : idx+1]
|
||||
}
|
||||
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#algorithm-for-extracting-a-character-encoding-from-a-meta-element
|
||||
func extractCharsetFromMeta(s scan.Bytes) []byte {
|
||||
for {
|
||||
i := bytes.Index(s, []byte("charset"))
|
||||
if i == -1 {
|
||||
return nil
|
||||
}
|
||||
s.Advance(i + len("charset"))
|
||||
for scan.ByteIsWS(s.Peek()) {
|
||||
s.Advance(1)
|
||||
}
|
||||
if s.Pop() != '=' {
|
||||
continue
|
||||
}
|
||||
for scan.ByteIsWS(s.Peek()) {
|
||||
s.Advance(1)
|
||||
}
|
||||
quote := s.Peek()
|
||||
if quote == 0 {
|
||||
return nil
|
||||
}
|
||||
if quote == '"' || quote == '\'' {
|
||||
s.Advance(1)
|
||||
return bytes.TrimSpace(s.PopUntil(quote))
|
||||
}
|
||||
|
||||
// trimLWS trims whitespace from beginning of the input.
|
||||
// TODO: find a way to call trimLWS once per detection instead of once in each
|
||||
// detector which needs the trimmed input.
|
||||
func trimLWS(in []byte) []byte {
|
||||
firstNonWS := 0
|
||||
for ; firstNonWS < len(in) && isWS(in[firstNonWS]); firstNonWS++ {
|
||||
return bytes.TrimSpace(s.PopUntil(';', '\t', '\n', '\x0c', '\r', ' '))
|
||||
}
|
||||
|
||||
return in[firstNonWS:]
|
||||
}
|
||||
|
||||
func isWS(b byte) bool {
|
||||
return b == '\t' || b == '\n' || b == '\x0c' || b == '\r' || b == ' '
|
||||
}
|
||||
|
|
|
|||
125
vendor/github.com/gabriel-vasile/mimetype/internal/csv/parser.go
generated
vendored
Normal file
125
vendor/github.com/gabriel-vasile/mimetype/internal/csv/parser.go
generated
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package csv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype/internal/scan"
|
||||
)
|
||||
|
||||
// Parser is a CSV reader that only counts fields.
|
||||
// It avoids allocating/copying memory and to verify behaviour, it is tested
|
||||
// and fuzzed against encoding/csv parser.
|
||||
type Parser struct {
|
||||
comma byte
|
||||
comment byte
|
||||
s scan.Bytes
|
||||
}
|
||||
|
||||
func NewParser(comma, comment byte, s scan.Bytes) *Parser {
|
||||
return &Parser{
|
||||
comma: comma,
|
||||
comment: comment,
|
||||
s: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Parser) readLine() (line []byte, cutShort bool) {
|
||||
line = r.s.ReadSlice('\n')
|
||||
|
||||
n := len(line)
|
||||
if n > 0 && line[n-1] == '\r' {
|
||||
return line[:n-1], false // drop \r at end of line
|
||||
}
|
||||
|
||||
// This line is problematic. The logic from CountFields comes from
|
||||
// encoding/csv.Reader which relies on mutating the input bytes.
|
||||
// https://github.com/golang/go/blob/b3251514531123d7fd007682389bce7428d159a0/src/encoding/csv/reader.go#L275-L279
|
||||
// To avoid mutating the input, we return cutShort. #680
|
||||
if n >= 2 && line[n-2] == '\r' && line[n-1] == '\n' {
|
||||
return line[:n-2], true
|
||||
}
|
||||
return line, false
|
||||
}
|
||||
|
||||
// CountFields reads one CSV line and counts how many records that line contained.
|
||||
// hasMore reports whether there are more lines in the input.
|
||||
// collectIndexes makes CountFields return a list of indexes where CSV fields
|
||||
// start in the line. These indexes are used to test the correctness against the
|
||||
// encoding/csv parser.
|
||||
func (r *Parser) CountFields(collectIndexes bool) (fields int, fieldPos []int, hasMore bool) {
|
||||
finished := false
|
||||
var line scan.Bytes
|
||||
cutShort := false
|
||||
for {
|
||||
line, cutShort = r.readLine()
|
||||
if finished {
|
||||
return 0, nil, false
|
||||
}
|
||||
finished = len(r.s) == 0 && len(line) == 0
|
||||
if len(line) == lengthNL(line) {
|
||||
line = nil
|
||||
continue // Skip empty lines.
|
||||
}
|
||||
if len(line) > 0 && line[0] == r.comment {
|
||||
line = nil
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
indexes := []int{}
|
||||
originalLine := line
|
||||
parseField:
|
||||
for {
|
||||
if len(line) == 0 || line[0] != '"' { // non-quoted string field
|
||||
fields++
|
||||
if collectIndexes {
|
||||
indexes = append(indexes, len(originalLine)-len(line))
|
||||
}
|
||||
i := bytes.IndexByte(line, r.comma)
|
||||
if i >= 0 {
|
||||
line.Advance(i + 1) // 1 to get over ending comma
|
||||
continue parseField
|
||||
}
|
||||
break parseField
|
||||
} else { // Quoted string field.
|
||||
if collectIndexes {
|
||||
indexes = append(indexes, len(originalLine)-len(line))
|
||||
}
|
||||
line.Advance(1) // get over starting quote
|
||||
for {
|
||||
i := bytes.IndexByte(line, '"')
|
||||
if i >= 0 {
|
||||
line.Advance(i + 1) // 1 for ending quote
|
||||
switch rn := line.Peek(); {
|
||||
case rn == '"':
|
||||
line.Advance(1)
|
||||
case rn == r.comma:
|
||||
line.Advance(1)
|
||||
fields++
|
||||
continue parseField
|
||||
case lengthNL(line) == len(line):
|
||||
fields++
|
||||
break parseField
|
||||
}
|
||||
} else if len(line) > 0 || cutShort {
|
||||
line, cutShort = r.readLine()
|
||||
originalLine = line
|
||||
} else {
|
||||
fields++
|
||||
break parseField
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fields, indexes, fields != 0
|
||||
}
|
||||
|
||||
// lengthNL reports the number of bytes for the trailing \n.
|
||||
func lengthNL(b []byte) int {
|
||||
if len(b) > 0 && b[len(b)-1] == '\n' {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
30
vendor/github.com/gabriel-vasile/mimetype/internal/json/parser.go
generated
vendored
30
vendor/github.com/gabriel-vasile/mimetype/internal/json/parser.go
generated
vendored
|
|
@ -258,7 +258,7 @@ out:
|
|||
}
|
||||
|
||||
func (p *parserState) consumeArray(b []byte, qs []query, lvl int) (n int) {
|
||||
p.currPath = append(p.currPath, []byte{'['})
|
||||
p.appendPath([]byte{'['}, qs)
|
||||
if len(b) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
|
@ -270,7 +270,7 @@ func (p *parserState) consumeArray(b []byte, qs []query, lvl int) (n int) {
|
|||
}
|
||||
if b[n] == ']' {
|
||||
p.ib++
|
||||
p.currPath = p.currPath[:len(p.currPath)-1]
|
||||
p.popLastPath(qs)
|
||||
return n + 1
|
||||
}
|
||||
innerParsed := p.consumeAny(b[n:], qs, lvl)
|
||||
|
|
@ -305,6 +305,20 @@ func queryPathMatch(qs []query, path [][]byte) int {
|
|||
return -1
|
||||
}
|
||||
|
||||
// appendPath will append a path fragment if queries is not empty.
|
||||
// If we don't need query functionality (just checking if a JSON is valid),
|
||||
// then we can skip keeping track of the path we're currently in.
|
||||
func (p *parserState) appendPath(path []byte, qs []query) {
|
||||
if len(qs) != 0 {
|
||||
p.currPath = append(p.currPath, path)
|
||||
}
|
||||
}
|
||||
func (p *parserState) popLastPath(qs []query) {
|
||||
if len(qs) != 0 {
|
||||
p.currPath = p.currPath[:len(p.currPath)-1]
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parserState) consumeObject(b []byte, qs []query, lvl int) (n int) {
|
||||
for n < len(b) {
|
||||
n += p.consumeSpace(b[n:])
|
||||
|
|
@ -326,7 +340,7 @@ func (p *parserState) consumeObject(b []byte, qs []query, lvl int) (n int) {
|
|||
if keyLen := p.consumeString(b[n:]); keyLen == 0 {
|
||||
return 0
|
||||
} else {
|
||||
p.currPath = append(p.currPath, b[n:n+keyLen-1])
|
||||
p.appendPath(b[n:n+keyLen-1], qs)
|
||||
if !p.querySatisfied {
|
||||
queryMatched = queryPathMatch(qs, p.currPath)
|
||||
}
|
||||
|
|
@ -368,12 +382,12 @@ func (p *parserState) consumeObject(b []byte, qs []query, lvl int) (n int) {
|
|||
}
|
||||
switch b[n] {
|
||||
case ',':
|
||||
p.currPath = p.currPath[:len(p.currPath)-1]
|
||||
p.popLastPath(qs)
|
||||
n++
|
||||
p.ib++
|
||||
continue
|
||||
case '}':
|
||||
p.currPath = p.currPath[:len(p.currPath)-1]
|
||||
p.popLastPath(qs)
|
||||
p.ib++
|
||||
return n + 1
|
||||
default:
|
||||
|
|
@ -388,6 +402,9 @@ func (p *parserState) consumeAny(b []byte, qs []query, lvl int) (n int) {
|
|||
if p.maxRecursion != 0 && lvl > p.maxRecursion {
|
||||
return 0
|
||||
}
|
||||
if len(qs) == 0 {
|
||||
p.querySatisfied = true
|
||||
}
|
||||
n += p.consumeSpace(b)
|
||||
if len(b[n:]) == 0 {
|
||||
return 0
|
||||
|
|
@ -426,9 +443,6 @@ func (p *parserState) consumeAny(b []byte, qs []query, lvl int) (n int) {
|
|||
if lvl == 0 {
|
||||
p.firstToken = t
|
||||
}
|
||||
if len(qs) == 0 {
|
||||
p.querySatisfied = true
|
||||
}
|
||||
if rv <= 0 {
|
||||
return n
|
||||
}
|
||||
|
|
|
|||
47
vendor/github.com/gabriel-vasile/mimetype/internal/magic/document.go
generated
vendored
47
vendor/github.com/gabriel-vasile/mimetype/internal/magic/document.go
generated
vendored
|
|
@ -1,18 +1,11 @@
|
|||
package magic
|
||||
|
||||
import "bytes"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
var (
|
||||
// Pdf matches a Portable Document Format file.
|
||||
// https://github.com/file/file/blob/11010cc805546a3e35597e67e1129a481aed40e8/magic/Magdir/pdf
|
||||
Pdf = prefix(
|
||||
// usual pdf signature
|
||||
[]byte("%PDF-"),
|
||||
// new-line prefixed signature
|
||||
[]byte("\012%PDF-"),
|
||||
// UTF-8 BOM prefixed signature
|
||||
[]byte("\xef\xbb\xbf%PDF-"),
|
||||
)
|
||||
// Fdf matches a Forms Data Format file.
|
||||
Fdf = prefix([]byte("%FDF"))
|
||||
// Mobi matches a Mobi file.
|
||||
|
|
@ -21,8 +14,18 @@ var (
|
|||
Lit = prefix([]byte("ITOLITLS"))
|
||||
)
|
||||
|
||||
// PDF matches a Portable Document Format file.
|
||||
// The %PDF- header should be the first thing inside the file but many
|
||||
// implementations don't follow the rule. The PDF spec at Appendix H says the
|
||||
// signature can be prepended by anything.
|
||||
// https://bugs.astron.com/view.php?id=446
|
||||
func PDF(raw []byte, _ uint32) bool {
|
||||
raw = raw[:min(len(raw), 1024)]
|
||||
return bytes.Contains(raw, []byte("%PDF-"))
|
||||
}
|
||||
|
||||
// DjVu matches a DjVu file.
|
||||
func DjVu(raw []byte, limit uint32) bool {
|
||||
func DjVu(raw []byte, _ uint32) bool {
|
||||
if len(raw) < 12 {
|
||||
return false
|
||||
}
|
||||
|
|
@ -36,7 +39,7 @@ func DjVu(raw []byte, limit uint32) bool {
|
|||
}
|
||||
|
||||
// P7s matches an .p7s signature File (PEM, Base64).
|
||||
func P7s(raw []byte, limit uint32) bool {
|
||||
func P7s(raw []byte, _ uint32) bool {
|
||||
// Check for PEM Encoding.
|
||||
if bytes.HasPrefix(raw, []byte("-----BEGIN PKCS7")) {
|
||||
return true
|
||||
|
|
@ -60,3 +63,21 @@ func P7s(raw []byte, limit uint32) bool {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
// Lotus123 matches a Lotus 1-2-3 spreadsheet document.
|
||||
func Lotus123(raw []byte, _ uint32) bool {
|
||||
if len(raw) <= 20 {
|
||||
return false
|
||||
}
|
||||
version := binary.BigEndian.Uint32(raw)
|
||||
if version == 0x00000200 {
|
||||
return raw[6] != 0 && raw[7] == 0
|
||||
}
|
||||
|
||||
return version == 0x00001a00 && raw[20] > 0 && raw[20] < 32
|
||||
}
|
||||
|
||||
// CHM matches a Microsoft Compiled HTML Help file.
|
||||
func CHM(raw []byte, _ uint32) bool {
|
||||
return bytes.HasPrefix(raw, []byte("ITSF\003\000\000\000\x60\000\000\000"))
|
||||
}
|
||||
|
|
|
|||
81
vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go
generated
vendored
81
vendor/github.com/gabriel-vasile/mimetype/internal/magic/magic.go
generated
vendored
|
|
@ -4,6 +4,8 @@ package magic
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype/internal/scan"
|
||||
)
|
||||
|
||||
type (
|
||||
|
|
@ -74,12 +76,13 @@ func ciCheck(sig, raw []byte) bool {
|
|||
// matches the raw input.
|
||||
func xml(sigs ...xmlSig) Detector {
|
||||
return func(raw []byte, limit uint32) bool {
|
||||
raw = trimLWS(raw)
|
||||
if len(raw) == 0 {
|
||||
b := scan.Bytes(raw)
|
||||
b.TrimLWS()
|
||||
if len(b) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, s := range sigs {
|
||||
if xmlCheck(s, raw) {
|
||||
if xmlCheck(s, b) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -104,19 +107,19 @@ func xmlCheck(sig xmlSig, raw []byte) bool {
|
|||
// matches the raw input.
|
||||
func markup(sigs ...[]byte) Detector {
|
||||
return func(raw []byte, limit uint32) bool {
|
||||
if bytes.HasPrefix(raw, []byte{0xEF, 0xBB, 0xBF}) {
|
||||
b := scan.Bytes(raw)
|
||||
if bytes.HasPrefix(b, []byte{0xEF, 0xBB, 0xBF}) {
|
||||
// We skip the UTF-8 BOM if present to ensure we correctly
|
||||
// process any leading whitespace. The presence of the BOM
|
||||
// is taken into account during charset detection in charset.go.
|
||||
raw = trimLWS(raw[3:])
|
||||
} else {
|
||||
raw = trimLWS(raw)
|
||||
b.Advance(3)
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
b.TrimLWS()
|
||||
if len(b) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, s := range sigs {
|
||||
if markupCheck(s, raw) {
|
||||
if markupCheck(s, b) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -139,7 +142,7 @@ func markupCheck(sig, raw []byte) bool {
|
|||
}
|
||||
}
|
||||
// Next byte must be space or right angle bracket.
|
||||
if db := raw[len(sig)]; db != ' ' && db != '>' {
|
||||
if db := raw[len(sig)]; !scan.ByteIsWS(db) && db != '>' {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -183,8 +186,10 @@ func newXMLSig(localName, xmlns string) xmlSig {
|
|||
// /usr/bin/env is the interpreter, php is the first and only argument.
|
||||
func shebang(sigs ...[]byte) Detector {
|
||||
return func(raw []byte, limit uint32) bool {
|
||||
b := scan.Bytes(raw)
|
||||
line := b.Line()
|
||||
for _, s := range sigs {
|
||||
if shebangCheck(s, firstLine(raw)) {
|
||||
if shebangCheck(s, line) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -192,7 +197,7 @@ func shebang(sigs ...[]byte) Detector {
|
|||
}
|
||||
}
|
||||
|
||||
func shebangCheck(sig, raw []byte) bool {
|
||||
func shebangCheck(sig []byte, raw scan.Bytes) bool {
|
||||
if len(raw) < len(sig)+2 {
|
||||
return false
|
||||
}
|
||||
|
|
@ -200,52 +205,8 @@ func shebangCheck(sig, raw []byte) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
return bytes.Equal(trimLWS(trimRWS(raw[2:])), sig)
|
||||
}
|
||||
|
||||
// trimLWS trims whitespace from beginning of the input.
|
||||
func trimLWS(in []byte) []byte {
|
||||
firstNonWS := 0
|
||||
for ; firstNonWS < len(in) && isWS(in[firstNonWS]); firstNonWS++ {
|
||||
}
|
||||
|
||||
return in[firstNonWS:]
|
||||
}
|
||||
|
||||
// trimRWS trims whitespace from the end of the input.
|
||||
func trimRWS(in []byte) []byte {
|
||||
lastNonWS := len(in) - 1
|
||||
for ; lastNonWS > 0 && isWS(in[lastNonWS]); lastNonWS-- {
|
||||
}
|
||||
|
||||
return in[:lastNonWS+1]
|
||||
}
|
||||
|
||||
func firstLine(in []byte) []byte {
|
||||
lineEnd := 0
|
||||
for ; lineEnd < len(in) && in[lineEnd] != '\n'; lineEnd++ {
|
||||
}
|
||||
|
||||
return in[:lineEnd]
|
||||
}
|
||||
|
||||
func isWS(b byte) bool {
|
||||
return b == '\t' || b == '\n' || b == '\x0c' || b == '\r' || b == ' '
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
type readBuf []byte
|
||||
|
||||
func (b *readBuf) advance(n int) bool {
|
||||
if n < 0 || len(*b) < n {
|
||||
return false
|
||||
}
|
||||
*b = (*b)[n:]
|
||||
return true
|
||||
raw.Advance(2) // skip #! we checked above
|
||||
raw.TrimLWS()
|
||||
raw.TrimRWS()
|
||||
return bytes.Equal(raw, sig)
|
||||
}
|
||||
|
|
|
|||
31
vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go
generated
vendored
31
vendor/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go
generated
vendored
|
|
@ -7,17 +7,34 @@ import (
|
|||
|
||||
// Xlsx matches a Microsoft Excel 2007 file.
|
||||
func Xlsx(raw []byte, limit uint32) bool {
|
||||
return zipContains(raw, []byte("xl/"), true)
|
||||
return msoxml(raw, zipEntries{{
|
||||
name: []byte("xl/"),
|
||||
dir: true,
|
||||
}}, 100)
|
||||
}
|
||||
|
||||
// Docx matches a Microsoft Word 2007 file.
|
||||
func Docx(raw []byte, limit uint32) bool {
|
||||
return zipContains(raw, []byte("word/"), true)
|
||||
return msoxml(raw, zipEntries{{
|
||||
name: []byte("word/"),
|
||||
dir: true,
|
||||
}}, 100)
|
||||
}
|
||||
|
||||
// Pptx matches a Microsoft PowerPoint 2007 file.
|
||||
func Pptx(raw []byte, limit uint32) bool {
|
||||
return zipContains(raw, []byte("ppt/"), true)
|
||||
return msoxml(raw, zipEntries{{
|
||||
name: []byte("ppt/"),
|
||||
dir: true,
|
||||
}}, 100)
|
||||
}
|
||||
|
||||
// Visio matches a Microsoft Visio 2013+ file.
|
||||
func Visio(raw []byte, limit uint32) bool {
|
||||
return msoxml(raw, zipEntries{{
|
||||
name: []byte("visio/"),
|
||||
dir: true,
|
||||
}}, 100)
|
||||
}
|
||||
|
||||
// Ole matches an Open Linking and Embedding file.
|
||||
|
|
@ -157,6 +174,14 @@ func Msi(raw []byte, limit uint32) bool {
|
|||
})
|
||||
}
|
||||
|
||||
// One matches a Microsoft OneNote file.
|
||||
func One(raw []byte, limit uint32) bool {
|
||||
return bytes.HasPrefix(raw, []byte{
|
||||
0xe4, 0x52, 0x5c, 0x7b, 0x8c, 0xd8, 0xa7, 0x4d,
|
||||
0xae, 0xb1, 0x53, 0x78, 0xd0, 0x29, 0x96, 0xd3,
|
||||
})
|
||||
}
|
||||
|
||||
// Helper to match by a specific CLSID of a compound file.
|
||||
//
|
||||
// http://fileformats.archiveteam.org/wiki/Microsoft_Compound_File
|
||||
|
|
|
|||
111
vendor/github.com/gabriel-vasile/mimetype/internal/magic/netpbm.go
generated
vendored
Normal file
111
vendor/github.com/gabriel-vasile/mimetype/internal/magic/netpbm.go
generated
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
package magic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype/internal/scan"
|
||||
)
|
||||
|
||||
// NetPBM matches a Netpbm Portable BitMap ASCII/Binary file.
|
||||
//
|
||||
// See: https://en.wikipedia.org/wiki/Netpbm
|
||||
func NetPBM(raw []byte, _ uint32) bool {
|
||||
return netp(raw, "P1\n", "P4\n")
|
||||
}
|
||||
|
||||
// NetPGM matches a Netpbm Portable GrayMap ASCII/Binary file.
|
||||
//
|
||||
// See: https://en.wikipedia.org/wiki/Netpbm
|
||||
func NetPGM(raw []byte, _ uint32) bool {
|
||||
return netp(raw, "P2\n", "P5\n")
|
||||
}
|
||||
|
||||
// NetPPM matches a Netpbm Portable PixMap ASCII/Binary file.
|
||||
//
|
||||
// See: https://en.wikipedia.org/wiki/Netpbm
|
||||
func NetPPM(raw []byte, _ uint32) bool {
|
||||
return netp(raw, "P3\n", "P6\n")
|
||||
}
|
||||
|
||||
// NetPAM matches a Netpbm Portable Arbitrary Map file.
|
||||
//
|
||||
// See: https://en.wikipedia.org/wiki/Netpbm
|
||||
func NetPAM(raw []byte, _ uint32) bool {
|
||||
if !bytes.HasPrefix(raw, []byte("P7\n")) {
|
||||
return false
|
||||
}
|
||||
w, h, d, m, e := false, false, false, false, false
|
||||
s := scan.Bytes(raw)
|
||||
var l scan.Bytes
|
||||
// Read line by line.
|
||||
for i := 0; i < 128; i++ {
|
||||
l = s.Line()
|
||||
// If the line is empty or a comment, skip.
|
||||
if len(l) == 0 || l.Peek() == '#' {
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
} else if bytes.HasPrefix(l, []byte("TUPLTYPE")) {
|
||||
continue
|
||||
} else if bytes.HasPrefix(l, []byte("WIDTH ")) {
|
||||
w = true
|
||||
} else if bytes.HasPrefix(l, []byte("HEIGHT ")) {
|
||||
h = true
|
||||
} else if bytes.HasPrefix(l, []byte("DEPTH ")) {
|
||||
d = true
|
||||
} else if bytes.HasPrefix(l, []byte("MAXVAL ")) {
|
||||
m = true
|
||||
} else if bytes.HasPrefix(l, []byte("ENDHDR")) {
|
||||
e = true
|
||||
}
|
||||
// When we reached header, return true if we collected all four required headers.
|
||||
// WIDTH, HEIGHT, DEPTH and MAXVAL.
|
||||
if e {
|
||||
return w && h && d && m
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func netp(s scan.Bytes, prefixes ...string) bool {
|
||||
foundPrefix := ""
|
||||
for _, p := range prefixes {
|
||||
if bytes.HasPrefix(s, []byte(p)) {
|
||||
foundPrefix = p
|
||||
}
|
||||
}
|
||||
if foundPrefix == "" {
|
||||
return false
|
||||
}
|
||||
s.Advance(len(foundPrefix)) // jump over P1, P2, P3, etc.
|
||||
|
||||
var l scan.Bytes
|
||||
// Read line by line.
|
||||
for i := 0; i < 128; i++ {
|
||||
l = s.Line()
|
||||
// If the line is a comment, skip.
|
||||
if l.Peek() == '#' {
|
||||
continue
|
||||
}
|
||||
// If line has leading whitespace, then skip over whitespace.
|
||||
for scan.ByteIsWS(l.Peek()) {
|
||||
l.Advance(1)
|
||||
}
|
||||
if len(s) == 0 || len(l) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// At this point l should be the two integers denoting the size of the matrix.
|
||||
width := l.PopUntil(scan.ASCIISpaces...)
|
||||
for scan.ByteIsWS(l.Peek()) {
|
||||
l.Advance(1)
|
||||
}
|
||||
height := l.PopUntil(scan.ASCIISpaces...)
|
||||
|
||||
w, errw := strconv.ParseInt(string(width), 10, 64)
|
||||
h, errh := strconv.ParseInt(string(height), 10, 64)
|
||||
return errw == nil && errh == nil && w > 0 && h > 0
|
||||
}
|
||||
146
vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go
generated
vendored
146
vendor/github.com/gabriel-vasile/mimetype/internal/magic/text.go
generated
vendored
|
|
@ -6,6 +6,8 @@ import (
|
|||
|
||||
"github.com/gabriel-vasile/mimetype/internal/charset"
|
||||
"github.com/gabriel-vasile/mimetype/internal/json"
|
||||
mkup "github.com/gabriel-vasile/mimetype/internal/markup"
|
||||
"github.com/gabriel-vasile/mimetype/internal/scan"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -27,6 +29,7 @@ var (
|
|||
[]byte("<BODY"),
|
||||
[]byte("<BR"),
|
||||
[]byte("<P"),
|
||||
[]byte("<!--"),
|
||||
)
|
||||
// XML matches an Extensible Markup Language file.
|
||||
XML = markup([]byte("<?XML"))
|
||||
|
|
@ -105,6 +108,18 @@ var (
|
|||
[]byte("/usr/bin/python"),
|
||||
[]byte("/usr/local/bin/python"),
|
||||
[]byte("/usr/bin/env python"),
|
||||
[]byte("/usr/bin/python2"),
|
||||
[]byte("/usr/local/bin/python2"),
|
||||
[]byte("/usr/bin/env python2"),
|
||||
[]byte("/usr/bin/python3"),
|
||||
[]byte("/usr/local/bin/python3"),
|
||||
[]byte("/usr/bin/env python3"),
|
||||
)
|
||||
// Ruby matches a Ruby programming language file.
|
||||
Ruby = shebang(
|
||||
[]byte("/usr/bin/ruby"),
|
||||
[]byte("/usr/local/bin/ruby"),
|
||||
[]byte("/usr/bin/env ruby"),
|
||||
)
|
||||
// Tcl matches a Tcl programming language file.
|
||||
Tcl = shebang(
|
||||
|
|
@ -120,19 +135,42 @@ var (
|
|||
)
|
||||
// Rtf matches a Rich Text Format file.
|
||||
Rtf = prefix([]byte("{\\rtf"))
|
||||
// Shell matches a shell script file.
|
||||
Shell = shebang(
|
||||
[]byte("/bin/sh"),
|
||||
[]byte("/bin/bash"),
|
||||
[]byte("/usr/local/bin/bash"),
|
||||
[]byte("/usr/bin/env bash"),
|
||||
[]byte("/bin/csh"),
|
||||
[]byte("/usr/local/bin/csh"),
|
||||
[]byte("/usr/bin/env csh"),
|
||||
[]byte("/bin/dash"),
|
||||
[]byte("/usr/local/bin/dash"),
|
||||
[]byte("/usr/bin/env dash"),
|
||||
[]byte("/bin/ksh"),
|
||||
[]byte("/usr/local/bin/ksh"),
|
||||
[]byte("/usr/bin/env ksh"),
|
||||
[]byte("/bin/tcsh"),
|
||||
[]byte("/usr/local/bin/tcsh"),
|
||||
[]byte("/usr/bin/env tcsh"),
|
||||
[]byte("/bin/zsh"),
|
||||
[]byte("/usr/local/bin/zsh"),
|
||||
[]byte("/usr/bin/env zsh"),
|
||||
)
|
||||
)
|
||||
|
||||
// Text matches a plain text file.
|
||||
//
|
||||
// TODO: This function does not parse BOM-less UTF16 and UTF32 files. Not really
|
||||
// sure it should. Linux file utility also requires a BOM for UTF16 and UTF32.
|
||||
func Text(raw []byte, limit uint32) bool {
|
||||
func Text(raw []byte, _ uint32) bool {
|
||||
// First look for BOM.
|
||||
if cset := charset.FromBOM(raw); cset != "" {
|
||||
return true
|
||||
}
|
||||
// Binary data bytes as defined here: https://mimesniff.spec.whatwg.org/#binary-data-byte
|
||||
for _, b := range raw {
|
||||
for i := 0; i < min(len(raw), 4096); i++ {
|
||||
b := raw[i]
|
||||
if b <= 0x08 ||
|
||||
b == 0x0B ||
|
||||
0x0E <= b && b <= 0x1A ||
|
||||
|
|
@ -143,6 +181,14 @@ func Text(raw []byte, limit uint32) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// XHTML matches an XHTML file. This check depends on the XML check to have passed.
|
||||
func XHTML(raw []byte, limit uint32) bool {
|
||||
raw = raw[:min(len(raw), 4096)]
|
||||
b := scan.Bytes(raw)
|
||||
return b.Search([]byte("<!DOCTYPE HTML"), scan.CompactWS|scan.IgnoreCase) != -1 ||
|
||||
b.Search([]byte("<HTML XMLNS="), scan.CompactWS|scan.IgnoreCase) != -1
|
||||
}
|
||||
|
||||
// Php matches a PHP: Hypertext Preprocessor file.
|
||||
func Php(raw []byte, limit uint32) bool {
|
||||
if res := phpPageF(raw, limit); res {
|
||||
|
|
@ -207,10 +253,12 @@ func jsonHelper(raw []byte, limit uint32, q string, wantTok int) bool {
|
|||
// types.
|
||||
func NdJSON(raw []byte, limit uint32) bool {
|
||||
lCount, objOrArr := 0, 0
|
||||
raw = dropLastLine(raw, limit)
|
||||
var l []byte
|
||||
for len(raw) != 0 {
|
||||
l, raw = scanLine(raw)
|
||||
|
||||
s := scan.Bytes(raw)
|
||||
s.DropLastLine(limit)
|
||||
var l scan.Bytes
|
||||
for len(s) != 0 {
|
||||
l = s.Line()
|
||||
_, inspected, firstToken, _ := json.Parse(json.QueryNone, l)
|
||||
if len(l) != inspected {
|
||||
return false
|
||||
|
|
@ -226,18 +274,84 @@ func NdJSON(raw []byte, limit uint32) bool {
|
|||
|
||||
// Svg matches a SVG file.
|
||||
func Svg(raw []byte, limit uint32) bool {
|
||||
return bytes.Contains(raw, []byte("<svg"))
|
||||
return svgWithoutXMLDeclaration(raw) || svgWithXMLDeclaration(raw)
|
||||
}
|
||||
|
||||
// svgWithoutXMLDeclaration matches a SVG image that does not have an XML header.
|
||||
// Example:
|
||||
//
|
||||
// <!-- xml comment ignored -->
|
||||
// <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
// <rect fill="#fff" stroke="#000" x="-70" y="-70" width="390" height="390"/>
|
||||
// </svg>
|
||||
func svgWithoutXMLDeclaration(s scan.Bytes) bool {
|
||||
for scan.ByteIsWS(s.Peek()) {
|
||||
s.Advance(1)
|
||||
}
|
||||
for mkup.SkipAComment(&s) {
|
||||
}
|
||||
if !bytes.HasPrefix(s, []byte("<svg")) {
|
||||
return false
|
||||
}
|
||||
|
||||
targetName, targetVal := "xmlns", "http://www.w3.org/2000/svg"
|
||||
aName, aVal, hasMore := "", "", true
|
||||
for hasMore {
|
||||
aName, aVal, hasMore = mkup.GetAnAttribute(&s)
|
||||
if aName == targetName && aVal == targetVal {
|
||||
return true
|
||||
}
|
||||
if !hasMore {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// svgWithXMLDeclaration matches a SVG image that has an XML header.
|
||||
// Example:
|
||||
//
|
||||
// <?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
// <svg width="391" height="391" viewBox="-70.5 -70.5 391 391" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
// <rect fill="#fff" stroke="#000" x="-70" y="-70" width="390" height="390"/>
|
||||
// </svg>
|
||||
func svgWithXMLDeclaration(s scan.Bytes) bool {
|
||||
for scan.ByteIsWS(s.Peek()) {
|
||||
s.Advance(1)
|
||||
}
|
||||
if !bytes.HasPrefix(s, []byte("<?xml")) {
|
||||
return false
|
||||
}
|
||||
|
||||
// version is a required attribute for XML.
|
||||
hasVersion := false
|
||||
aName, hasMore := "", true
|
||||
for hasMore {
|
||||
aName, _, hasMore = mkup.GetAnAttribute(&s)
|
||||
if aName == "version" {
|
||||
hasVersion = true
|
||||
break
|
||||
}
|
||||
if !hasMore {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(s) > 4096 {
|
||||
s = s[:4096]
|
||||
}
|
||||
return hasVersion && bytes.Contains(s, []byte("<svg"))
|
||||
}
|
||||
|
||||
// Srt matches a SubRip file.
|
||||
func Srt(raw []byte, _ uint32) bool {
|
||||
line, raw := scanLine(raw)
|
||||
s := scan.Bytes(raw)
|
||||
line := s.Line()
|
||||
|
||||
// First line must be 1.
|
||||
if len(line) != 1 || line[0] != '1' {
|
||||
return false
|
||||
}
|
||||
line, raw = scanLine(raw)
|
||||
line = s.Line()
|
||||
// Timestamp format (e.g: 00:02:16,612 --> 00:02:19,376) limits second line
|
||||
// length to exactly 29 characters.
|
||||
if len(line) != 29 {
|
||||
|
|
@ -266,7 +380,7 @@ func Srt(raw []byte, _ uint32) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
line, _ = scanLine(raw)
|
||||
line = s.Line()
|
||||
// A third line must exist and not be empty. This is the actual subtitle text.
|
||||
return len(line) != 0
|
||||
}
|
||||
|
|
@ -295,15 +409,3 @@ func Vtt(raw []byte, limit uint32) bool {
|
|||
return bytes.Equal(raw, []byte{0xEF, 0xBB, 0xBF, 0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) || // UTF-8 BOM and "WEBVTT"
|
||||
bytes.Equal(raw, []byte{0x57, 0x45, 0x42, 0x56, 0x54, 0x54}) // "WEBVTT"
|
||||
}
|
||||
|
||||
// dropCR drops a terminal \r from the data.
|
||||
func dropCR(data []byte) []byte {
|
||||
if len(data) > 0 && data[len(data)-1] == '\r' {
|
||||
return data[0 : len(data)-1]
|
||||
}
|
||||
return data
|
||||
}
|
||||
func scanLine(b []byte) (line, remainder []byte) {
|
||||
line, remainder, _ = bytes.Cut(b, []byte("\n"))
|
||||
return dropCR(line), remainder
|
||||
}
|
||||
|
|
|
|||
80
vendor/github.com/gabriel-vasile/mimetype/internal/magic/text_csv.go
generated
vendored
80
vendor/github.com/gabriel-vasile/mimetype/internal/magic/text_csv.go
generated
vendored
|
|
@ -1,77 +1,43 @@
|
|||
package magic
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
"github.com/gabriel-vasile/mimetype/internal/csv"
|
||||
"github.com/gabriel-vasile/mimetype/internal/scan"
|
||||
)
|
||||
|
||||
// A bufio.Reader pool to alleviate problems with memory allocations.
|
||||
var readerPool = sync.Pool{
|
||||
New: func() any {
|
||||
// Initiate with empty source reader.
|
||||
return bufio.NewReader(nil)
|
||||
},
|
||||
}
|
||||
|
||||
func newReader(r io.Reader) *bufio.Reader {
|
||||
br := readerPool.Get().(*bufio.Reader)
|
||||
br.Reset(r)
|
||||
return br
|
||||
}
|
||||
|
||||
// Csv matches a comma-separated values file.
|
||||
func Csv(raw []byte, limit uint32) bool {
|
||||
// CSV matches a comma-separated values file.
|
||||
func CSV(raw []byte, limit uint32) bool {
|
||||
return sv(raw, ',', limit)
|
||||
}
|
||||
|
||||
// Tsv matches a tab-separated values file.
|
||||
func Tsv(raw []byte, limit uint32) bool {
|
||||
// TSV matches a tab-separated values file.
|
||||
func TSV(raw []byte, limit uint32) bool {
|
||||
return sv(raw, '\t', limit)
|
||||
}
|
||||
|
||||
func sv(in []byte, comma rune, limit uint32) bool {
|
||||
in = dropLastLine(in, limit)
|
||||
func sv(in []byte, comma byte, limit uint32) bool {
|
||||
s := scan.Bytes(in)
|
||||
s.DropLastLine(limit)
|
||||
r := csv.NewParser(comma, '#', s)
|
||||
|
||||
br := newReader(bytes.NewReader(in))
|
||||
defer readerPool.Put(br)
|
||||
r := csv.NewReader(br)
|
||||
r.Comma = comma
|
||||
r.ReuseRecord = true
|
||||
r.LazyQuotes = true
|
||||
r.Comment = '#'
|
||||
|
||||
lines := 0
|
||||
headerFields, _, hasMore := r.CountFields(false)
|
||||
if headerFields < 2 || !hasMore {
|
||||
return false
|
||||
}
|
||||
csvLines := 1 // 1 for header
|
||||
for {
|
||||
_, err := r.Read()
|
||||
if errors.Is(err, io.EOF) {
|
||||
fields, _, hasMore := r.CountFields(false)
|
||||
if !hasMore && fields == 0 {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
csvLines++
|
||||
if fields != headerFields {
|
||||
return false
|
||||
}
|
||||
lines++
|
||||
}
|
||||
|
||||
return r.FieldsPerRecord > 1 && lines > 1
|
||||
}
|
||||
|
||||
// dropLastLine drops the last incomplete line from b.
|
||||
//
|
||||
// mimetype limits itself to ReadLimit bytes when performing a detection.
|
||||
// This means, for file formats like CSV for NDJSON, the last line of the input
|
||||
// can be an incomplete line.
|
||||
func dropLastLine(b []byte, readLimit uint32) []byte {
|
||||
if readLimit == 0 || uint32(len(b)) < readLimit {
|
||||
return b
|
||||
}
|
||||
for i := len(b) - 1; i > 0; i-- {
|
||||
if b[i] == '\n' {
|
||||
return b[:i]
|
||||
if csvLines >= 10 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return b
|
||||
|
||||
return csvLines >= 2
|
||||
}
|
||||
|
|
|
|||
198
vendor/github.com/gabriel-vasile/mimetype/internal/magic/zip.go
generated
vendored
198
vendor/github.com/gabriel-vasile/mimetype/internal/magic/zip.go
generated
vendored
|
|
@ -2,7 +2,8 @@ package magic
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype/internal/scan"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -40,92 +41,149 @@ func Zip(raw []byte, limit uint32) bool {
|
|||
(raw[3] == 0x4 || raw[3] == 0x6 || raw[3] == 0x8)
|
||||
}
|
||||
|
||||
// Jar matches a Java archive file.
|
||||
// Jar matches a Java archive file. There are two types of Jar files:
|
||||
// 1. the ones that can be opened with jexec and have 0xCAFE optional flag
|
||||
// https://stackoverflow.com/tags/executable-jar/info
|
||||
// 2. regular jars, same as above, just without the executable flag
|
||||
// https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=262278#c0
|
||||
// There is an argument to only check for manifest, since it's the common nominator
|
||||
// for both executable and non-executable versions. But the traversing zip entries
|
||||
// is unreliable because it does linear search for signatures
|
||||
// (instead of relying on offsets told by the file.)
|
||||
func Jar(raw []byte, limit uint32) bool {
|
||||
return zipContains(raw, []byte("META-INF/MANIFEST.MF"), false)
|
||||
return executableJar(raw) ||
|
||||
zipHas(raw, zipEntries{{
|
||||
name: []byte("META-INF/MANIFEST.MF"),
|
||||
}, {
|
||||
name: []byte("META-INF/"),
|
||||
}}, 1)
|
||||
}
|
||||
|
||||
func zipContains(raw, sig []byte, msoCheck bool) bool {
|
||||
b := readBuf(raw)
|
||||
pk := []byte("PK\003\004")
|
||||
if len(b) < 0x1E {
|
||||
// KMZ matches a zipped KML file, which is "doc.kml" by convention.
|
||||
func KMZ(raw []byte, _ uint32) bool {
|
||||
return zipHas(raw, zipEntries{{
|
||||
name: []byte("doc.kml"),
|
||||
}}, 100)
|
||||
}
|
||||
|
||||
// An executable Jar has a 0xCAFE flag enabled in the first zip entry.
|
||||
// The rule from file/file is:
|
||||
// >(26.s+30) leshort 0xcafe Java archive data (JAR)
|
||||
func executableJar(b scan.Bytes) bool {
|
||||
b.Advance(0x1A)
|
||||
offset, ok := b.Uint16()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
b.Advance(int(offset) + 2)
|
||||
|
||||
if !b.advance(0x1E) {
|
||||
return false
|
||||
}
|
||||
if bytes.HasPrefix(b, sig) {
|
||||
return true
|
||||
}
|
||||
cafe, ok := b.Uint16()
|
||||
return ok && cafe == 0xCAFE
|
||||
}
|
||||
|
||||
if msoCheck {
|
||||
skipFiles := [][]byte{
|
||||
[]byte("[Content_Types].xml"),
|
||||
[]byte("_rels/.rels"),
|
||||
[]byte("docProps"),
|
||||
[]byte("customXml"),
|
||||
[]byte("[trash]"),
|
||||
}
|
||||
// zipIterator iterates over a zip file returning the name of the zip entries
|
||||
// in that file.
|
||||
type zipIterator struct {
|
||||
b scan.Bytes
|
||||
}
|
||||
|
||||
hasSkipFile := false
|
||||
for _, sf := range skipFiles {
|
||||
if bytes.HasPrefix(b, sf) {
|
||||
hasSkipFile = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasSkipFile {
|
||||
return false
|
||||
}
|
||||
}
|
||||
type zipEntries []struct {
|
||||
name []byte
|
||||
dir bool // dir means checking just the prefix of the entry, not the whole path
|
||||
}
|
||||
|
||||
searchOffset := binary.LittleEndian.Uint32(raw[18:]) + 49
|
||||
if !b.advance(int(searchOffset)) {
|
||||
return false
|
||||
}
|
||||
|
||||
nextHeader := bytes.Index(raw[searchOffset:], pk)
|
||||
if !b.advance(nextHeader) {
|
||||
return false
|
||||
}
|
||||
if bytes.HasPrefix(b, sig) {
|
||||
return true
|
||||
}
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
if !b.advance(0x1A) {
|
||||
return false
|
||||
func (z zipEntries) match(file []byte) bool {
|
||||
for i := range z {
|
||||
if z[i].dir && bytes.HasPrefix(file, z[i].name) {
|
||||
return true
|
||||
}
|
||||
nextHeader = bytes.Index(b, pk)
|
||||
if nextHeader == -1 {
|
||||
return false
|
||||
}
|
||||
if !b.advance(nextHeader + 0x1E) {
|
||||
return false
|
||||
}
|
||||
if bytes.HasPrefix(b, sig) {
|
||||
if bytes.Equal(file, z[i].name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func zipHas(raw scan.Bytes, searchFor zipEntries, stopAfter int) bool {
|
||||
iter := zipIterator{raw}
|
||||
for i := 0; i < stopAfter; i++ {
|
||||
f := iter.next()
|
||||
if len(f) == 0 {
|
||||
break
|
||||
}
|
||||
if searchFor.match(f) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// msoxml behaves like zipHas, but it puts restrictions on what the first zip
|
||||
// entry can be.
|
||||
func msoxml(raw scan.Bytes, searchFor zipEntries, stopAfter int) bool {
|
||||
iter := zipIterator{raw}
|
||||
for i := 0; i < stopAfter; i++ {
|
||||
f := iter.next()
|
||||
if len(f) == 0 {
|
||||
break
|
||||
}
|
||||
if searchFor.match(f) {
|
||||
return true
|
||||
}
|
||||
// If the first is not one of the next usually expected entries,
|
||||
// then abort this check.
|
||||
if i == 0 {
|
||||
if !bytes.Equal(f, []byte("[Content_Types].xml")) &&
|
||||
!bytes.Equal(f, []byte("_rels/.rels")) &&
|
||||
!bytes.Equal(f, []byte("docProps")) &&
|
||||
!bytes.Equal(f, []byte("customXml")) &&
|
||||
!bytes.Equal(f, []byte("[trash]")) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// next extracts the name of the next zip entry.
|
||||
func (i *zipIterator) next() []byte {
|
||||
pk := []byte("PK\003\004")
|
||||
|
||||
n := bytes.Index(i.b, pk)
|
||||
if n == -1 {
|
||||
return nil
|
||||
}
|
||||
i.b.Advance(n)
|
||||
if !i.b.Advance(0x1A) {
|
||||
return nil
|
||||
}
|
||||
l, ok := i.b.Uint16()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if !i.b.Advance(0x02) {
|
||||
return nil
|
||||
}
|
||||
if len(i.b) < int(l) {
|
||||
return nil
|
||||
}
|
||||
return i.b[:l]
|
||||
}
|
||||
|
||||
// APK matches an Android Package Archive.
|
||||
// The source of signatures is https://github.com/file/file/blob/1778642b8ba3d947a779a36fcd81f8e807220a19/magic/Magdir/archive#L1820-L1887
|
||||
func APK(raw []byte, _ uint32) bool {
|
||||
apkSignatures := [][]byte{
|
||||
[]byte("AndroidManifest.xml"),
|
||||
[]byte("META-INF/com/android/build/gradle/app-metadata.properties"),
|
||||
[]byte("classes.dex"),
|
||||
[]byte("resources.arsc"),
|
||||
[]byte("res/drawable"),
|
||||
}
|
||||
for _, sig := range apkSignatures {
|
||||
if zipContains(raw, sig, false) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return zipHas(raw, zipEntries{{
|
||||
name: []byte("AndroidManifest.xml"),
|
||||
}, {
|
||||
name: []byte("META-INF/com/android/build/gradle/app-metadata.properties"),
|
||||
}, {
|
||||
name: []byte("classes.dex"),
|
||||
}, {
|
||||
name: []byte("resources.arsc"),
|
||||
}, {
|
||||
name: []byte("res/drawable"),
|
||||
}}, 100)
|
||||
}
|
||||
|
|
|
|||
103
vendor/github.com/gabriel-vasile/mimetype/internal/markup/markup.go
generated
vendored
Normal file
103
vendor/github.com/gabriel-vasile/mimetype/internal/markup/markup.go
generated
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// Package markup implements functions for extracting info from
|
||||
// HTML and XML documents.
|
||||
package markup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype/internal/scan"
|
||||
)
|
||||
|
||||
func GetAnAttribute(s *scan.Bytes) (name, val string, hasMore bool) {
|
||||
for scan.ByteIsWS(s.Peek()) || s.Peek() == '/' {
|
||||
s.Advance(1)
|
||||
}
|
||||
if s.Peek() == '>' {
|
||||
return "", "", false
|
||||
}
|
||||
// Allocate 10 to avoid resizes.
|
||||
// Attribute names and values are continuous slices of bytes in input,
|
||||
// so we could do without allocating and returning slices of input.
|
||||
nameB := make([]byte, 0, 10)
|
||||
// step 4 and 5
|
||||
for {
|
||||
// bap means byte at position in the specification.
|
||||
bap := s.Pop()
|
||||
if bap == 0 {
|
||||
return "", "", false
|
||||
}
|
||||
if bap == '=' && len(nameB) > 0 {
|
||||
val, hasMore := getAValue(s)
|
||||
return string(nameB), string(val), hasMore
|
||||
} else if scan.ByteIsWS(bap) {
|
||||
for scan.ByteIsWS(s.Peek()) {
|
||||
s.Advance(1)
|
||||
}
|
||||
if s.Peek() != '=' {
|
||||
return string(nameB), "", true
|
||||
}
|
||||
s.Advance(1)
|
||||
for scan.ByteIsWS(s.Peek()) {
|
||||
s.Advance(1)
|
||||
}
|
||||
val, hasMore := getAValue(s)
|
||||
return string(nameB), string(val), hasMore
|
||||
} else if bap == '/' || bap == '>' {
|
||||
return string(nameB), "", false
|
||||
} else if bap >= 'A' && bap <= 'Z' {
|
||||
nameB = append(nameB, bap+0x20)
|
||||
} else {
|
||||
nameB = append(nameB, bap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getAValue(s *scan.Bytes) (_ []byte, hasMore bool) {
|
||||
for scan.ByteIsWS(s.Peek()) {
|
||||
s.Advance(1)
|
||||
}
|
||||
origS, end := *s, 0
|
||||
bap := s.Pop()
|
||||
if bap == 0 {
|
||||
return nil, false
|
||||
}
|
||||
end++
|
||||
// Step 10
|
||||
switch bap {
|
||||
case '"', '\'':
|
||||
val := s.PopUntil(bap)
|
||||
if s.Pop() != bap {
|
||||
return nil, false
|
||||
}
|
||||
return val, s.Peek() != 0 && s.Peek() != '>'
|
||||
case '>':
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Step 11
|
||||
for {
|
||||
bap = s.Pop()
|
||||
if bap == 0 {
|
||||
return nil, false
|
||||
}
|
||||
switch {
|
||||
case scan.ByteIsWS(bap):
|
||||
return origS[:end], true
|
||||
case bap == '>':
|
||||
return origS[:end], false
|
||||
default:
|
||||
end++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SkipAComment(s *scan.Bytes) (skipped bool) {
|
||||
if bytes.HasPrefix(*s, []byte("<!--")) {
|
||||
// Offset by 2 len(<!) because the starting and ending -- can be the same.
|
||||
if i := bytes.Index((*s)[2:], []byte("-->")); i != -1 {
|
||||
s.Advance(i + 2 + 3) // 2 comes from len(<!) and 3 comes from len(-->).
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
213
vendor/github.com/gabriel-vasile/mimetype/internal/scan/bytes.go
generated
vendored
Normal file
213
vendor/github.com/gabriel-vasile/mimetype/internal/scan/bytes.go
generated
vendored
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
// Package scan has functions for scanning byte slices.
|
||||
package scan
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// Bytes is a byte slice with helper methods for easier scanning.
|
||||
type Bytes []byte
|
||||
|
||||
func (b *Bytes) Advance(n int) bool {
|
||||
if n < 0 || len(*b) < n {
|
||||
return false
|
||||
}
|
||||
*b = (*b)[n:]
|
||||
return true
|
||||
}
|
||||
|
||||
// TrimLWS trims whitespace from beginning of the bytes.
|
||||
func (b *Bytes) TrimLWS() {
|
||||
firstNonWS := 0
|
||||
for ; firstNonWS < len(*b) && ByteIsWS((*b)[firstNonWS]); firstNonWS++ {
|
||||
}
|
||||
|
||||
*b = (*b)[firstNonWS:]
|
||||
}
|
||||
|
||||
// TrimRWS trims whitespace from the end of the bytes.
|
||||
func (b *Bytes) TrimRWS() {
|
||||
lb := len(*b)
|
||||
for lb > 0 && ByteIsWS((*b)[lb-1]) {
|
||||
*b = (*b)[:lb-1]
|
||||
lb--
|
||||
}
|
||||
}
|
||||
|
||||
// Peek one byte from b or 0x00 if b is empty.
|
||||
func (b *Bytes) Peek() byte {
|
||||
if len(*b) > 0 {
|
||||
return (*b)[0]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Pop one byte from b or 0x00 if b is empty.
|
||||
func (b *Bytes) Pop() byte {
|
||||
if len(*b) > 0 {
|
||||
ret := (*b)[0]
|
||||
*b = (*b)[1:]
|
||||
return ret
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// PopN pops n bytes from b or nil if b is empty.
|
||||
func (b *Bytes) PopN(n int) []byte {
|
||||
if len(*b) >= n {
|
||||
ret := (*b)[:n]
|
||||
*b = (*b)[n:]
|
||||
return ret
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PopUntil will advance b until, but not including, the first occurence of stopAt
|
||||
// character. If no occurence is found, then it will advance until the end of b.
|
||||
// The returned Bytes is a slice of all the bytes that we're advanced over.
|
||||
func (b *Bytes) PopUntil(stopAt ...byte) Bytes {
|
||||
if len(*b) == 0 {
|
||||
return Bytes{}
|
||||
}
|
||||
i := bytes.IndexAny(*b, string(stopAt))
|
||||
if i == -1 {
|
||||
i = len(*b)
|
||||
}
|
||||
|
||||
prefix := (*b)[:i]
|
||||
*b = (*b)[i:]
|
||||
return Bytes(prefix)
|
||||
}
|
||||
|
||||
// ReadSlice is the same as PopUntil, but the returned value includes stopAt as well.
|
||||
func (b *Bytes) ReadSlice(stopAt byte) Bytes {
|
||||
if len(*b) == 0 {
|
||||
return Bytes{}
|
||||
}
|
||||
i := bytes.IndexByte(*b, stopAt)
|
||||
if i == -1 {
|
||||
i = len(*b)
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
|
||||
prefix := (*b)[:i]
|
||||
*b = (*b)[i:]
|
||||
return Bytes(prefix)
|
||||
}
|
||||
|
||||
// Line returns the first line from b and advances b with the length of the
|
||||
// line. One new line character is trimmed after the line if it exists.
|
||||
func (b *Bytes) Line() Bytes {
|
||||
line := b.PopUntil('\n')
|
||||
lline := len(line)
|
||||
if lline > 0 && line[lline-1] == '\r' {
|
||||
line = line[:lline-1]
|
||||
}
|
||||
b.Advance(1)
|
||||
return line
|
||||
}
|
||||
|
||||
// DropLastLine drops the last incomplete line from b.
|
||||
//
|
||||
// mimetype limits itself to ReadLimit bytes when performing a detection.
|
||||
// This means, for file formats like CSV for NDJSON, the last line of the input
|
||||
// can be an incomplete line.
|
||||
// If b length is less than readLimit, it means we received an incomplete file
|
||||
// and proceed with dropping the last line.
|
||||
func (b *Bytes) DropLastLine(readLimit uint32) {
|
||||
if readLimit == 0 || uint32(len(*b)) < readLimit {
|
||||
return
|
||||
}
|
||||
|
||||
for i := len(*b) - 1; i > 0; i-- {
|
||||
if (*b)[i] == '\n' {
|
||||
*b = (*b)[:i]
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bytes) Uint16() (uint16, bool) {
|
||||
if len(*b) < 2 {
|
||||
return 0, false
|
||||
}
|
||||
v := binary.LittleEndian.Uint16(*b)
|
||||
*b = (*b)[2:]
|
||||
return v, true
|
||||
}
|
||||
|
||||
const (
|
||||
CompactWS = 1 << iota
|
||||
IgnoreCase
|
||||
)
|
||||
|
||||
// Search for occurences of pattern p inside b at any index.
|
||||
func (b Bytes) Search(p []byte, flags int) int {
|
||||
if flags == 0 {
|
||||
return bytes.Index(b, p)
|
||||
}
|
||||
|
||||
lb, lp := len(b), len(p)
|
||||
for i := range b {
|
||||
if lb-i < lp {
|
||||
return -1
|
||||
}
|
||||
if b[i:].Match(p, flags) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// Match pattern p at index 0 of b.
|
||||
func (b Bytes) Match(p []byte, flags int) bool {
|
||||
for len(b) > 0 {
|
||||
// If we finished all we we're looking for from p.
|
||||
if len(p) == 0 {
|
||||
return true
|
||||
}
|
||||
if flags&IgnoreCase > 0 && isUpper(p[0]) {
|
||||
if upper(b[0]) != p[0] {
|
||||
return false
|
||||
}
|
||||
b, p = b[1:], p[1:]
|
||||
} else if flags&CompactWS > 0 && ByteIsWS(p[0]) {
|
||||
p = p[1:]
|
||||
if !ByteIsWS(b[0]) {
|
||||
return false
|
||||
}
|
||||
b = b[1:]
|
||||
if !ByteIsWS(p[0]) {
|
||||
b.TrimLWS()
|
||||
}
|
||||
} else {
|
||||
if b[0] != p[0] {
|
||||
return false
|
||||
}
|
||||
b, p = b[1:], p[1:]
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isUpper(c byte) bool {
|
||||
return c >= 'A' && c <= 'Z'
|
||||
}
|
||||
func upper(c byte) byte {
|
||||
if c >= 'a' && c <= 'z' {
|
||||
return c - ('a' - 'A')
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func ByteIsWS(b byte) bool {
|
||||
return b == '\t' || b == '\n' || b == '\x0c' || b == '\r' || b == ' '
|
||||
}
|
||||
|
||||
var (
|
||||
ASCIISpaces = []byte{' ', '\r', '\n', '\x0c', '\t'}
|
||||
ASCIIDigits = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
|
||||
)
|
||||
26
vendor/github.com/gabriel-vasile/mimetype/mime.go
generated
vendored
26
vendor/github.com/gabriel-vasile/mimetype/mime.go
generated
vendored
|
|
@ -103,15 +103,17 @@ func (m *MIME) match(in []byte, readLimit uint32) *MIME {
|
|||
"text/html": charset.FromHTML,
|
||||
"text/xml": charset.FromXML,
|
||||
}
|
||||
// ps holds optional MIME parameters.
|
||||
ps := map[string]string{}
|
||||
charset := ""
|
||||
if f, ok := needsCharset[m.mime]; ok {
|
||||
if cset := f(in); cset != "" {
|
||||
ps["charset"] = cset
|
||||
}
|
||||
// The charset comes from BOM, from HTML headers, from XML headers.
|
||||
// Limit the number of bytes searched for to 1024.
|
||||
charset = f(in[:min(len(in), 1024)])
|
||||
}
|
||||
if m == root {
|
||||
return m
|
||||
}
|
||||
|
||||
return m.cloneHierarchy(ps)
|
||||
return m.cloneHierarchy(charset)
|
||||
}
|
||||
|
||||
// flatten transforms an hierarchy of MIMEs into a slice of MIMEs.
|
||||
|
|
@ -125,10 +127,10 @@ func (m *MIME) flatten() []*MIME {
|
|||
}
|
||||
|
||||
// clone creates a new MIME with the provided optional MIME parameters.
|
||||
func (m *MIME) clone(ps map[string]string) *MIME {
|
||||
func (m *MIME) clone(charset string) *MIME {
|
||||
clonedMIME := m.mime
|
||||
if len(ps) > 0 {
|
||||
clonedMIME = mime.FormatMediaType(m.mime, ps)
|
||||
if charset != "" {
|
||||
clonedMIME = m.mime + "; charset=" + charset
|
||||
}
|
||||
|
||||
return &MIME{
|
||||
|
|
@ -140,11 +142,11 @@ func (m *MIME) clone(ps map[string]string) *MIME {
|
|||
|
||||
// cloneHierarchy creates a clone of m and all its ancestors. The optional MIME
|
||||
// parameters are set on the last child of the hierarchy.
|
||||
func (m *MIME) cloneHierarchy(ps map[string]string) *MIME {
|
||||
ret := m.clone(ps)
|
||||
func (m *MIME) cloneHierarchy(charset string) *MIME {
|
||||
ret := m.clone(charset)
|
||||
lastChild := ret
|
||||
for p := m.Parent(); p != nil; p = p.Parent() {
|
||||
pClone := p.clone(nil)
|
||||
pClone := p.clone("")
|
||||
lastChild.parent = pClone
|
||||
lastChild = pClone
|
||||
}
|
||||
|
|
|
|||
22
vendor/github.com/gabriel-vasile/mimetype/supported_mimes.md
generated
vendored
22
vendor/github.com/gabriel-vasile/mimetype/supported_mimes.md
generated
vendored
|
|
@ -1,4 +1,4 @@
|
|||
## 179 Supported MIME types
|
||||
## 191 Supported MIME types
|
||||
This file is automatically generated when running tests. Do not edit manually.
|
||||
|
||||
Extension | MIME type | Aliases
|
||||
|
|
@ -7,12 +7,12 @@ Extension | MIME type | Aliases
|
|||
**.xpm** | image/x-xpixmap | -
|
||||
**.7z** | application/x-7z-compressed | -
|
||||
**.zip** | application/zip | application/x-zip, application/x-zip-compressed
|
||||
**.xlsx** | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | -
|
||||
**.docx** | application/vnd.openxmlformats-officedocument.wordprocessingml.document | -
|
||||
**.pptx** | application/vnd.openxmlformats-officedocument.presentationml.presentation | -
|
||||
**.xlsx** | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | -
|
||||
**.epub** | application/epub+zip | -
|
||||
**.apk** | application/vnd.android.package-archive | -
|
||||
**.jar** | application/jar | -
|
||||
**.jar** | application/java-archive | application/jar, application/jar-archive, application/x-java-archive
|
||||
**.odt** | application/vnd.oasis.opendocument.text | application/x-vnd.oasis.opendocument.text
|
||||
**.ott** | application/vnd.oasis.opendocument.text-template | application/x-vnd.oasis.opendocument.text-template
|
||||
**.ods** | application/vnd.oasis.opendocument.spreadsheet | application/x-vnd.oasis.opendocument.spreadsheet
|
||||
|
|
@ -24,6 +24,8 @@ Extension | MIME type | Aliases
|
|||
**.odf** | application/vnd.oasis.opendocument.formula | application/x-vnd.oasis.opendocument.formula
|
||||
**.odc** | application/vnd.oasis.opendocument.chart | application/x-vnd.oasis.opendocument.chart
|
||||
**.sxc** | application/vnd.sun.xml.calc | -
|
||||
**.kmz** | application/vnd.google-earth.kmz | -
|
||||
**.vsdx** | application/vnd.ms-visio.drawing.main+xml | -
|
||||
**.pdf** | application/pdf | application/x-pdf
|
||||
**.fdf** | application/vnd.fdf | -
|
||||
**n/a** | application/x-ole-storage | -
|
||||
|
|
@ -61,9 +63,10 @@ Extension | MIME type | Aliases
|
|||
**.tar** | application/x-tar | -
|
||||
**.xar** | application/x-xar | -
|
||||
**.bz2** | application/x-bzip2 | -
|
||||
**.fits** | application/fits | -
|
||||
**.fits** | application/fits | image/fits
|
||||
**.tiff** | image/tiff | -
|
||||
**.bmp** | image/bmp | image/x-bmp, image/x-ms-bmp
|
||||
**.123** | application/vnd.lotus-1-2-3 | -
|
||||
**.ico** | image/x-icon | -
|
||||
**.mp3** | audio/mpeg | audio/x-mpeg, audio/mp3
|
||||
**.flac** | audio/flac | -
|
||||
|
|
@ -146,9 +149,11 @@ Extension | MIME type | Aliases
|
|||
**.cab** | application/x-installshield | -
|
||||
**.jxr** | image/jxr | image/vnd.ms-photo
|
||||
**.parquet** | application/vnd.apache.parquet | application/x-parquet
|
||||
**.one** | application/onenote | -
|
||||
**.chm** | application/vnd.ms-htmlhelp | -
|
||||
**.txt** | text/plain | -
|
||||
**.html** | text/html | -
|
||||
**.svg** | image/svg+xml | -
|
||||
**.html** | text/html | -
|
||||
**.xml** | text/xml | application/xml
|
||||
**.rss** | application/rss+xml | text/rss
|
||||
**.atom** | application/atom+xml | -
|
||||
|
|
@ -163,11 +168,13 @@ Extension | MIME type | Aliases
|
|||
**.3mf** | application/vnd.ms-package.3dmanufacturing-3dmodel+xml | -
|
||||
**.xfdf** | application/vnd.adobe.xfdf | -
|
||||
**.owl** | application/owl+xml | -
|
||||
**.html** | application/xhtml+xml | -
|
||||
**.php** | text/x-php | -
|
||||
**.js** | text/javascript | application/x-javascript, application/javascript
|
||||
**.lua** | text/x-lua | -
|
||||
**.pl** | text/x-perl | -
|
||||
**.py** | text/x-python | text/x-script.python, application/x-python
|
||||
**.rb** | text/x-ruby | application/x-ruby
|
||||
**.json** | application/json | -
|
||||
**.geojson** | application/geo+json | -
|
||||
**.har** | application/json | -
|
||||
|
|
@ -182,3 +189,8 @@ Extension | MIME type | Aliases
|
|||
**.ics** | text/calendar | -
|
||||
**.warc** | application/warc | -
|
||||
**.vtt** | text/vtt | -
|
||||
**.sh** | text/x-shellscript | text/x-sh, application/x-shellscript, application/x-sh
|
||||
**.pbm** | image/x-portable-bitmap | -
|
||||
**.pgm** | image/x-portable-graymap | -
|
||||
**.ppm** | image/x-portable-pixmap | -
|
||||
**.pam** | image/x-portable-arbitrarymap | -
|
||||
|
|
|
|||
62
vendor/github.com/gabriel-vasile/mimetype/tree.go
generated
vendored
62
vendor/github.com/gabriel-vasile/mimetype/tree.go
generated
vendored
|
|
@ -18,12 +18,13 @@ import (
|
|||
var root = newMIME("application/octet-stream", "",
|
||||
func([]byte, uint32) bool { return true },
|
||||
xpm, sevenZ, zip, pdf, fdf, ole, ps, psd, p7s, ogg, png, jpg, jxl, jp2, jpx,
|
||||
jpm, jxs, gif, webp, exe, elf, ar, tar, xar, bz2, fits, tiff, bmp, ico, mp3,
|
||||
flac, midi, ape, musePack, amr, wav, aiff, au, mpeg, quickTime, mp4, webM,
|
||||
jpm, jxs, gif, webp, exe, elf, ar, tar, xar, bz2, fits, tiff, bmp, lotus, ico,
|
||||
mp3, flac, midi, ape, musePack, amr, wav, aiff, au, mpeg, quickTime, mp4, webM,
|
||||
avi, flv, mkv, asf, aac, voc, m3u, rmvb, gzip, class, swf, crx, ttf, woff,
|
||||
woff2, otf, ttc, eot, wasm, shx, dbf, dcm, rar, djvu, mobi, lit, bpg, cbor,
|
||||
sqlite3, dwg, nes, lnk, macho, qcp, icns, hdr, mrc, mdb, accdb, zstd, cab,
|
||||
rpm, xz, lzip, torrent, cpio, tzif, xcf, pat, gbr, glb, cabIS, jxr, parquet,
|
||||
oneNote, chm,
|
||||
// Keep text last because it is the slowest check.
|
||||
text,
|
||||
)
|
||||
|
|
@ -48,22 +49,24 @@ var (
|
|||
// This means APK should be a child of JAR detector, but in practice,
|
||||
// the decisive signature for JAR might be located at the end of the file
|
||||
// and not reachable because of library readLimit.
|
||||
zip = newMIME("application/zip", ".zip", magic.Zip, xlsx, docx, pptx, epub, apk, jar, odt, ods, odp, odg, odf, odc, sxc).
|
||||
zip = newMIME("application/zip", ".zip", magic.Zip, docx, pptx, xlsx, epub, apk, jar, odt, ods, odp, odg, odf, odc, sxc, kmz, visio).
|
||||
alias("application/x-zip", "application/x-zip-compressed")
|
||||
tar = newMIME("application/x-tar", ".tar", magic.Tar)
|
||||
xar = newMIME("application/x-xar", ".xar", magic.Xar)
|
||||
bz2 = newMIME("application/x-bzip2", ".bz2", magic.Bz2)
|
||||
pdf = newMIME("application/pdf", ".pdf", magic.Pdf).
|
||||
pdf = newMIME("application/pdf", ".pdf", magic.PDF).
|
||||
alias("application/x-pdf")
|
||||
fdf = newMIME("application/vnd.fdf", ".fdf", magic.Fdf)
|
||||
xlsx = newMIME("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".xlsx", magic.Xlsx)
|
||||
docx = newMIME("application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".docx", magic.Docx)
|
||||
pptx = newMIME("application/vnd.openxmlformats-officedocument.presentationml.presentation", ".pptx", magic.Pptx)
|
||||
epub = newMIME("application/epub+zip", ".epub", magic.Epub)
|
||||
jar = newMIME("application/jar", ".jar", magic.Jar)
|
||||
apk = newMIME("application/vnd.android.package-archive", ".apk", magic.APK)
|
||||
ole = newMIME("application/x-ole-storage", "", magic.Ole, msi, aaf, msg, xls, pub, ppt, doc)
|
||||
msi = newMIME("application/x-ms-installer", ".msi", magic.Msi).
|
||||
fdf = newMIME("application/vnd.fdf", ".fdf", magic.Fdf)
|
||||
xlsx = newMIME("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ".xlsx", magic.Xlsx)
|
||||
docx = newMIME("application/vnd.openxmlformats-officedocument.wordprocessingml.document", ".docx", magic.Docx)
|
||||
pptx = newMIME("application/vnd.openxmlformats-officedocument.presentationml.presentation", ".pptx", magic.Pptx)
|
||||
visio = newMIME("application/vnd.ms-visio.drawing.main+xml", ".vsdx", magic.Visio)
|
||||
epub = newMIME("application/epub+zip", ".epub", magic.Epub)
|
||||
jar = newMIME("application/java-archive", ".jar", magic.Jar).
|
||||
alias("application/jar", "application/jar-archive", "application/x-java-archive")
|
||||
apk = newMIME("application/vnd.android.package-archive", ".apk", magic.APK)
|
||||
ole = newMIME("application/x-ole-storage", "", magic.Ole, msi, aaf, msg, xls, pub, ppt, doc)
|
||||
msi = newMIME("application/x-ms-installer", ".msi", magic.Msi).
|
||||
alias("application/x-windows-installer", "application/x-msi")
|
||||
aaf = newMIME("application/octet-stream", ".aaf", magic.Aaf)
|
||||
doc = newMIME("application/msword", ".doc", magic.Doc).
|
||||
|
|
@ -75,18 +78,19 @@ var (
|
|||
alias("application/msexcel")
|
||||
msg = newMIME("application/vnd.ms-outlook", ".msg", magic.Msg)
|
||||
ps = newMIME("application/postscript", ".ps", magic.Ps)
|
||||
fits = newMIME("application/fits", ".fits", magic.Fits)
|
||||
fits = newMIME("application/fits", ".fits", magic.Fits).alias("image/fits")
|
||||
ogg = newMIME("application/ogg", ".ogg", magic.Ogg, oggAudio, oggVideo).
|
||||
alias("application/x-ogg")
|
||||
oggAudio = newMIME("audio/ogg", ".oga", magic.OggAudio)
|
||||
oggVideo = newMIME("video/ogg", ".ogv", magic.OggVideo)
|
||||
text = newMIME("text/plain", ".txt", magic.Text, html, svg, xml, php, js, lua, perl, python, json, ndJSON, rtf, srt, tcl, csv, tsv, vCard, iCalendar, warc, vtt)
|
||||
xml = newMIME("text/xml", ".xml", magic.XML, rss, atom, x3d, kml, xliff, collada, gml, gpx, tcx, amf, threemf, xfdf, owl2).
|
||||
text = newMIME("text/plain", ".txt", magic.Text, svg, html, xml, php, js, lua, perl, python, ruby, json, ndJSON, rtf, srt, tcl, csv, tsv, vCard, iCalendar, warc, vtt, shell, netpbm, netpgm, netppm, netpam)
|
||||
xml = newMIME("text/xml", ".xml", magic.XML, rss, atom, x3d, kml, xliff, collada, gml, gpx, tcx, amf, threemf, xfdf, owl2, xhtml).
|
||||
alias("application/xml")
|
||||
xhtml = newMIME("application/xhtml+xml", ".html", magic.XHTML)
|
||||
json = newMIME("application/json", ".json", magic.JSON, geoJSON, har, gltf)
|
||||
har = newMIME("application/json", ".har", magic.HAR)
|
||||
csv = newMIME("text/csv", ".csv", magic.Csv)
|
||||
tsv = newMIME("text/tab-separated-values", ".tsv", magic.Tsv)
|
||||
csv = newMIME("text/csv", ".csv", magic.CSV)
|
||||
tsv = newMIME("text/tab-separated-values", ".tsv", magic.TSV)
|
||||
geoJSON = newMIME("application/geo+json", ".geojson", magic.GeoJSON)
|
||||
ndJSON = newMIME("application/x-ndjson", ".ndjson", magic.NdJSON)
|
||||
html = newMIME("text/html", ".html", magic.HTML)
|
||||
|
|
@ -101,6 +105,10 @@ var (
|
|||
perl = newMIME("text/x-perl", ".pl", magic.Perl)
|
||||
python = newMIME("text/x-python", ".py", magic.Python).
|
||||
alias("text/x-script.python", "application/x-python")
|
||||
ruby = newMIME("text/x-ruby", ".rb", magic.Ruby).
|
||||
alias("application/x-ruby")
|
||||
shell = newMIME("text/x-shellscript", ".sh", magic.Shell).
|
||||
alias("text/x-sh", "application/x-shellscript", "application/x-sh")
|
||||
tcl = newMIME("text/x-tcl", ".tcl", magic.Tcl).
|
||||
alias("application/x-tcl")
|
||||
vCard = newMIME("text/vcard", ".vcf", magic.VCard)
|
||||
|
|
@ -112,6 +120,7 @@ var (
|
|||
atom = newMIME("application/atom+xml", ".atom", magic.Atom)
|
||||
x3d = newMIME("model/x3d+xml", ".x3d", magic.X3d)
|
||||
kml = newMIME("application/vnd.google-earth.kml+xml", ".kml", magic.Kml)
|
||||
kmz = newMIME("application/vnd.google-earth.kmz", ".kmz", magic.KMZ)
|
||||
xliff = newMIME("application/x-xliff+xml", ".xlf", magic.Xliff)
|
||||
collada = newMIME("model/vnd.collada+xml", ".dae", magic.Collada)
|
||||
gml = newMIME("application/gml+xml", ".gml", magic.Gml)
|
||||
|
|
@ -135,9 +144,12 @@ var (
|
|||
tiff = newMIME("image/tiff", ".tiff", magic.Tiff)
|
||||
bmp = newMIME("image/bmp", ".bmp", magic.Bmp).
|
||||
alias("image/x-bmp", "image/x-ms-bmp")
|
||||
ico = newMIME("image/x-icon", ".ico", magic.Ico)
|
||||
icns = newMIME("image/x-icns", ".icns", magic.Icns)
|
||||
psd = newMIME("image/vnd.adobe.photoshop", ".psd", magic.Psd).
|
||||
// lotus check must be done before ico because some ico detection is a bit
|
||||
// relaxed and some lotus files are wrongfully identified as ico otherwise.
|
||||
lotus = newMIME("application/vnd.lotus-1-2-3", ".123", magic.Lotus123)
|
||||
ico = newMIME("image/x-icon", ".ico", magic.Ico)
|
||||
icns = newMIME("image/x-icns", ".icns", magic.Icns)
|
||||
psd = newMIME("image/vnd.adobe.photoshop", ".psd", magic.Psd).
|
||||
alias("image/x-psd", "application/photoshop")
|
||||
heic = newMIME("image/heic", ".heic", magic.Heic)
|
||||
heicSeq = newMIME("image/heic-sequence", ".heic", magic.HeicSequence)
|
||||
|
|
@ -267,5 +279,11 @@ var (
|
|||
jxr = newMIME("image/jxr", ".jxr", magic.Jxr).alias("image/vnd.ms-photo")
|
||||
parquet = newMIME("application/vnd.apache.parquet", ".parquet", magic.Par1).
|
||||
alias("application/x-parquet")
|
||||
cbor = newMIME("application/cbor", ".cbor", magic.CBOR)
|
||||
netpbm = newMIME("image/x-portable-bitmap", ".pbm", magic.NetPBM)
|
||||
netpgm = newMIME("image/x-portable-graymap", ".pgm", magic.NetPGM)
|
||||
netppm = newMIME("image/x-portable-pixmap", ".ppm", magic.NetPPM)
|
||||
netpam = newMIME("image/x-portable-arbitrarymap", ".pam", magic.NetPAM)
|
||||
cbor = newMIME("application/cbor", ".cbor", magic.CBOR)
|
||||
oneNote = newMIME("application/onenote", ".one", magic.One)
|
||||
chm = newMIME("application/vnd.ms-htmlhelp", ".chm", magic.CHM)
|
||||
)
|
||||
|
|
|
|||
83
vendor/github.com/getsentry/sentry-go/CHANGELOG.md
generated
vendored
83
vendor/github.com/getsentry/sentry-go/CHANGELOG.md
generated
vendored
|
|
@ -1,5 +1,88 @@
|
|||
# Changelog
|
||||
|
||||
## 0.35.3
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.35.3.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Add missing rate limit categories ([#1082](https://github.com/getsentry/sentry-go/pull/1082))
|
||||
|
||||
## 0.35.2
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.35.2.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix OpenTelemetry spans being created as transactions instead of child spans ([#1073](https://github.com/getsentry/sentry-go/pull/1073))
|
||||
|
||||
### Misc
|
||||
|
||||
- Add `MockTransport` to test clients for improved testing ([#1071](https://github.com/getsentry/sentry-go/pull/1071))
|
||||
|
||||
## 0.35.1
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.35.1.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix race conditions when accessing the scope during logging operations ([#1050](https://github.com/getsentry/sentry-go/pull/1050))
|
||||
- Fix nil pointer dereference with malformed URLs when tracing is enabled in `fasthttp` and `fiber` integrations ([#1055](https://github.com/getsentry/sentry-go/pull/1055))
|
||||
|
||||
### Misc
|
||||
|
||||
- Bump `github.com/gofiber/fiber/v2` from 2.52.5 to 2.52.9 in `/fiber` ([#1067](https://github.com/getsentry/sentry-go/pull/1067))
|
||||
|
||||
## 0.35.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.35.0.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Changes to the logging API ([#1046](https://github.com/getsentry/sentry-go/pull/1046))
|
||||
|
||||
The logging API now supports a fluent interface for structured logging with attributes:
|
||||
|
||||
```go
|
||||
// usage before
|
||||
logger := sentry.NewLogger(ctx)
|
||||
// attributes weren't being set permanently
|
||||
logger.SetAttributes(
|
||||
attribute.String("version", "1.0.0"),
|
||||
)
|
||||
logger.Infof(ctx, "Message with parameters %d and %d", 1, 2)
|
||||
|
||||
// new behavior
|
||||
ctx := context.Background()
|
||||
logger := sentry.NewLogger(ctx)
|
||||
|
||||
// Set permanent attributes on the logger
|
||||
logger.SetAttributes(
|
||||
attribute.String("version", "1.0.0"),
|
||||
)
|
||||
|
||||
// Chain attributes on individual log entries
|
||||
logger.Info().
|
||||
String("key.string", "value").
|
||||
Int("key.int", 42).
|
||||
Bool("key.bool", true).
|
||||
Emitf("Message with parameters %d and %d", 1, 2)
|
||||
```
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Correctly serialize `FailureIssueThreshold` and `RecoveryThreshold` onto check-in payloads ([#1060](https://github.com/getsentry/sentry-go/pull/1060))
|
||||
|
||||
## 0.34.1
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.34.1.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Allow flush to be used multiple times without issues, particularly for the batch logger ([#1051](https://github.com/getsentry/sentry-go/pull/1051))
|
||||
- Fix race condition in `Scope.GetSpan()` method by adding proper mutex locking ([#1044](https://github.com/getsentry/sentry-go/pull/1044))
|
||||
- Guard transport on `Close()` to prevent panic when called multiple times ([#1044](https://github.com/getsentry/sentry-go/pull/1044))
|
||||
|
||||
## 0.34.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.34.0.
|
||||
|
|
|
|||
15
vendor/github.com/getsentry/sentry-go/Makefile
generated
vendored
15
vendor/github.com/getsentry/sentry-go/Makefile
generated
vendored
|
|
@ -54,12 +54,23 @@ test-coverage: $(COVERAGE_REPORT_DIR) clean-report-dir ## Test with coverage en
|
|||
$(GO) tool cover -html=$(COVERAGE_PROFILE) -o coverage.html); \
|
||||
done;
|
||||
.PHONY: test-coverage clean-report-dir
|
||||
|
||||
test-race-coverage: $(COVERAGE_REPORT_DIR) clean-report-dir ## Run tests with race detection and coverage
|
||||
set -e ; \
|
||||
for dir in $(ALL_GO_MOD_DIRS); do \
|
||||
echo ">>> Running tests with race detection and coverage for module: $${dir}"; \
|
||||
DIR_ABS=$$(python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' $${dir}) ; \
|
||||
REPORT_NAME=$$(basename $${DIR_ABS}); \
|
||||
(cd "$${dir}" && \
|
||||
$(GO) test -count=1 -timeout $(TIMEOUT)s -race -coverpkg=./... -covermode=$(COVERAGE_MODE) -coverprofile="$(COVERAGE_PROFILE)" ./... && \
|
||||
cp $(COVERAGE_PROFILE) "$(COVERAGE_REPORT_DIR_ABS)/$${REPORT_NAME}_$(COVERAGE_PROFILE)" && \
|
||||
$(GO) tool cover -html=$(COVERAGE_PROFILE) -o coverage.html); \
|
||||
done;
|
||||
.PHONY: test-race-coverage
|
||||
mod-tidy: ## Check go.mod tidiness
|
||||
set -e ; \
|
||||
for dir in $(ALL_GO_MOD_DIRS); do \
|
||||
echo ">>> Running 'go mod tidy' for module: $${dir}"; \
|
||||
(cd "$${dir}" && go mod tidy -go=1.21 -compat=1.21); \
|
||||
(cd "$${dir}" && go mod tidy -go=1.22 -compat=1.22); \
|
||||
done; \
|
||||
git diff --exit-code;
|
||||
.PHONY: mod-tidy
|
||||
|
|
|
|||
61
vendor/github.com/getsentry/sentry-go/batch_logger.go
generated
vendored
61
vendor/github.com/getsentry/sentry-go/batch_logger.go
generated
vendored
|
|
@ -12,17 +12,20 @@ const (
|
|||
)
|
||||
|
||||
type BatchLogger struct {
|
||||
client *Client
|
||||
logCh chan Log
|
||||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
startOnce sync.Once
|
||||
client *Client
|
||||
logCh chan Log
|
||||
flushCh chan chan struct{}
|
||||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
startOnce sync.Once
|
||||
shutdownOnce sync.Once
|
||||
}
|
||||
|
||||
func NewBatchLogger(client *Client) *BatchLogger {
|
||||
return &BatchLogger{
|
||||
client: client,
|
||||
logCh: make(chan Log, batchSize),
|
||||
client: client,
|
||||
logCh: make(chan Log, batchSize),
|
||||
flushCh: make(chan chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -35,17 +38,32 @@ func (l *BatchLogger) Start() {
|
|||
})
|
||||
}
|
||||
|
||||
func (l *BatchLogger) Flush() {
|
||||
if l.cancel != nil {
|
||||
l.cancel()
|
||||
l.wg.Wait()
|
||||
func (l *BatchLogger) Flush(timeout <-chan struct{}) {
|
||||
done := make(chan struct{})
|
||||
select {
|
||||
case l.flushCh <- done:
|
||||
select {
|
||||
case <-done:
|
||||
case <-timeout:
|
||||
}
|
||||
case <-timeout:
|
||||
}
|
||||
}
|
||||
|
||||
func (l *BatchLogger) Shutdown() {
|
||||
l.shutdownOnce.Do(func() {
|
||||
if l.cancel != nil {
|
||||
l.cancel()
|
||||
l.wg.Wait()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (l *BatchLogger) run(ctx context.Context) {
|
||||
defer l.wg.Done()
|
||||
var logs []Log
|
||||
timer := time.NewTimer(batchTimeout)
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
|
|
@ -65,8 +83,27 @@ func (l *BatchLogger) run(ctx context.Context) {
|
|||
logs = nil
|
||||
}
|
||||
timer.Reset(batchTimeout)
|
||||
case done := <-l.flushCh:
|
||||
flushDrain:
|
||||
for {
|
||||
select {
|
||||
case log := <-l.logCh:
|
||||
logs = append(logs, log)
|
||||
default:
|
||||
break flushDrain
|
||||
}
|
||||
}
|
||||
|
||||
if len(logs) > 0 {
|
||||
l.processEvent(logs)
|
||||
logs = nil
|
||||
}
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
timer.Reset(batchTimeout)
|
||||
close(done)
|
||||
case <-ctx.Done():
|
||||
// Drain remaining logs from channel
|
||||
drain:
|
||||
for {
|
||||
select {
|
||||
|
|
|
|||
6
vendor/github.com/getsentry/sentry-go/client.go
generated
vendored
6
vendor/github.com/getsentry/sentry-go/client.go
generated
vendored
|
|
@ -511,7 +511,9 @@ func (client *Client) RecoverWithContext(
|
|||
// call to Init.
|
||||
func (client *Client) Flush(timeout time.Duration) bool {
|
||||
if client.batchLogger != nil {
|
||||
client.batchLogger.Flush()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
return client.FlushWithContext(ctx)
|
||||
}
|
||||
return client.Transport.Flush(timeout)
|
||||
}
|
||||
|
|
@ -530,7 +532,7 @@ func (client *Client) Flush(timeout time.Duration) bool {
|
|||
|
||||
func (client *Client) FlushWithContext(ctx context.Context) bool {
|
||||
if client.batchLogger != nil {
|
||||
client.batchLogger.Flush()
|
||||
client.batchLogger.Flush(ctx.Done())
|
||||
}
|
||||
return client.Transport.FlushWithContext(ctx)
|
||||
}
|
||||
|
|
|
|||
123
vendor/github.com/getsentry/sentry-go/interfaces.go
generated
vendored
123
vendor/github.com/getsentry/sentry-go/interfaces.go
generated
vendored
|
|
@ -13,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go/attribute"
|
||||
"github.com/getsentry/sentry-go/internal/ratelimit"
|
||||
)
|
||||
|
||||
const eventType = "event"
|
||||
|
|
@ -101,6 +102,7 @@ func (b *Breadcrumb) MarshalJSON() ([]byte, error) {
|
|||
return json.Marshal((*breadcrumb)(b))
|
||||
}
|
||||
|
||||
// Logger provides a chaining API for structured logging to Sentry.
|
||||
type Logger interface {
|
||||
// Write implements the io.Writer interface. Currently, the [sentry.Hub] is
|
||||
// context aware, in order to get the correct trace correlation. Using this
|
||||
|
|
@ -108,51 +110,47 @@ type Logger interface {
|
|||
// Write it is recommended to create a NewLogger so that the associated context
|
||||
// is passed correctly.
|
||||
Write(p []byte) (n int, err error)
|
||||
// Trace emits a [LogLevelTrace] log to Sentry.
|
||||
// Arguments are handled in the manner of [fmt.Print].
|
||||
Trace(ctx context.Context, v ...interface{})
|
||||
// Debug emits a [LogLevelDebug] log to Sentry.
|
||||
// Arguments are handled in the manner of [fmt.Print].
|
||||
Debug(ctx context.Context, v ...interface{})
|
||||
// Info emits a [LogLevelInfo] log to Sentry.
|
||||
// Arguments are handled in the manner of [fmt.Print].
|
||||
Info(ctx context.Context, v ...interface{})
|
||||
// Warn emits a [LogLevelWarn] log to Sentry.
|
||||
// Arguments are handled in the manner of [fmt.Print].
|
||||
Warn(ctx context.Context, v ...interface{})
|
||||
// Error emits a [LogLevelError] log to Sentry.
|
||||
// Arguments are handled in the manner of [fmt.Print].
|
||||
Error(ctx context.Context, v ...interface{})
|
||||
// Fatal emits a [LogLevelFatal] log to Sentry followed by a call to [os.Exit](1).
|
||||
// Arguments are handled in the manner of [fmt.Print].
|
||||
Fatal(ctx context.Context, v ...interface{})
|
||||
// Panic emits a [LogLevelFatal] log to Sentry followed by a call to panic().
|
||||
// Arguments are handled in the manner of [fmt.Print].
|
||||
Panic(ctx context.Context, v ...interface{})
|
||||
|
||||
// Tracef emits a [LogLevelTrace] log to Sentry.
|
||||
// Arguments are handled in the manner of [fmt.Printf].
|
||||
Tracef(ctx context.Context, format string, v ...interface{})
|
||||
// Debugf emits a [LogLevelDebug] log to Sentry.
|
||||
// Arguments are handled in the manner of [fmt.Printf].
|
||||
Debugf(ctx context.Context, format string, v ...interface{})
|
||||
// Infof emits a [LogLevelInfo] log to Sentry.
|
||||
// Arguments are handled in the manner of [fmt.Printf].
|
||||
Infof(ctx context.Context, format string, v ...interface{})
|
||||
// Warnf emits a [LogLevelWarn] log to Sentry.
|
||||
// Arguments are handled in the manner of [fmt.Printf].
|
||||
Warnf(ctx context.Context, format string, v ...interface{})
|
||||
// Errorf emits a [LogLevelError] log to Sentry.
|
||||
// Arguments are handled in the manner of [fmt.Printf].
|
||||
Errorf(ctx context.Context, format string, v ...interface{})
|
||||
// Fatalf emits a [LogLevelFatal] log to Sentry followed by a call to [os.Exit](1).
|
||||
// Arguments are handled in the manner of [fmt.Printf].
|
||||
Fatalf(ctx context.Context, format string, v ...interface{})
|
||||
// Panicf emits a [LogLevelFatal] log to Sentry followed by a call to panic().
|
||||
// Arguments are handled in the manner of [fmt.Printf].
|
||||
Panicf(ctx context.Context, format string, v ...interface{})
|
||||
// SetAttributes allows attaching parameters to the log message using the attribute API.
|
||||
// SetAttributes allows attaching parameters to the logger using the attribute API.
|
||||
// These attributes will be included in all subsequent log entries.
|
||||
SetAttributes(...attribute.Builder)
|
||||
|
||||
// Trace defines the [sentry.LogLevel] for the log entry.
|
||||
Trace() LogEntry
|
||||
// Debug defines the [sentry.LogLevel] for the log entry.
|
||||
Debug() LogEntry
|
||||
// Info defines the [sentry.LogLevel] for the log entry.
|
||||
Info() LogEntry
|
||||
// Warn defines the [sentry.LogLevel] for the log entry.
|
||||
Warn() LogEntry
|
||||
// Error defines the [sentry.LogLevel] for the log entry.
|
||||
Error() LogEntry
|
||||
// Fatal defines the [sentry.LogLevel] for the log entry.
|
||||
Fatal() LogEntry
|
||||
// Panic defines the [sentry.LogLevel] for the log entry.
|
||||
Panic() LogEntry
|
||||
// GetCtx returns the [context.Context] set on the logger.
|
||||
GetCtx() context.Context
|
||||
}
|
||||
|
||||
// LogEntry defines the interface for a log entry that supports chaining attributes.
|
||||
type LogEntry interface {
|
||||
// WithCtx creates a new LogEntry with the specified context without overwriting the previous one.
|
||||
WithCtx(ctx context.Context) LogEntry
|
||||
// String adds a string attribute to the LogEntry.
|
||||
String(key, value string) LogEntry
|
||||
// Int adds an int attribute to the LogEntry.
|
||||
Int(key string, value int) LogEntry
|
||||
// Int64 adds an int64 attribute to the LogEntry.
|
||||
Int64(key string, value int64) LogEntry
|
||||
// Float64 adds a float64 attribute to the LogEntry.
|
||||
Float64(key string, value float64) LogEntry
|
||||
// Bool adds a bool attribute to the LogEntry.
|
||||
Bool(key string, value bool) LogEntry
|
||||
// Emit emits the LogEntry with the provided arguments.
|
||||
Emit(args ...interface{})
|
||||
// Emitf emits the LogEntry using a format string and arguments.
|
||||
Emitf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// Attachment allows associating files with your events to aid in investigation.
|
||||
|
|
@ -595,16 +593,33 @@ func (e *Event) checkInMarshalJSON() ([]byte, error) {
|
|||
|
||||
if e.MonitorConfig != nil {
|
||||
checkIn.MonitorConfig = &MonitorConfig{
|
||||
Schedule: e.MonitorConfig.Schedule,
|
||||
CheckInMargin: e.MonitorConfig.CheckInMargin,
|
||||
MaxRuntime: e.MonitorConfig.MaxRuntime,
|
||||
Timezone: e.MonitorConfig.Timezone,
|
||||
Schedule: e.MonitorConfig.Schedule,
|
||||
CheckInMargin: e.MonitorConfig.CheckInMargin,
|
||||
MaxRuntime: e.MonitorConfig.MaxRuntime,
|
||||
Timezone: e.MonitorConfig.Timezone,
|
||||
FailureIssueThreshold: e.MonitorConfig.FailureIssueThreshold,
|
||||
RecoveryThreshold: e.MonitorConfig.RecoveryThreshold,
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(checkIn)
|
||||
}
|
||||
|
||||
func (e *Event) toCategory() ratelimit.Category {
|
||||
switch e.Type {
|
||||
case "":
|
||||
return ratelimit.CategoryError
|
||||
case transactionType:
|
||||
return ratelimit.CategoryTransaction
|
||||
case logEvent.Type:
|
||||
return ratelimit.CategoryLog
|
||||
case checkInType:
|
||||
return ratelimit.CategoryMonitor
|
||||
default:
|
||||
return ratelimit.CategoryUnknown
|
||||
}
|
||||
}
|
||||
|
||||
// NewEvent creates a new Event.
|
||||
func NewEvent() *Event {
|
||||
return &Event{
|
||||
|
|
@ -644,7 +659,17 @@ type Log struct {
|
|||
Attributes map[string]Attribute `json:"attributes,omitempty"`
|
||||
}
|
||||
|
||||
type AttrType string
|
||||
|
||||
const (
|
||||
AttributeInvalid AttrType = ""
|
||||
AttributeBool AttrType = "boolean"
|
||||
AttributeInt AttrType = "integer"
|
||||
AttributeFloat AttrType = "double"
|
||||
AttributeString AttrType = "string"
|
||||
)
|
||||
|
||||
type Attribute struct {
|
||||
Value any `json:"value"`
|
||||
Type string `json:"type"`
|
||||
Value any `json:"value"`
|
||||
Type AttrType `json:"type"`
|
||||
}
|
||||
|
|
|
|||
36
vendor/github.com/getsentry/sentry-go/internal/ratelimit/category.go
generated
vendored
36
vendor/github.com/getsentry/sentry-go/internal/ratelimit/category.go
generated
vendored
|
|
@ -14,12 +14,14 @@ import (
|
|||
// and, therefore, rate limited.
|
||||
type Category string
|
||||
|
||||
// Known rate limit categories. As a special case, the CategoryAll applies to
|
||||
// all known payload types.
|
||||
// Known rate limit categories that are specified in rate limit headers.
|
||||
const (
|
||||
CategoryAll Category = ""
|
||||
CategoryUnknown Category = "unknown" // Unknown category should not get rate limited
|
||||
CategoryAll Category = "" // Special category for empty categories (applies to all)
|
||||
CategoryError Category = "error"
|
||||
CategoryTransaction Category = "transaction"
|
||||
CategoryLog Category = "log_item"
|
||||
CategoryMonitor Category = "monitor"
|
||||
)
|
||||
|
||||
// knownCategories is the set of currently known categories. Other categories
|
||||
|
|
@ -28,18 +30,30 @@ var knownCategories = map[Category]struct{}{
|
|||
CategoryAll: {},
|
||||
CategoryError: {},
|
||||
CategoryTransaction: {},
|
||||
CategoryLog: {},
|
||||
CategoryMonitor: {},
|
||||
}
|
||||
|
||||
// String returns the category formatted for debugging.
|
||||
func (c Category) String() string {
|
||||
if c == "" {
|
||||
switch c {
|
||||
case CategoryAll:
|
||||
return "CategoryAll"
|
||||
case CategoryError:
|
||||
return "CategoryError"
|
||||
case CategoryTransaction:
|
||||
return "CategoryTransaction"
|
||||
case CategoryLog:
|
||||
return "CategoryLog"
|
||||
case CategoryMonitor:
|
||||
return "CategoryMonitor"
|
||||
default:
|
||||
// For unknown categories, use the original formatting logic
|
||||
caser := cases.Title(language.English)
|
||||
rv := "Category"
|
||||
for _, w := range strings.Fields(string(c)) {
|
||||
rv += caser.String(w)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
caser := cases.Title(language.English)
|
||||
rv := "Category"
|
||||
for _, w := range strings.Fields(string(c)) {
|
||||
rv += caser.String(w)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
|
|
|||
268
vendor/github.com/getsentry/sentry-go/log.go
generated
vendored
268
vendor/github.com/getsentry/sentry-go/log.go
generated
vendored
|
|
@ -3,8 +3,10 @@ package sentry
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go/attribute"
|
||||
|
|
@ -30,17 +32,28 @@ const (
|
|||
LogSeverityFatal int = 21
|
||||
)
|
||||
|
||||
var mapTypesToStr = map[attribute.Type]string{
|
||||
attribute.INVALID: "",
|
||||
attribute.BOOL: "boolean",
|
||||
attribute.INT64: "integer",
|
||||
attribute.FLOAT64: "double",
|
||||
attribute.STRING: "string",
|
||||
var mapTypesToStr = map[attribute.Type]AttrType{
|
||||
attribute.INVALID: AttributeInvalid,
|
||||
attribute.BOOL: AttributeBool,
|
||||
attribute.INT64: AttributeInt,
|
||||
attribute.FLOAT64: AttributeFloat,
|
||||
attribute.STRING: AttributeString,
|
||||
}
|
||||
|
||||
type sentryLogger struct {
|
||||
ctx context.Context
|
||||
client *Client
|
||||
attributes map[string]Attribute
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
type logEntry struct {
|
||||
logger *sentryLogger
|
||||
ctx context.Context
|
||||
level LogLevel
|
||||
severity int
|
||||
attributes map[string]Attribute
|
||||
shouldPanic bool
|
||||
}
|
||||
|
||||
// NewLogger returns a Logger that emits logs to Sentry. If logging is turned off, all logs get discarded.
|
||||
|
|
@ -53,7 +66,12 @@ func NewLogger(ctx context.Context) Logger {
|
|||
|
||||
client := hub.Client()
|
||||
if client != nil && client.batchLogger != nil {
|
||||
return &sentryLogger{client, make(map[string]Attribute)}
|
||||
return &sentryLogger{
|
||||
ctx: ctx,
|
||||
client: client,
|
||||
attributes: make(map[string]Attribute),
|
||||
mu: sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
DebugLogger.Println("fallback to noopLogger: enableLogs disabled")
|
||||
|
|
@ -63,11 +81,11 @@ func NewLogger(ctx context.Context) Logger {
|
|||
func (l *sentryLogger) Write(p []byte) (int, error) {
|
||||
// Avoid sending double newlines to Sentry
|
||||
msg := strings.TrimRight(string(p), "\n")
|
||||
l.log(context.Background(), LogLevelInfo, LogSeverityInfo, msg)
|
||||
l.Info().Emit(msg)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (l *sentryLogger) log(ctx context.Context, level LogLevel, severity int, message string, args ...interface{}) {
|
||||
func (l *sentryLogger) log(ctx context.Context, level LogLevel, severity int, message string, entryAttrs map[string]Attribute, args ...interface{}) {
|
||||
if message == "" {
|
||||
return
|
||||
}
|
||||
|
|
@ -78,71 +96,77 @@ func (l *sentryLogger) log(ctx context.Context, level LogLevel, severity int, me
|
|||
|
||||
var traceID TraceID
|
||||
var spanID SpanID
|
||||
var span *Span
|
||||
var user User
|
||||
|
||||
span := hub.Scope().span
|
||||
if span != nil {
|
||||
traceID = span.TraceID
|
||||
spanID = span.SpanID
|
||||
} else {
|
||||
traceID = hub.Scope().propagationContext.TraceID
|
||||
scope := hub.Scope()
|
||||
if scope != nil {
|
||||
scope.mu.Lock()
|
||||
span = scope.span
|
||||
if span != nil {
|
||||
traceID = span.TraceID
|
||||
spanID = span.SpanID
|
||||
} else {
|
||||
traceID = scope.propagationContext.TraceID
|
||||
}
|
||||
user = scope.user
|
||||
scope.mu.Unlock()
|
||||
}
|
||||
|
||||
attrs := map[string]Attribute{}
|
||||
if len(args) > 0 {
|
||||
attrs["sentry.message.template"] = Attribute{
|
||||
Value: message, Type: "string",
|
||||
Value: message, Type: AttributeString,
|
||||
}
|
||||
for i, p := range args {
|
||||
attrs[fmt.Sprintf("sentry.message.parameters.%d", i)] = Attribute{
|
||||
Value: fmt.Sprint(p), Type: "string",
|
||||
Value: fmt.Sprintf("%+v", p), Type: AttributeString,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If `log` was called with SetAttributes, pass the attributes to attrs
|
||||
if len(l.attributes) > 0 {
|
||||
for k, v := range l.attributes {
|
||||
attrs[k] = v
|
||||
}
|
||||
// flush attributes from logger after send
|
||||
clear(l.attributes)
|
||||
l.mu.RLock()
|
||||
for k, v := range l.attributes {
|
||||
attrs[k] = v
|
||||
}
|
||||
l.mu.RUnlock()
|
||||
|
||||
for k, v := range entryAttrs {
|
||||
attrs[k] = v
|
||||
}
|
||||
|
||||
// Set default attributes
|
||||
if release := l.client.options.Release; release != "" {
|
||||
attrs["sentry.release"] = Attribute{Value: release, Type: "string"}
|
||||
attrs["sentry.release"] = Attribute{Value: release, Type: AttributeString}
|
||||
}
|
||||
if environment := l.client.options.Environment; environment != "" {
|
||||
attrs["sentry.environment"] = Attribute{Value: environment, Type: "string"}
|
||||
attrs["sentry.environment"] = Attribute{Value: environment, Type: AttributeString}
|
||||
}
|
||||
if serverName := l.client.options.ServerName; serverName != "" {
|
||||
attrs["sentry.server.address"] = Attribute{Value: serverName, Type: "string"}
|
||||
attrs["sentry.server.address"] = Attribute{Value: serverName, Type: AttributeString}
|
||||
} else if serverAddr, err := os.Hostname(); err == nil {
|
||||
attrs["sentry.server.address"] = Attribute{Value: serverAddr, Type: "string"}
|
||||
attrs["sentry.server.address"] = Attribute{Value: serverAddr, Type: AttributeString}
|
||||
}
|
||||
scope := hub.Scope()
|
||||
if scope != nil {
|
||||
user := scope.user
|
||||
if !user.IsEmpty() {
|
||||
if user.ID != "" {
|
||||
attrs["user.id"] = Attribute{Value: user.ID, Type: "string"}
|
||||
}
|
||||
if user.Name != "" {
|
||||
attrs["user.name"] = Attribute{Value: user.Name, Type: "string"}
|
||||
}
|
||||
if user.Email != "" {
|
||||
attrs["user.email"] = Attribute{Value: user.Email, Type: "string"}
|
||||
}
|
||||
|
||||
if !user.IsEmpty() {
|
||||
if user.ID != "" {
|
||||
attrs["user.id"] = Attribute{Value: user.ID, Type: AttributeString}
|
||||
}
|
||||
if user.Name != "" {
|
||||
attrs["user.name"] = Attribute{Value: user.Name, Type: AttributeString}
|
||||
}
|
||||
if user.Email != "" {
|
||||
attrs["user.email"] = Attribute{Value: user.Email, Type: AttributeString}
|
||||
}
|
||||
}
|
||||
if spanID.String() != "0000000000000000" {
|
||||
attrs["sentry.trace.parent_span_id"] = Attribute{Value: spanID.String(), Type: "string"}
|
||||
if span != nil {
|
||||
attrs["sentry.trace.parent_span_id"] = Attribute{Value: spanID.String(), Type: AttributeString}
|
||||
}
|
||||
if sdkIdentifier := l.client.sdkIdentifier; sdkIdentifier != "" {
|
||||
attrs["sentry.sdk.name"] = Attribute{Value: sdkIdentifier, Type: "string"}
|
||||
attrs["sentry.sdk.name"] = Attribute{Value: sdkIdentifier, Type: AttributeString}
|
||||
}
|
||||
if sdkVersion := l.client.sdkVersion; sdkVersion != "" {
|
||||
attrs["sentry.sdk.version"] = Attribute{Value: sdkVersion, Type: "string"}
|
||||
attrs["sentry.sdk.version"] = Attribute{Value: sdkVersion, Type: AttributeString}
|
||||
}
|
||||
|
||||
log := &Log{
|
||||
|
|
@ -168,6 +192,9 @@ func (l *sentryLogger) log(ctx context.Context, level LogLevel, severity int, me
|
|||
}
|
||||
|
||||
func (l *sentryLogger) SetAttributes(attrs ...attribute.Builder) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
for _, v := range attrs {
|
||||
t, ok := mapTypesToStr[v.Value.Type()]
|
||||
if !ok || t == "" {
|
||||
|
|
@ -182,49 +209,136 @@ func (l *sentryLogger) SetAttributes(attrs ...attribute.Builder) {
|
|||
}
|
||||
}
|
||||
|
||||
func (l *sentryLogger) Trace(ctx context.Context, v ...interface{}) {
|
||||
l.log(ctx, LogLevelTrace, LogSeverityTrace, fmt.Sprint(v...))
|
||||
func (l *sentryLogger) Trace() LogEntry {
|
||||
return &logEntry{
|
||||
logger: l,
|
||||
ctx: l.ctx,
|
||||
level: LogLevelTrace,
|
||||
severity: LogSeverityTrace,
|
||||
attributes: make(map[string]Attribute),
|
||||
}
|
||||
}
|
||||
func (l *sentryLogger) Debug(ctx context.Context, v ...interface{}) {
|
||||
l.log(ctx, LogLevelDebug, LogSeverityDebug, fmt.Sprint(v...))
|
||||
|
||||
func (l *sentryLogger) Debug() LogEntry {
|
||||
return &logEntry{
|
||||
logger: l,
|
||||
ctx: l.ctx,
|
||||
level: LogLevelDebug,
|
||||
severity: LogSeverityDebug,
|
||||
attributes: make(map[string]Attribute),
|
||||
}
|
||||
}
|
||||
func (l *sentryLogger) Info(ctx context.Context, v ...interface{}) {
|
||||
l.log(ctx, LogLevelInfo, LogSeverityInfo, fmt.Sprint(v...))
|
||||
|
||||
func (l *sentryLogger) Info() LogEntry {
|
||||
return &logEntry{
|
||||
logger: l,
|
||||
ctx: l.ctx,
|
||||
level: LogLevelInfo,
|
||||
severity: LogSeverityInfo,
|
||||
attributes: make(map[string]Attribute),
|
||||
}
|
||||
}
|
||||
func (l *sentryLogger) Warn(ctx context.Context, v ...interface{}) {
|
||||
l.log(ctx, LogLevelWarn, LogSeverityWarning, fmt.Sprint(v...))
|
||||
|
||||
func (l *sentryLogger) Warn() LogEntry {
|
||||
return &logEntry{
|
||||
logger: l,
|
||||
ctx: l.ctx,
|
||||
level: LogLevelWarn,
|
||||
severity: LogSeverityWarning,
|
||||
attributes: make(map[string]Attribute),
|
||||
}
|
||||
}
|
||||
func (l *sentryLogger) Error(ctx context.Context, v ...interface{}) {
|
||||
l.log(ctx, LogLevelError, LogSeverityError, fmt.Sprint(v...))
|
||||
|
||||
func (l *sentryLogger) Error() LogEntry {
|
||||
return &logEntry{
|
||||
logger: l,
|
||||
ctx: l.ctx,
|
||||
level: LogLevelError,
|
||||
severity: LogSeverityError,
|
||||
attributes: make(map[string]Attribute),
|
||||
}
|
||||
}
|
||||
func (l *sentryLogger) Fatal(ctx context.Context, v ...interface{}) {
|
||||
l.log(ctx, LogLevelFatal, LogSeverityFatal, fmt.Sprint(v...))
|
||||
os.Exit(1)
|
||||
|
||||
func (l *sentryLogger) Fatal() LogEntry {
|
||||
return &logEntry{
|
||||
logger: l,
|
||||
ctx: l.ctx,
|
||||
level: LogLevelFatal,
|
||||
severity: LogSeverityFatal,
|
||||
attributes: make(map[string]Attribute),
|
||||
}
|
||||
}
|
||||
func (l *sentryLogger) Panic(ctx context.Context, v ...interface{}) {
|
||||
l.log(ctx, LogLevelFatal, LogSeverityFatal, fmt.Sprint(v...))
|
||||
panic(fmt.Sprint(v...))
|
||||
|
||||
func (l *sentryLogger) Panic() LogEntry {
|
||||
return &logEntry{
|
||||
logger: l,
|
||||
ctx: l.ctx,
|
||||
level: LogLevelFatal,
|
||||
severity: LogSeverityFatal,
|
||||
attributes: make(map[string]Attribute),
|
||||
shouldPanic: true, // this should panic instead of exit
|
||||
}
|
||||
}
|
||||
func (l *sentryLogger) Tracef(ctx context.Context, format string, v ...interface{}) {
|
||||
l.log(ctx, LogLevelTrace, LogSeverityTrace, format, v...)
|
||||
|
||||
func (l *sentryLogger) GetCtx() context.Context {
|
||||
return l.ctx
|
||||
}
|
||||
func (l *sentryLogger) Debugf(ctx context.Context, format string, v ...interface{}) {
|
||||
l.log(ctx, LogLevelDebug, LogSeverityDebug, format, v...)
|
||||
|
||||
func (e *logEntry) WithCtx(ctx context.Context) LogEntry {
|
||||
return &logEntry{
|
||||
logger: e.logger,
|
||||
ctx: ctx,
|
||||
level: e.level,
|
||||
severity: e.severity,
|
||||
attributes: maps.Clone(e.attributes),
|
||||
shouldPanic: e.shouldPanic,
|
||||
}
|
||||
}
|
||||
func (l *sentryLogger) Infof(ctx context.Context, format string, v ...interface{}) {
|
||||
l.log(ctx, LogLevelInfo, LogSeverityInfo, format, v...)
|
||||
|
||||
func (e *logEntry) String(key, value string) LogEntry {
|
||||
e.attributes[key] = Attribute{Value: value, Type: AttributeString}
|
||||
return e
|
||||
}
|
||||
func (l *sentryLogger) Warnf(ctx context.Context, format string, v ...interface{}) {
|
||||
l.log(ctx, LogLevelWarn, LogSeverityWarning, format, v...)
|
||||
|
||||
func (e *logEntry) Int(key string, value int) LogEntry {
|
||||
e.attributes[key] = Attribute{Value: int64(value), Type: AttributeInt}
|
||||
return e
|
||||
}
|
||||
func (l *sentryLogger) Errorf(ctx context.Context, format string, v ...interface{}) {
|
||||
l.log(ctx, LogLevelError, LogSeverityError, format, v...)
|
||||
|
||||
func (e *logEntry) Int64(key string, value int64) LogEntry {
|
||||
e.attributes[key] = Attribute{Value: value, Type: AttributeInt}
|
||||
return e
|
||||
}
|
||||
func (l *sentryLogger) Fatalf(ctx context.Context, format string, v ...interface{}) {
|
||||
l.log(ctx, LogLevelFatal, LogSeverityFatal, format, v...)
|
||||
os.Exit(1)
|
||||
|
||||
func (e *logEntry) Float64(key string, value float64) LogEntry {
|
||||
e.attributes[key] = Attribute{Value: value, Type: AttributeFloat}
|
||||
return e
|
||||
}
|
||||
func (l *sentryLogger) Panicf(ctx context.Context, format string, v ...interface{}) {
|
||||
l.log(ctx, LogLevelFatal, LogSeverityFatal, format, v...)
|
||||
panic(fmt.Sprint(v...))
|
||||
|
||||
func (e *logEntry) Bool(key string, value bool) LogEntry {
|
||||
e.attributes[key] = Attribute{Value: value, Type: AttributeBool}
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *logEntry) Emit(args ...interface{}) {
|
||||
e.logger.log(e.ctx, e.level, e.severity, fmt.Sprint(args...), e.attributes)
|
||||
|
||||
if e.level == LogLevelFatal {
|
||||
if e.shouldPanic {
|
||||
panic(fmt.Sprint(args...))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *logEntry) Emitf(format string, args ...interface{}) {
|
||||
e.logger.log(e.ctx, e.level, e.severity, format, e.attributes, args...)
|
||||
|
||||
if e.level == LogLevelFatal {
|
||||
if e.shouldPanic {
|
||||
formattedMessage := fmt.Sprintf(format, args...)
|
||||
panic(formattedMessage)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
105
vendor/github.com/getsentry/sentry-go/log_fallback.go
generated
vendored
105
vendor/github.com/getsentry/sentry-go/log_fallback.go
generated
vendored
|
|
@ -11,55 +11,94 @@ import (
|
|||
// Fallback, no-op logger if logging is disabled.
|
||||
type noopLogger struct{}
|
||||
|
||||
func (*noopLogger) Trace(_ context.Context, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelTrace)
|
||||
// noopLogEntry implements LogEntry for the no-op logger.
|
||||
type noopLogEntry struct {
|
||||
level LogLevel
|
||||
shouldPanic bool
|
||||
}
|
||||
func (*noopLogger) Debug(_ context.Context, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelDebug)
|
||||
|
||||
func (n *noopLogEntry) WithCtx(_ context.Context) LogEntry {
|
||||
return n
|
||||
}
|
||||
func (*noopLogger) Info(_ context.Context, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelInfo)
|
||||
|
||||
func (n *noopLogEntry) String(_, _ string) LogEntry {
|
||||
return n
|
||||
}
|
||||
func (*noopLogger) Warn(_ context.Context, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelWarn)
|
||||
|
||||
func (n *noopLogEntry) Int(_ string, _ int) LogEntry {
|
||||
return n
|
||||
}
|
||||
func (*noopLogger) Error(_ context.Context, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelError)
|
||||
|
||||
func (n *noopLogEntry) Int64(_ string, _ int64) LogEntry {
|
||||
return n
|
||||
}
|
||||
func (*noopLogger) Fatal(_ context.Context, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelFatal)
|
||||
os.Exit(1)
|
||||
|
||||
func (n *noopLogEntry) Float64(_ string, _ float64) LogEntry {
|
||||
return n
|
||||
}
|
||||
func (*noopLogger) Panic(_ context.Context, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelFatal)
|
||||
panic(fmt.Sprintf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelFatal))
|
||||
|
||||
func (n *noopLogEntry) Bool(_ string, _ bool) LogEntry {
|
||||
return n
|
||||
}
|
||||
func (*noopLogger) Tracef(_ context.Context, _ string, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelTrace)
|
||||
|
||||
func (n *noopLogEntry) Attributes(_ ...attribute.Builder) LogEntry {
|
||||
return n
|
||||
}
|
||||
func (*noopLogger) Debugf(_ context.Context, _ string, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelDebug)
|
||||
|
||||
func (n *noopLogEntry) Emit(args ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", n.level)
|
||||
if n.level == LogLevelFatal {
|
||||
if n.shouldPanic {
|
||||
panic(args)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
func (*noopLogger) Infof(_ context.Context, _ string, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelInfo)
|
||||
|
||||
func (n *noopLogEntry) Emitf(message string, args ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", n.level)
|
||||
if n.level == LogLevelFatal {
|
||||
if n.shouldPanic {
|
||||
panic(fmt.Sprintf(message, args...))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
func (*noopLogger) Warnf(_ context.Context, _ string, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelWarn)
|
||||
|
||||
func (n *noopLogger) GetCtx() context.Context { return context.Background() }
|
||||
|
||||
func (*noopLogger) Trace() LogEntry {
|
||||
return &noopLogEntry{level: LogLevelTrace}
|
||||
}
|
||||
func (*noopLogger) Errorf(_ context.Context, _ string, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelError)
|
||||
|
||||
func (*noopLogger) Debug() LogEntry {
|
||||
return &noopLogEntry{level: LogLevelDebug}
|
||||
}
|
||||
func (*noopLogger) Fatalf(_ context.Context, _ string, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelFatal)
|
||||
os.Exit(1)
|
||||
|
||||
func (*noopLogger) Info() LogEntry {
|
||||
return &noopLogEntry{level: LogLevelInfo}
|
||||
}
|
||||
func (*noopLogger) Panicf(_ context.Context, _ string, _ ...interface{}) {
|
||||
DebugLogger.Printf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelFatal)
|
||||
panic(fmt.Sprintf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelFatal))
|
||||
|
||||
func (*noopLogger) Warn() LogEntry {
|
||||
return &noopLogEntry{level: LogLevelWarn}
|
||||
}
|
||||
|
||||
func (*noopLogger) Error() LogEntry {
|
||||
return &noopLogEntry{level: LogLevelError}
|
||||
}
|
||||
|
||||
func (*noopLogger) Fatal() LogEntry {
|
||||
return &noopLogEntry{level: LogLevelFatal}
|
||||
}
|
||||
|
||||
func (*noopLogger) Panic() LogEntry {
|
||||
return &noopLogEntry{level: LogLevelFatal, shouldPanic: true}
|
||||
}
|
||||
|
||||
func (*noopLogger) SetAttributes(...attribute.Builder) {
|
||||
DebugLogger.Printf("No attributes attached. Turn on logging via EnableLogs")
|
||||
}
|
||||
|
||||
func (*noopLogger) Write(_ []byte) (n int, err error) {
|
||||
return 0, fmt.Errorf("Log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelInfo)
|
||||
return 0, fmt.Errorf("log with level=[%v] is being dropped. Turn on logging via EnableLogs", LogLevelInfo)
|
||||
}
|
||||
|
|
|
|||
3
vendor/github.com/getsentry/sentry-go/scope.go
generated
vendored
3
vendor/github.com/getsentry/sentry-go/scope.go
generated
vendored
|
|
@ -304,6 +304,9 @@ func (scope *Scope) SetPropagationContext(propagationContext PropagationContext)
|
|||
|
||||
// GetSpan returns the span from the current scope.
|
||||
func (scope *Scope) GetSpan() *Span {
|
||||
scope.mu.RLock()
|
||||
defer scope.mu.RUnlock()
|
||||
|
||||
return scope.span
|
||||
}
|
||||
|
||||
|
|
|
|||
2
vendor/github.com/getsentry/sentry-go/sentry.go
generated
vendored
2
vendor/github.com/getsentry/sentry-go/sentry.go
generated
vendored
|
|
@ -6,7 +6,7 @@ import (
|
|||
)
|
||||
|
||||
// The version of the SDK.
|
||||
const SDKVersion = "0.34.0"
|
||||
const SDKVersion = "0.35.3"
|
||||
|
||||
// apiVersion is the minimum version of the Sentry API compatible with the
|
||||
// sentry-go SDK.
|
||||
|
|
|
|||
32
vendor/github.com/getsentry/sentry-go/transport.go
generated
vendored
32
vendor/github.com/getsentry/sentry-go/transport.go
generated
vendored
|
|
@ -262,17 +262,6 @@ func getRequestFromEvent(ctx context.Context, event *Event, dsn *Dsn) (r *http.R
|
|||
)
|
||||
}
|
||||
|
||||
func categoryFor(eventType string) ratelimit.Category {
|
||||
switch eventType {
|
||||
case "":
|
||||
return ratelimit.CategoryError
|
||||
case transactionType:
|
||||
return ratelimit.CategoryTransaction
|
||||
default:
|
||||
return ratelimit.Category(eventType)
|
||||
}
|
||||
}
|
||||
|
||||
// ================================
|
||||
// HTTPTransport
|
||||
// ================================
|
||||
|
|
@ -303,7 +292,8 @@ type HTTPTransport struct {
|
|||
// current in-flight items and starts a new batch for subsequent events.
|
||||
buffer chan batch
|
||||
|
||||
start sync.Once
|
||||
startOnce sync.Once
|
||||
closeOnce sync.Once
|
||||
|
||||
// Size of the transport buffer. Defaults to 30.
|
||||
BufferSize int
|
||||
|
|
@ -364,7 +354,7 @@ func (t *HTTPTransport) Configure(options ClientOptions) {
|
|||
}
|
||||
}
|
||||
|
||||
t.start.Do(func() {
|
||||
t.startOnce.Do(func() {
|
||||
go t.worker()
|
||||
})
|
||||
}
|
||||
|
|
@ -380,7 +370,7 @@ func (t *HTTPTransport) SendEventWithContext(ctx context.Context, event *Event)
|
|||
return
|
||||
}
|
||||
|
||||
category := categoryFor(event.Type)
|
||||
category := event.toCategory()
|
||||
|
||||
if t.disabled(category) {
|
||||
return
|
||||
|
|
@ -440,11 +430,9 @@ func (t *HTTPTransport) SendEventWithContext(ctx context.Context, event *Event)
|
|||
// have the SDK send events over the network synchronously, configure it to use
|
||||
// the HTTPSyncTransport in the call to Init.
|
||||
func (t *HTTPTransport) Flush(timeout time.Duration) bool {
|
||||
timeoutCh := make(chan struct{})
|
||||
time.AfterFunc(timeout, func() {
|
||||
close(timeoutCh)
|
||||
})
|
||||
return t.flushInternal(timeoutCh)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
return t.FlushWithContext(ctx)
|
||||
}
|
||||
|
||||
// FlushWithContext works like Flush, but it accepts a context.Context instead of a timeout.
|
||||
|
|
@ -506,7 +494,9 @@ fail:
|
|||
// Close should be called after Flush and before terminating the program
|
||||
// otherwise some events may be lost.
|
||||
func (t *HTTPTransport) Close() {
|
||||
close(t.done)
|
||||
t.closeOnce.Do(func() {
|
||||
close(t.done)
|
||||
})
|
||||
}
|
||||
|
||||
func (t *HTTPTransport) worker() {
|
||||
|
|
@ -652,7 +642,7 @@ func (t *HTTPSyncTransport) SendEventWithContext(ctx context.Context, event *Eve
|
|||
return
|
||||
}
|
||||
|
||||
if t.disabled(categoryFor(event.Type)) {
|
||||
if t.disabled(event.toCategory()) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
43
vendor/github.com/mattn/go-runewidth/benchstat.txt
generated
vendored
Normal file
43
vendor/github.com/mattn/go-runewidth/benchstat.txt
generated
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
goos: darwin
|
||||
goarch: arm64
|
||||
pkg: github.com/mattn/go-runewidth
|
||||
cpu: Apple M2
|
||||
│ old.txt │ new.txt │
|
||||
│ sec/op │ sec/op vs base │
|
||||
String1WidthAll/regular-8 108.92m ± 0% 35.09m ± 3% -67.78% (p=0.002 n=6)
|
||||
String1WidthAll/lut-8 93.97m ± 0% 18.70m ± 0% -80.10% (p=0.002 n=6)
|
||||
String1Width768/regular-8 60.62µ ± 1% 11.54µ ± 0% -80.97% (p=0.002 n=6)
|
||||
String1Width768/lut-8 60.66µ ± 1% 11.43µ ± 0% -81.16% (p=0.002 n=6)
|
||||
String1WidthAllEastAsian/regular-8 115.13m ± 1% 40.79m ± 8% -64.57% (p=0.002 n=6)
|
||||
String1WidthAllEastAsian/lut-8 93.65m ± 0% 18.70m ± 2% -80.03% (p=0.002 n=6)
|
||||
String1Width768EastAsian/regular-8 75.32µ ± 0% 23.49µ ± 0% -68.82% (p=0.002 n=6)
|
||||
String1Width768EastAsian/lut-8 60.76µ ± 0% 11.50µ ± 0% -81.07% (p=0.002 n=6)
|
||||
geomean 2.562m 604.5µ -76.41%
|
||||
|
||||
│ old.txt │ new.txt │
|
||||
│ B/op │ B/op vs base │
|
||||
String1WidthAll/regular-8 106.3Mi ± 0% 0.0Mi ± 0% -100.00% (p=0.002 n=6)
|
||||
String1WidthAll/lut-8 106.3Mi ± 0% 0.0Mi ± 0% -100.00% (p=0.002 n=6)
|
||||
String1Width768/regular-8 75.00Ki ± 0% 0.00Ki ± 0% -100.00% (p=0.002 n=6)
|
||||
String1Width768/lut-8 75.00Ki ± 0% 0.00Ki ± 0% -100.00% (p=0.002 n=6)
|
||||
String1WidthAllEastAsian/regular-8 106.3Mi ± 0% 0.0Mi ± 0% -100.00% (p=0.002 n=6)
|
||||
String1WidthAllEastAsian/lut-8 106.3Mi ± 0% 0.0Mi ± 0% -100.00% (p=0.002 n=6)
|
||||
String1Width768EastAsian/regular-8 75.00Ki ± 0% 0.00Ki ± 0% -100.00% (p=0.002 n=6)
|
||||
String1Width768EastAsian/lut-8 75.00Ki ± 0% 0.00Ki ± 0% -100.00% (p=0.002 n=6)
|
||||
geomean 2.790Mi ? ¹ ²
|
||||
¹ summaries must be >0 to compute geomean
|
||||
² ratios must be >0 to compute geomean
|
||||
|
||||
│ old.txt │ new.txt │
|
||||
│ allocs/op │ allocs/op vs base │
|
||||
String1WidthAll/regular-8 3.342M ± 0% 0.000M ± 0% -100.00% (p=0.002 n=6)
|
||||
String1WidthAll/lut-8 3.342M ± 0% 0.000M ± 0% -100.00% (p=0.002 n=6)
|
||||
String1Width768/regular-8 2.304k ± 0% 0.000k ± 0% -100.00% (p=0.002 n=6)
|
||||
String1Width768/lut-8 2.304k ± 0% 0.000k ± 0% -100.00% (p=0.002 n=6)
|
||||
String1WidthAllEastAsian/regular-8 3.342M ± 0% 0.000M ± 0% -100.00% (p=0.002 n=6)
|
||||
String1WidthAllEastAsian/lut-8 3.342M ± 0% 0.000M ± 0% -100.00% (p=0.002 n=6)
|
||||
String1Width768EastAsian/regular-8 2.304k ± 0% 0.000k ± 0% -100.00% (p=0.002 n=6)
|
||||
String1Width768EastAsian/lut-8 2.304k ± 0% 0.000k ± 0% -100.00% (p=0.002 n=6)
|
||||
geomean 87.75k ? ¹ ²
|
||||
¹ summaries must be >0 to compute geomean
|
||||
² ratios must be >0 to compute geomean
|
||||
54
vendor/github.com/mattn/go-runewidth/new.txt
generated
vendored
Normal file
54
vendor/github.com/mattn/go-runewidth/new.txt
generated
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
goos: darwin
|
||||
goarch: arm64
|
||||
pkg: github.com/mattn/go-runewidth
|
||||
cpu: Apple M2
|
||||
BenchmarkString1WidthAll/regular-8 33 35033923 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAll/regular-8 33 34965112 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAll/regular-8 33 36307234 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAll/regular-8 33 35007705 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAll/regular-8 33 35154182 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAll/regular-8 34 35155400 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAll/lut-8 63 18688500 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAll/lut-8 63 18712474 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAll/lut-8 63 18700211 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAll/lut-8 62 18694179 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAll/lut-8 62 18708392 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAll/lut-8 63 18770608 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768/regular-8 104137 11526 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768/regular-8 103986 11540 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768/regular-8 104079 11552 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768/regular-8 103963 11530 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768/regular-8 103714 11538 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768/regular-8 104181 11537 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768/lut-8 105150 11420 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768/lut-8 104778 11423 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768/lut-8 105069 11422 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768/lut-8 105127 11475 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768/lut-8 104742 11433 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768/lut-8 105163 11432 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/regular-8 28 40723347 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/regular-8 28 40790299 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/regular-8 28 40801338 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/regular-8 28 40798216 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/regular-8 28 44135253 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/regular-8 28 40779546 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/lut-8 62 18694165 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/lut-8 62 18685047 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/lut-8 62 18689273 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/lut-8 62 19150346 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/lut-8 63 19126154 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/lut-8 62 18712619 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768EastAsian/regular-8 50775 23595 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768EastAsian/regular-8 51061 23563 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768EastAsian/regular-8 51057 23492 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768EastAsian/regular-8 51138 23445 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768EastAsian/regular-8 51195 23469 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768EastAsian/regular-8 51087 23482 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768EastAsian/lut-8 104559 11549 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768EastAsian/lut-8 104508 11483 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768EastAsian/lut-8 104296 11503 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768EastAsian/lut-8 104606 11485 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768EastAsian/lut-8 104588 11495 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkString1Width768EastAsian/lut-8 104602 11518 ns/op 0 B/op 0 allocs/op
|
||||
PASS
|
||||
ok github.com/mattn/go-runewidth 64.455s
|
||||
54
vendor/github.com/mattn/go-runewidth/old.txt
generated
vendored
Normal file
54
vendor/github.com/mattn/go-runewidth/old.txt
generated
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
goos: darwin
|
||||
goarch: arm64
|
||||
pkg: github.com/mattn/go-runewidth
|
||||
cpu: Apple M2
|
||||
BenchmarkString1WidthAll/regular-8 10 108559258 ns/op 111412145 B/op 3342342 allocs/op
|
||||
BenchmarkString1WidthAll/regular-8 10 108968079 ns/op 111412364 B/op 3342343 allocs/op
|
||||
BenchmarkString1WidthAll/regular-8 10 108890338 ns/op 111412388 B/op 3342344 allocs/op
|
||||
BenchmarkString1WidthAll/regular-8 10 108940704 ns/op 111412584 B/op 3342346 allocs/op
|
||||
BenchmarkString1WidthAll/regular-8 10 108632796 ns/op 111412348 B/op 3342343 allocs/op
|
||||
BenchmarkString1WidthAll/regular-8 10 109354546 ns/op 111412777 B/op 3342343 allocs/op
|
||||
BenchmarkString1WidthAll/lut-8 12 93844406 ns/op 111412569 B/op 3342345 allocs/op
|
||||
BenchmarkString1WidthAll/lut-8 12 93991080 ns/op 111412512 B/op 3342344 allocs/op
|
||||
BenchmarkString1WidthAll/lut-8 12 93980632 ns/op 111412413 B/op 3342343 allocs/op
|
||||
BenchmarkString1WidthAll/lut-8 12 94004083 ns/op 111412396 B/op 3342343 allocs/op
|
||||
BenchmarkString1WidthAll/lut-8 12 93959795 ns/op 111412445 B/op 3342343 allocs/op
|
||||
BenchmarkString1WidthAll/lut-8 12 93846198 ns/op 111412556 B/op 3342345 allocs/op
|
||||
BenchmarkString1Width768/regular-8 19785 60696 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768/regular-8 19824 60520 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768/regular-8 19832 60547 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768/regular-8 19778 60543 ns/op 76800 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768/regular-8 19842 61142 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768/regular-8 19780 60696 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768/lut-8 19598 61161 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768/lut-8 19731 60707 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768/lut-8 19738 60626 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768/lut-8 19764 60670 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768/lut-8 19797 60642 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768/lut-8 19738 60608 ns/op 76800 B/op 2304 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/regular-8 9 115080431 ns/op 111412458 B/op 3342345 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/regular-8 9 114908880 ns/op 111412476 B/op 3342345 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/regular-8 9 115077134 ns/op 111412540 B/op 3342345 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/regular-8 9 115175292 ns/op 111412467 B/op 3342345 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/regular-8 9 115792653 ns/op 111412362 B/op 3342344 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/regular-8 9 115255417 ns/op 111412572 B/op 3342346 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/lut-8 12 93761542 ns/op 111412538 B/op 3342345 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/lut-8 12 94089990 ns/op 111412440 B/op 3342343 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/lut-8 12 93721410 ns/op 111412514 B/op 3342344 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/lut-8 12 93572951 ns/op 111412329 B/op 3342342 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/lut-8 12 93536052 ns/op 111412206 B/op 3342341 allocs/op
|
||||
BenchmarkString1WidthAllEastAsian/lut-8 12 93532365 ns/op 111412412 B/op 3342343 allocs/op
|
||||
BenchmarkString1Width768EastAsian/regular-8 15904 75401 ns/op 76800 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768EastAsian/regular-8 15932 75449 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768EastAsian/regular-8 15944 75181 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768EastAsian/regular-8 15963 75311 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768EastAsian/regular-8 15879 75292 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768EastAsian/regular-8 15955 75334 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768EastAsian/lut-8 19692 60692 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768EastAsian/lut-8 19712 60699 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768EastAsian/lut-8 19741 60819 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768EastAsian/lut-8 19771 60653 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768EastAsian/lut-8 19737 61027 ns/op 76801 B/op 2304 allocs/op
|
||||
BenchmarkString1Width768EastAsian/lut-8 19657 60820 ns/op 76801 B/op 2304 allocs/op
|
||||
PASS
|
||||
ok github.com/mattn/go-runewidth 76.165s
|
||||
23
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
23
vendor/github.com/mattn/go-runewidth/runewidth.go
generated
vendored
|
|
@ -4,7 +4,7 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rivo/uniseg"
|
||||
"github.com/clipperhouse/uax29/v2/graphemes"
|
||||
)
|
||||
|
||||
//go:generate go run script/generate.go
|
||||
|
|
@ -64,6 +64,9 @@ func inTable(r rune, t table) bool {
|
|||
if r < t[0].first {
|
||||
return false
|
||||
}
|
||||
if r > t[len(t)-1].last {
|
||||
return false
|
||||
}
|
||||
|
||||
bot := 0
|
||||
top := len(t) - 1
|
||||
|
|
@ -175,10 +178,10 @@ func (c *Condition) CreateLUT() {
|
|||
|
||||
// StringWidth return width as you can see
|
||||
func (c *Condition) StringWidth(s string) (width int) {
|
||||
g := uniseg.NewGraphemes(s)
|
||||
g := graphemes.FromString(s)
|
||||
for g.Next() {
|
||||
var chWidth int
|
||||
for _, r := range g.Runes() {
|
||||
for _, r := range g.Value() {
|
||||
chWidth = c.RuneWidth(r)
|
||||
if chWidth > 0 {
|
||||
break // Our best guess at this point is to use the width of the first non-zero-width rune.
|
||||
|
|
@ -197,17 +200,17 @@ func (c *Condition) Truncate(s string, w int, tail string) string {
|
|||
w -= c.StringWidth(tail)
|
||||
var width int
|
||||
pos := len(s)
|
||||
g := uniseg.NewGraphemes(s)
|
||||
g := graphemes.FromString(s)
|
||||
for g.Next() {
|
||||
var chWidth int
|
||||
for _, r := range g.Runes() {
|
||||
for _, r := range g.Value() {
|
||||
chWidth = c.RuneWidth(r)
|
||||
if chWidth > 0 {
|
||||
break // See StringWidth() for details.
|
||||
}
|
||||
}
|
||||
if width+chWidth > w {
|
||||
pos, _ = g.Positions()
|
||||
pos = g.Start()
|
||||
break
|
||||
}
|
||||
width += chWidth
|
||||
|
|
@ -224,10 +227,10 @@ func (c *Condition) TruncateLeft(s string, w int, prefix string) string {
|
|||
var width int
|
||||
pos := len(s)
|
||||
|
||||
g := uniseg.NewGraphemes(s)
|
||||
g := graphemes.FromString(s)
|
||||
for g.Next() {
|
||||
var chWidth int
|
||||
for _, r := range g.Runes() {
|
||||
for _, r := range g.Value() {
|
||||
chWidth = c.RuneWidth(r)
|
||||
if chWidth > 0 {
|
||||
break // See StringWidth() for details.
|
||||
|
|
@ -236,10 +239,10 @@ func (c *Condition) TruncateLeft(s string, w int, prefix string) string {
|
|||
|
||||
if width+chWidth > w {
|
||||
if width < w {
|
||||
_, pos = g.Positions()
|
||||
pos = g.End()
|
||||
prefix += strings.Repeat(" ", width+chWidth-w)
|
||||
} else {
|
||||
pos, _ = g.Positions()
|
||||
pos = g.Start()
|
||||
}
|
||||
|
||||
break
|
||||
|
|
|
|||
6
vendor/github.com/mattn/go-runewidth/runewidth_windows.go
generated
vendored
6
vendor/github.com/mattn/go-runewidth/runewidth_windows.go
generated
vendored
|
|
@ -4,6 +4,7 @@
|
|||
package runewidth
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
|
|
@ -14,6 +15,11 @@ var (
|
|||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
func IsEastAsian() bool {
|
||||
if os.Getenv("WT_SESSION") != "" {
|
||||
// Windows Terminal always not use East Asian Ambiguous Width(s).
|
||||
return false
|
||||
}
|
||||
|
||||
r1, _, _ := procGetConsoleOutputCP.Call()
|
||||
if r1 == 0 {
|
||||
return false
|
||||
|
|
|
|||
2
vendor/github.com/mattn/go-sqlite3/README.md
generated
vendored
2
vendor/github.com/mattn/go-sqlite3/README.md
generated
vendored
|
|
@ -351,6 +351,8 @@ For example the TDM-GCC Toolchain can be found [here](https://jmeubank.github.io
|
|||
|
||||
# User Authentication
|
||||
|
||||
***This is deprecated***
|
||||
|
||||
This package supports the SQLite User Authentication module.
|
||||
|
||||
## Compile
|
||||
|
|
|
|||
4759
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c
generated
vendored
4759
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c
generated
vendored
File diff suppressed because it is too large
Load diff
316
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h
generated
vendored
316
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h
generated
vendored
|
|
@ -134,7 +134,7 @@ extern "C" {
|
|||
**
|
||||
** Since [version 3.6.18] ([dateof:3.6.18]),
|
||||
** SQLite source code has been stored in the
|
||||
** <a href="http://www.fossil-scm.org/">Fossil configuration management
|
||||
** <a href="http://fossil-scm.org/">Fossil configuration management
|
||||
** system</a>. ^The SQLITE_SOURCE_ID macro evaluates to
|
||||
** a string which identifies a particular check-in of SQLite
|
||||
** within its configuration management system. ^The SQLITE_SOURCE_ID
|
||||
|
|
@ -147,9 +147,9 @@ extern "C" {
|
|||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||
** [sqlite_version()] and [sqlite_source_id()].
|
||||
*/
|
||||
#define SQLITE_VERSION "3.49.1"
|
||||
#define SQLITE_VERSION_NUMBER 3049001
|
||||
#define SQLITE_SOURCE_ID "2025-02-18 13:38:58 873d4e274b4988d260ba8354a9718324a1c26187a4ab4c1cc0227c03d0f10e70"
|
||||
#define SQLITE_VERSION "3.50.4"
|
||||
#define SQLITE_VERSION_NUMBER 3050004
|
||||
#define SQLITE_SOURCE_ID "2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3"
|
||||
|
||||
/*
|
||||
** CAPI3REF: Run-Time Library Version Numbers
|
||||
|
|
@ -1164,6 +1164,12 @@ struct sqlite3_io_methods {
|
|||
** the value that M is to be set to. Before returning, the 32-bit signed
|
||||
** integer is overwritten with the previous value of M.
|
||||
**
|
||||
** <li>[[SQLITE_FCNTL_BLOCK_ON_CONNECT]]
|
||||
** The [SQLITE_FCNTL_BLOCK_ON_CONNECT] opcode is used to configure the
|
||||
** VFS to block when taking a SHARED lock to connect to a wal mode database.
|
||||
** This is used to implement the functionality associated with
|
||||
** SQLITE_SETLK_BLOCK_ON_CONNECT.
|
||||
**
|
||||
** <li>[[SQLITE_FCNTL_DATA_VERSION]]
|
||||
** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to
|
||||
** a database file. The argument is a pointer to a 32-bit unsigned integer.
|
||||
|
|
@ -1260,6 +1266,7 @@ struct sqlite3_io_methods {
|
|||
#define SQLITE_FCNTL_CKSM_FILE 41
|
||||
#define SQLITE_FCNTL_RESET_CACHE 42
|
||||
#define SQLITE_FCNTL_NULL_IO 43
|
||||
#define SQLITE_FCNTL_BLOCK_ON_CONNECT 44
|
||||
|
||||
/* deprecated names */
|
||||
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
|
||||
|
|
@ -1990,13 +1997,16 @@ struct sqlite3_mem_methods {
|
|||
**
|
||||
** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt>
|
||||
** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine
|
||||
** the default size of lookaside memory on each [database connection].
|
||||
** the default size of [lookaside memory] on each [database connection].
|
||||
** The first argument is the
|
||||
** size of each lookaside buffer slot and the second is the number of
|
||||
** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE
|
||||
** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE]
|
||||
** option to [sqlite3_db_config()] can be used to change the lookaside
|
||||
** configuration on individual connections.)^ </dd>
|
||||
** size of each lookaside buffer slot ("sz") and the second is the number of
|
||||
** slots allocated to each database connection ("cnt").)^
|
||||
** ^(SQLITE_CONFIG_LOOKASIDE sets the <i>default</i> lookaside size.
|
||||
** The [SQLITE_DBCONFIG_LOOKASIDE] option to [sqlite3_db_config()] can
|
||||
** be used to change the lookaside configuration on individual connections.)^
|
||||
** The [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to change the
|
||||
** default lookaside configuration at compile-time.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt>
|
||||
** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is
|
||||
|
|
@ -2233,31 +2243,50 @@ struct sqlite3_mem_methods {
|
|||
** [[SQLITE_DBCONFIG_LOOKASIDE]]
|
||||
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
|
||||
** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the
|
||||
** configuration of the lookaside memory allocator within a database
|
||||
** configuration of the [lookaside memory allocator] within a database
|
||||
** connection.
|
||||
** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i>
|
||||
** in the [DBCONFIG arguments|usual format].
|
||||
** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two,
|
||||
** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE
|
||||
** should have a total of five parameters.
|
||||
** ^The first argument (the third parameter to [sqlite3_db_config()] is a
|
||||
** <ol>
|
||||
** <li><p>The first argument ("buf") is a
|
||||
** pointer to a memory buffer to use for lookaside memory.
|
||||
** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb
|
||||
** may be NULL in which case SQLite will allocate the
|
||||
** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the
|
||||
** size of each lookaside buffer slot. ^The third argument is the number of
|
||||
** slots. The size of the buffer in the first argument must be greater than
|
||||
** or equal to the product of the second and third arguments. The buffer
|
||||
** must be aligned to an 8-byte boundary. ^If the second argument to
|
||||
** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally
|
||||
** rounded down to the next smaller multiple of 8. ^(The lookaside memory
|
||||
** The first argument may be NULL in which case SQLite will allocate the
|
||||
** lookaside buffer itself using [sqlite3_malloc()].
|
||||
** <li><P>The second argument ("sz") is the
|
||||
** size of each lookaside buffer slot. Lookaside is disabled if "sz"
|
||||
** is less than 8. The "sz" argument should be a multiple of 8 less than
|
||||
** 65536. If "sz" does not meet this constraint, it is reduced in size until
|
||||
** it does.
|
||||
** <li><p>The third argument ("cnt") is the number of slots. Lookaside is disabled
|
||||
** if "cnt"is less than 1. The "cnt" value will be reduced, if necessary, so
|
||||
** that the product of "sz" and "cnt" does not exceed 2,147,418,112. The "cnt"
|
||||
** parameter is usually chosen so that the product of "sz" and "cnt" is less
|
||||
** than 1,000,000.
|
||||
** </ol>
|
||||
** <p>If the "buf" argument is not NULL, then it must
|
||||
** point to a memory buffer with a size that is greater than
|
||||
** or equal to the product of "sz" and "cnt".
|
||||
** The buffer must be aligned to an 8-byte boundary.
|
||||
** The lookaside memory
|
||||
** configuration for a database connection can only be changed when that
|
||||
** connection is not currently using lookaside memory, or in other words
|
||||
** when the "current value" returned by
|
||||
** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero.
|
||||
** when the value returned by [SQLITE_DBSTATUS_LOOKASIDE_USED] is zero.
|
||||
** Any attempt to change the lookaside memory configuration when lookaside
|
||||
** memory is in use leaves the configuration unchanged and returns
|
||||
** [SQLITE_BUSY].)^</dd>
|
||||
** [SQLITE_BUSY].
|
||||
** If the "buf" argument is NULL and an attempt
|
||||
** to allocate memory based on "sz" and "cnt" fails, then
|
||||
** lookaside is silently disabled.
|
||||
** <p>
|
||||
** The [SQLITE_CONFIG_LOOKASIDE] configuration option can be used to set the
|
||||
** default lookaside configuration at initialization. The
|
||||
** [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to set the default lookaside
|
||||
** configuration at compile-time. Typical values for lookaside are 1200 for
|
||||
** "sz" and 40 to 100 for "cnt".
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
|
||||
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
|
||||
|
|
@ -2994,6 +3023,44 @@ SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*);
|
|||
*/
|
||||
SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Set the Setlk Timeout
|
||||
** METHOD: sqlite3
|
||||
**
|
||||
** This routine is only useful in SQLITE_ENABLE_SETLK_TIMEOUT builds. If
|
||||
** the VFS supports blocking locks, it sets the timeout in ms used by
|
||||
** eligible locks taken on wal mode databases by the specified database
|
||||
** handle. In non-SQLITE_ENABLE_SETLK_TIMEOUT builds, or if the VFS does
|
||||
** not support blocking locks, this function is a no-op.
|
||||
**
|
||||
** Passing 0 to this function disables blocking locks altogether. Passing
|
||||
** -1 to this function requests that the VFS blocks for a long time -
|
||||
** indefinitely if possible. The results of passing any other negative value
|
||||
** are undefined.
|
||||
**
|
||||
** Internally, each SQLite database handle store two timeout values - the
|
||||
** busy-timeout (used for rollback mode databases, or if the VFS does not
|
||||
** support blocking locks) and the setlk-timeout (used for blocking locks
|
||||
** on wal-mode databases). The sqlite3_busy_timeout() method sets both
|
||||
** values, this function sets only the setlk-timeout value. Therefore,
|
||||
** to configure separate busy-timeout and setlk-timeout values for a single
|
||||
** database handle, call sqlite3_busy_timeout() followed by this function.
|
||||
**
|
||||
** Whenever the number of connections to a wal mode database falls from
|
||||
** 1 to 0, the last connection takes an exclusive lock on the database,
|
||||
** then checkpoints and deletes the wal file. While it is doing this, any
|
||||
** new connection that tries to read from the database fails with an
|
||||
** SQLITE_BUSY error. Or, if the SQLITE_SETLK_BLOCK_ON_CONNECT flag is
|
||||
** passed to this API, the new connection blocks until the exclusive lock
|
||||
** has been released.
|
||||
*/
|
||||
SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags);
|
||||
|
||||
/*
|
||||
** CAPI3REF: Flags for sqlite3_setlk_timeout()
|
||||
*/
|
||||
#define SQLITE_SETLK_BLOCK_ON_CONNECT 0x01
|
||||
|
||||
/*
|
||||
** CAPI3REF: Convenience Routines For Running Queries
|
||||
** METHOD: sqlite3
|
||||
|
|
@ -4013,7 +4080,7 @@ SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*);
|
|||
**
|
||||
** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of
|
||||
** database filename D with corresponding journal file J and WAL file W and
|
||||
** with N URI parameters key/values pairs in the array P. The result from
|
||||
** an array P of N URI Key/Value pairs. The result from
|
||||
** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that
|
||||
** is safe to pass to routines like:
|
||||
** <ul>
|
||||
|
|
@ -4694,7 +4761,7 @@ typedef struct sqlite3_context sqlite3_context;
|
|||
** METHOD: sqlite3_stmt
|
||||
**
|
||||
** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants,
|
||||
** literals may be replaced by a [parameter] that matches one of following
|
||||
** literals may be replaced by a [parameter] that matches one of the following
|
||||
** templates:
|
||||
**
|
||||
** <ul>
|
||||
|
|
@ -4739,7 +4806,7 @@ typedef struct sqlite3_context sqlite3_context;
|
|||
**
|
||||
** [[byte-order determination rules]] ^The byte-order of
|
||||
** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF)
|
||||
** found in first character, which is removed, or in the absence of a BOM
|
||||
** found in the first character, which is removed, or in the absence of a BOM
|
||||
** the byte order is the native byte order of the host
|
||||
** machine for sqlite3_bind_text16() or the byte order specified in
|
||||
** the 6th parameter for sqlite3_bind_text64().)^
|
||||
|
|
@ -4759,7 +4826,7 @@ typedef struct sqlite3_context sqlite3_context;
|
|||
** or sqlite3_bind_text16() or sqlite3_bind_text64() then
|
||||
** that parameter must be the byte offset
|
||||
** where the NUL terminator would occur assuming the string were NUL
|
||||
** terminated. If any NUL characters occurs at byte offsets less than
|
||||
** terminated. If any NUL characters occur at byte offsets less than
|
||||
** the value of the fourth parameter then the resulting string value will
|
||||
** contain embedded NULs. The result of expressions involving strings
|
||||
** with embedded NULs is undefined.
|
||||
|
|
@ -4971,7 +5038,7 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);
|
|||
** METHOD: sqlite3_stmt
|
||||
**
|
||||
** ^These routines provide a means to determine the database, table, and
|
||||
** table column that is the origin of a particular result column in
|
||||
** table column that is the origin of a particular result column in a
|
||||
** [SELECT] statement.
|
||||
** ^The name of the database or table or column can be returned as
|
||||
** either a UTF-8 or UTF-16 string. ^The _database_ routines return
|
||||
|
|
@ -5109,7 +5176,7 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int);
|
|||
** other than [SQLITE_ROW] before any subsequent invocation of
|
||||
** sqlite3_step(). Failure to reset the prepared statement using
|
||||
** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from
|
||||
** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1],
|
||||
** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1]),
|
||||
** sqlite3_step() began
|
||||
** calling [sqlite3_reset()] automatically in this circumstance rather
|
||||
** than returning [SQLITE_MISUSE]. This is not considered a compatibility
|
||||
|
|
@ -5540,8 +5607,8 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
|
|||
**
|
||||
** For best security, the [SQLITE_DIRECTONLY] flag is recommended for
|
||||
** all application-defined SQL functions that do not need to be
|
||||
** used inside of triggers, view, CHECK constraints, or other elements of
|
||||
** the database schema. This flags is especially recommended for SQL
|
||||
** used inside of triggers, views, CHECK constraints, or other elements of
|
||||
** the database schema. This flag is especially recommended for SQL
|
||||
** functions that have side effects or reveal internal application state.
|
||||
** Without this flag, an attacker might be able to modify the schema of
|
||||
** a database file to include invocations of the function with parameters
|
||||
|
|
@ -5572,7 +5639,7 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt);
|
|||
** [user-defined window functions|available here].
|
||||
**
|
||||
** ^(If the final parameter to sqlite3_create_function_v2() or
|
||||
** sqlite3_create_window_function() is not NULL, then it is destructor for
|
||||
** sqlite3_create_window_function() is not NULL, then it is the destructor for
|
||||
** the application data pointer. The destructor is invoked when the function
|
||||
** is deleted, either by being overloaded or when the database connection
|
||||
** closes.)^ ^The destructor is also invoked if the call to
|
||||
|
|
@ -5972,7 +6039,7 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*);
|
|||
** METHOD: sqlite3_value
|
||||
**
|
||||
** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value]
|
||||
** object D and returns a pointer to that copy. ^The [sqlite3_value] returned
|
||||
** object V and returns a pointer to that copy. ^The [sqlite3_value] returned
|
||||
** is a [protected sqlite3_value] object even if the input is not.
|
||||
** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
|
||||
** memory allocation fails. ^If V is a [pointer value], then the result
|
||||
|
|
@ -6010,7 +6077,7 @@ SQLITE_API void sqlite3_value_free(sqlite3_value*);
|
|||
** allocation error occurs.
|
||||
**
|
||||
** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
|
||||
** determined by the N parameter on first successful call. Changing the
|
||||
** determined by the N parameter on the first successful call. Changing the
|
||||
** value of N in any subsequent call to sqlite3_aggregate_context() within
|
||||
** the same aggregate function instance will not resize the memory
|
||||
** allocation.)^ Within the xFinal callback, it is customary to set
|
||||
|
|
@ -6172,7 +6239,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi
|
|||
**
|
||||
** Security Warning: These interfaces should not be exposed in scripting
|
||||
** languages or in other circumstances where it might be possible for an
|
||||
** an attacker to invoke them. Any agent that can invoke these interfaces
|
||||
** attacker to invoke them. Any agent that can invoke these interfaces
|
||||
** can probably also take control of the process.
|
||||
**
|
||||
** Database connection client data is only available for SQLite
|
||||
|
|
@ -6286,7 +6353,7 @@ typedef void (*sqlite3_destructor_type)(void*);
|
|||
** pointed to by the 2nd parameter are taken as the application-defined
|
||||
** function result. If the 3rd parameter is non-negative, then it
|
||||
** must be the byte offset into the string where the NUL terminator would
|
||||
** appear if the string where NUL terminated. If any NUL characters occur
|
||||
** appear if the string were NUL terminated. If any NUL characters occur
|
||||
** in the string at a byte offset that is less than the value of the 3rd
|
||||
** parameter, then the resulting string will contain embedded NULs and the
|
||||
** result of expressions operating on strings with embedded NULs is undefined.
|
||||
|
|
@ -6344,7 +6411,7 @@ typedef void (*sqlite3_destructor_type)(void*);
|
|||
** string and preferably a string literal. The sqlite3_result_pointer()
|
||||
** routine is part of the [pointer passing interface] added for SQLite 3.20.0.
|
||||
**
|
||||
** If these routines are called from within the different thread
|
||||
** If these routines are called from within a different thread
|
||||
** than the one containing the application-defined function that received
|
||||
** the [sqlite3_context] pointer, the results are undefined.
|
||||
*/
|
||||
|
|
@ -6750,7 +6817,7 @@ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);
|
|||
** METHOD: sqlite3
|
||||
**
|
||||
** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name
|
||||
** for the N-th database on database connection D, or a NULL pointer of N is
|
||||
** for the N-th database on database connection D, or a NULL pointer if N is
|
||||
** out of range. An N value of 0 means the main database file. An N of 1 is
|
||||
** the "temp" schema. Larger values of N correspond to various ATTACH-ed
|
||||
** databases.
|
||||
|
|
@ -6845,7 +6912,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema);
|
|||
** <dd>The SQLITE_TXN_READ state means that the database is currently
|
||||
** in a read transaction. Content has been read from the database file
|
||||
** but nothing in the database file has changed. The transaction state
|
||||
** will advanced to SQLITE_TXN_WRITE if any changes occur and there are
|
||||
** will be advanced to SQLITE_TXN_WRITE if any changes occur and there are
|
||||
** no other conflicting concurrent write transactions. The transaction
|
||||
** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or
|
||||
** [COMMIT].</dd>
|
||||
|
|
@ -6854,7 +6921,7 @@ SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema);
|
|||
** <dd>The SQLITE_TXN_WRITE state means that the database is currently
|
||||
** in a write transaction. Content has been written to the database file
|
||||
** but has not yet committed. The transaction state will change to
|
||||
** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
|
||||
** SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
|
||||
*/
|
||||
#define SQLITE_TXN_NONE 0
|
||||
#define SQLITE_TXN_READ 1
|
||||
|
|
@ -7005,6 +7072,8 @@ SQLITE_API int sqlite3_autovacuum_pages(
|
|||
**
|
||||
** ^The second argument is a pointer to the function to invoke when a
|
||||
** row is updated, inserted or deleted in a rowid table.
|
||||
** ^The update hook is disabled by invoking sqlite3_update_hook()
|
||||
** with a NULL pointer as the second parameter.
|
||||
** ^The first argument to the callback is a copy of the third argument
|
||||
** to sqlite3_update_hook().
|
||||
** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE],
|
||||
|
|
@ -7133,7 +7202,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
|
|||
** CAPI3REF: Impose A Limit On Heap Size
|
||||
**
|
||||
** These interfaces impose limits on the amount of heap memory that will be
|
||||
** by all database connections within a single process.
|
||||
** used by all database connections within a single process.
|
||||
**
|
||||
** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the
|
||||
** soft limit on the amount of heap memory that may be allocated by SQLite.
|
||||
|
|
@ -7191,7 +7260,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*);
|
|||
** </ul>)^
|
||||
**
|
||||
** The circumstances under which SQLite will enforce the heap limits may
|
||||
** changes in future releases of SQLite.
|
||||
** change in future releases of SQLite.
|
||||
*/
|
||||
SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N);
|
||||
SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N);
|
||||
|
|
@ -7306,8 +7375,8 @@ SQLITE_API int sqlite3_table_column_metadata(
|
|||
** ^The entry point is zProc.
|
||||
** ^(zProc may be 0, in which case SQLite will try to come up with an
|
||||
** entry point name on its own. It first tries "sqlite3_extension_init".
|
||||
** If that does not work, it constructs a name "sqlite3_X_init" where the
|
||||
** X is consists of the lower-case equivalent of all ASCII alphabetic
|
||||
** If that does not work, it constructs a name "sqlite3_X_init" where
|
||||
** X consists of the lower-case equivalent of all ASCII alphabetic
|
||||
** characters in the filename from the last "/" to the first following
|
||||
** "." and omitting any initial "lib".)^
|
||||
** ^The sqlite3_load_extension() interface returns
|
||||
|
|
@ -7378,7 +7447,7 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff);
|
|||
** ^(Even though the function prototype shows that xEntryPoint() takes
|
||||
** no arguments and returns void, SQLite invokes xEntryPoint() with three
|
||||
** arguments and expects an integer result as if the signature of the
|
||||
** entry point where as follows:
|
||||
** entry point were as follows:
|
||||
**
|
||||
** <blockquote><pre>
|
||||
** int xEntryPoint(
|
||||
|
|
@ -7542,7 +7611,7 @@ struct sqlite3_module {
|
|||
** virtual table and might not be checked again by the byte code.)^ ^(The
|
||||
** aConstraintUsage[].omit flag is an optimization hint. When the omit flag
|
||||
** is left in its default setting of false, the constraint will always be
|
||||
** checked separately in byte code. If the omit flag is change to true, then
|
||||
** checked separately in byte code. If the omit flag is changed to true, then
|
||||
** the constraint may or may not be checked in byte code. In other words,
|
||||
** when the omit flag is true there is no guarantee that the constraint will
|
||||
** not be checked again using byte code.)^
|
||||
|
|
@ -7568,7 +7637,7 @@ struct sqlite3_module {
|
|||
** The xBestIndex method may optionally populate the idxFlags field with a
|
||||
** mask of SQLITE_INDEX_SCAN_* flags. One such flag is
|
||||
** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN]
|
||||
** output to show the idxNum has hex instead of as decimal. Another flag is
|
||||
** output to show the idxNum as hex instead of as decimal. Another flag is
|
||||
** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will
|
||||
** return at most one row.
|
||||
**
|
||||
|
|
@ -7709,7 +7778,7 @@ struct sqlite3_index_info {
|
|||
** the implementation of the [virtual table module]. ^The fourth
|
||||
** parameter is an arbitrary client data pointer that is passed through
|
||||
** into the [xCreate] and [xConnect] methods of the virtual table module
|
||||
** when a new virtual table is be being created or reinitialized.
|
||||
** when a new virtual table is being created or reinitialized.
|
||||
**
|
||||
** ^The sqlite3_create_module_v2() interface has a fifth parameter which
|
||||
** is a pointer to a destructor for the pClientData. ^SQLite will
|
||||
|
|
@ -7874,7 +7943,7 @@ typedef struct sqlite3_blob sqlite3_blob;
|
|||
** in *ppBlob. Otherwise an [error code] is returned and, unless the error
|
||||
** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
|
||||
** the API is not misused, it is always safe to call [sqlite3_blob_close()]
|
||||
** on *ppBlob after this function it returns.
|
||||
** on *ppBlob after this function returns.
|
||||
**
|
||||
** This function fails with SQLITE_ERROR if any of the following are true:
|
||||
** <ul>
|
||||
|
|
@ -7994,7 +8063,7 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *);
|
|||
**
|
||||
** ^Returns the size in bytes of the BLOB accessible via the
|
||||
** successfully opened [BLOB handle] in its only argument. ^The
|
||||
** incremental blob I/O routines can only read or overwriting existing
|
||||
** incremental blob I/O routines can only read or overwrite existing
|
||||
** blob content; they cannot change the size of a blob.
|
||||
**
|
||||
** This routine only works on a [BLOB handle] which has been created
|
||||
|
|
@ -8144,7 +8213,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*);
|
|||
** ^The sqlite3_mutex_alloc() routine allocates a new
|
||||
** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc()
|
||||
** routine returns NULL if it is unable to allocate the requested
|
||||
** mutex. The argument to sqlite3_mutex_alloc() must one of these
|
||||
** mutex. The argument to sqlite3_mutex_alloc() must be one of these
|
||||
** integer constants:
|
||||
**
|
||||
** <ul>
|
||||
|
|
@ -8377,7 +8446,7 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*);
|
|||
** CAPI3REF: Retrieve the mutex for a database connection
|
||||
** METHOD: sqlite3
|
||||
**
|
||||
** ^This interface returns a pointer the [sqlite3_mutex] object that
|
||||
** ^This interface returns a pointer to the [sqlite3_mutex] object that
|
||||
** serializes access to the [database connection] given in the argument
|
||||
** when the [threading mode] is Serialized.
|
||||
** ^If the [threading mode] is Single-thread or Multi-thread then this
|
||||
|
|
@ -8500,7 +8569,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
|
|||
** CAPI3REF: SQL Keyword Checking
|
||||
**
|
||||
** These routines provide access to the set of SQL language keywords
|
||||
** recognized by SQLite. Applications can uses these routines to determine
|
||||
** recognized by SQLite. Applications can use these routines to determine
|
||||
** whether or not a specific identifier needs to be escaped (for example,
|
||||
** by enclosing in double-quotes) so as not to confuse the parser.
|
||||
**
|
||||
|
|
@ -8668,7 +8737,7 @@ SQLITE_API void sqlite3_str_reset(sqlite3_str*);
|
|||
** content of the dynamic string under construction in X. The value
|
||||
** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X
|
||||
** and might be freed or altered by any subsequent method on the same
|
||||
** [sqlite3_str] object. Applications must not used the pointer returned
|
||||
** [sqlite3_str] object. Applications must not use the pointer returned by
|
||||
** [sqlite3_str_value(X)] after any subsequent method call on the same
|
||||
** object. ^Applications may change the content of the string returned
|
||||
** by [sqlite3_str_value(X)] as long as they do not write into any bytes
|
||||
|
|
@ -8754,7 +8823,7 @@ SQLITE_API int sqlite3_status64(
|
|||
** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE]
|
||||
** buffer and where forced to overflow to [sqlite3_malloc()]. The
|
||||
** returned value includes allocations that overflowed because they
|
||||
** where too large (they were larger than the "sz" parameter to
|
||||
** were too large (they were larger than the "sz" parameter to
|
||||
** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because
|
||||
** no space was left in the page cache.</dd>)^
|
||||
**
|
||||
|
|
@ -8838,28 +8907,29 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
|||
** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt>
|
||||
** <dd>This parameter returns the number of malloc attempts that were
|
||||
** satisfied using lookaside memory. Only the high-water value is meaningful;
|
||||
** the current value is always zero.)^
|
||||
** the current value is always zero.</dd>)^
|
||||
**
|
||||
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]]
|
||||
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt>
|
||||
** <dd>This parameter returns the number malloc attempts that might have
|
||||
** <dd>This parameter returns the number of malloc attempts that might have
|
||||
** been satisfied using lookaside memory but failed due to the amount of
|
||||
** memory requested being larger than the lookaside slot size.
|
||||
** Only the high-water value is meaningful;
|
||||
** the current value is always zero.)^
|
||||
** the current value is always zero.</dd>)^
|
||||
**
|
||||
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]]
|
||||
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL</dt>
|
||||
** <dd>This parameter returns the number malloc attempts that might have
|
||||
** <dd>This parameter returns the number of malloc attempts that might have
|
||||
** been satisfied using lookaside memory but failed due to all lookaside
|
||||
** memory already being in use.
|
||||
** Only the high-water value is meaningful;
|
||||
** the current value is always zero.)^
|
||||
** the current value is always zero.</dd>)^
|
||||
**
|
||||
** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt>
|
||||
** <dd>This parameter returns the approximate number of bytes of heap
|
||||
** memory used by all pager caches associated with the database connection.)^
|
||||
** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]]
|
||||
** ^(<dt>SQLITE_DBSTATUS_CACHE_USED_SHARED</dt>
|
||||
|
|
@ -8868,10 +8938,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
|||
** memory used by that pager cache is divided evenly between the attached
|
||||
** connections.)^ In other words, if none of the pager caches associated
|
||||
** with the database connection are shared, this request returns the same
|
||||
** value as DBSTATUS_CACHE_USED. Or, if one or more or the pager caches are
|
||||
** value as DBSTATUS_CACHE_USED. Or, if one or more of the pager caches are
|
||||
** shared, the value returned by this call will be smaller than that returned
|
||||
** by DBSTATUS_CACHE_USED. ^The highwater mark associated with
|
||||
** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.
|
||||
** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.</dd>
|
||||
**
|
||||
** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt>
|
||||
** <dd>This parameter returns the approximate number of bytes of heap
|
||||
|
|
@ -8881,6 +8951,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
|||
** schema memory is shared with other database connections due to
|
||||
** [shared cache mode] being enabled.
|
||||
** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0.
|
||||
** </dd>
|
||||
**
|
||||
** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt>
|
||||
** <dd>This parameter returns the approximate number of bytes of heap
|
||||
|
|
@ -8917,7 +8988,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r
|
|||
** been written to disk in the middle of a transaction due to the page
|
||||
** cache overflowing. Transactions are more efficient if they are written
|
||||
** to disk all at once. When pages spill mid-transaction, that introduces
|
||||
** additional overhead. This parameter can be used help identify
|
||||
** additional overhead. This parameter can be used to help identify
|
||||
** inefficiencies that can be resolved by increasing the cache size.
|
||||
** </dd>
|
||||
**
|
||||
|
|
@ -8988,13 +9059,13 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
|||
** [[SQLITE_STMTSTATUS_SORT]] <dt>SQLITE_STMTSTATUS_SORT</dt>
|
||||
** <dd>^This is the number of sort operations that have occurred.
|
||||
** A non-zero value in this counter may indicate an opportunity to
|
||||
** improvement performance through careful use of indices.</dd>
|
||||
** improve performance through careful use of indices.</dd>
|
||||
**
|
||||
** [[SQLITE_STMTSTATUS_AUTOINDEX]] <dt>SQLITE_STMTSTATUS_AUTOINDEX</dt>
|
||||
** <dd>^This is the number of rows inserted into transient indices that
|
||||
** were created automatically in order to help joins run faster.
|
||||
** A non-zero value in this counter may indicate an opportunity to
|
||||
** improvement performance by adding permanent indices that do not
|
||||
** improve performance by adding permanent indices that do not
|
||||
** need to be reinitialized each time the statement is run.</dd>
|
||||
**
|
||||
** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt>
|
||||
|
|
@ -9003,19 +9074,19 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
|||
** to 2147483647. The number of virtual machine operations can be
|
||||
** used as a proxy for the total work done by the prepared statement.
|
||||
** If the number of virtual machine operations exceeds 2147483647
|
||||
** then the value returned by this statement status code is undefined.
|
||||
** then the value returned by this statement status code is undefined.</dd>
|
||||
**
|
||||
** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt>
|
||||
** <dd>^This is the number of times that the prepare statement has been
|
||||
** automatically regenerated due to schema changes or changes to
|
||||
** [bound parameters] that might affect the query plan.
|
||||
** [bound parameters] that might affect the query plan.</dd>
|
||||
**
|
||||
** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt>
|
||||
** <dd>^This is the number of times that the prepared statement has
|
||||
** been run. A single "run" for the purposes of this counter is one
|
||||
** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()].
|
||||
** The counter is incremented on the first [sqlite3_step()] call of each
|
||||
** cycle.
|
||||
** cycle.</dd>
|
||||
**
|
||||
** [[SQLITE_STMTSTATUS_FILTER_MISS]]
|
||||
** [[SQLITE_STMTSTATUS_FILTER HIT]]
|
||||
|
|
@ -9025,7 +9096,7 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg);
|
|||
** step was bypassed because a Bloom filter returned not-found. The
|
||||
** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of
|
||||
** times that the Bloom filter returned a find, and thus the join step
|
||||
** had to be processed as normal.
|
||||
** had to be processed as normal.</dd>
|
||||
**
|
||||
** [[SQLITE_STMTSTATUS_MEMUSED]] <dt>SQLITE_STMTSTATUS_MEMUSED</dt>
|
||||
** <dd>^This is the approximate number of bytes of heap memory
|
||||
|
|
@ -9130,9 +9201,9 @@ struct sqlite3_pcache_page {
|
|||
** SQLite will typically create one cache instance for each open database file,
|
||||
** though this is not guaranteed. ^The
|
||||
** first parameter, szPage, is the size in bytes of the pages that must
|
||||
** be allocated by the cache. ^szPage will always a power of two. ^The
|
||||
** be allocated by the cache. ^szPage will always be a power of two. ^The
|
||||
** second parameter szExtra is a number of bytes of extra storage
|
||||
** associated with each page cache entry. ^The szExtra parameter will
|
||||
** associated with each page cache entry. ^The szExtra parameter will be
|
||||
** a number less than 250. SQLite will use the
|
||||
** extra szExtra bytes on each page to store metadata about the underlying
|
||||
** database page on disk. The value passed into szExtra depends
|
||||
|
|
@ -9140,17 +9211,17 @@ struct sqlite3_pcache_page {
|
|||
** ^The third argument to xCreate(), bPurgeable, is true if the cache being
|
||||
** created will be used to cache database pages of a file stored on disk, or
|
||||
** false if it is used for an in-memory database. The cache implementation
|
||||
** does not have to do anything special based with the value of bPurgeable;
|
||||
** does not have to do anything special based upon the value of bPurgeable;
|
||||
** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will
|
||||
** never invoke xUnpin() except to deliberately delete a page.
|
||||
** ^In other words, calls to xUnpin() on a cache with bPurgeable set to
|
||||
** false will always have the "discard" flag set to true.
|
||||
** ^Hence, a cache created with bPurgeable false will
|
||||
** ^Hence, a cache created with bPurgeable set to false will
|
||||
** never contain any unpinned pages.
|
||||
**
|
||||
** [[the xCachesize() page cache method]]
|
||||
** ^(The xCachesize() method may be called at any time by SQLite to set the
|
||||
** suggested maximum cache-size (number of pages stored by) the cache
|
||||
** suggested maximum cache-size (number of pages stored) for the cache
|
||||
** instance passed as the first argument. This is the value configured using
|
||||
** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable
|
||||
** parameter, the implementation is not required to do anything with this
|
||||
|
|
@ -9177,12 +9248,12 @@ struct sqlite3_pcache_page {
|
|||
** implementation must return a pointer to the page buffer with its content
|
||||
** intact. If the requested page is not already in the cache, then the
|
||||
** cache implementation should use the value of the createFlag
|
||||
** parameter to help it determined what action to take:
|
||||
** parameter to help it determine what action to take:
|
||||
**
|
||||
** <table border=1 width=85% align=center>
|
||||
** <tr><th> createFlag <th> Behavior when page is not already in cache
|
||||
** <tr><td> 0 <td> Do not allocate a new page. Return NULL.
|
||||
** <tr><td> 1 <td> Allocate a new page if it easy and convenient to do so.
|
||||
** <tr><td> 1 <td> Allocate a new page if it is easy and convenient to do so.
|
||||
** Otherwise return NULL.
|
||||
** <tr><td> 2 <td> Make every effort to allocate a new page. Only return
|
||||
** NULL if allocating a new page is effectively impossible.
|
||||
|
|
@ -9199,7 +9270,7 @@ struct sqlite3_pcache_page {
|
|||
** as its second argument. If the third parameter, discard, is non-zero,
|
||||
** then the page must be evicted from the cache.
|
||||
** ^If the discard parameter is
|
||||
** zero, then the page may be discarded or retained at the discretion of
|
||||
** zero, then the page may be discarded or retained at the discretion of the
|
||||
** page cache implementation. ^The page cache implementation
|
||||
** may choose to evict unpinned pages at any time.
|
||||
**
|
||||
|
|
@ -9217,7 +9288,7 @@ struct sqlite3_pcache_page {
|
|||
** When SQLite calls the xTruncate() method, the cache must discard all
|
||||
** existing cache entries with page numbers (keys) greater than or equal
|
||||
** to the value of the iLimit parameter passed to xTruncate(). If any
|
||||
** of these pages are pinned, they are implicitly unpinned, meaning that
|
||||
** of these pages are pinned, they become implicitly unpinned, meaning that
|
||||
** they can be safely discarded.
|
||||
**
|
||||
** [[the xDestroy() page cache method]]
|
||||
|
|
@ -9397,7 +9468,7 @@ typedef struct sqlite3_backup sqlite3_backup;
|
|||
** external process or via a database connection other than the one being
|
||||
** used by the backup operation, then the backup will be automatically
|
||||
** restarted by the next call to sqlite3_backup_step(). ^If the source
|
||||
** database is modified by the using the same database connection as is used
|
||||
** database is modified by using the same database connection as is used
|
||||
** by the backup operation, then the backup database is automatically
|
||||
** updated at the same time.
|
||||
**
|
||||
|
|
@ -9414,7 +9485,7 @@ typedef struct sqlite3_backup sqlite3_backup;
|
|||
** and may not be used following a call to sqlite3_backup_finish().
|
||||
**
|
||||
** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no
|
||||
** sqlite3_backup_step() errors occurred, regardless or whether or not
|
||||
** sqlite3_backup_step() errors occurred, regardless of whether or not
|
||||
** sqlite3_backup_step() completed.
|
||||
** ^If an out-of-memory condition or IO error occurred during any prior
|
||||
** sqlite3_backup_step() call on the same [sqlite3_backup] object, then
|
||||
|
|
@ -9516,7 +9587,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
|
|||
** application receives an SQLITE_LOCKED error, it may call the
|
||||
** sqlite3_unlock_notify() method with the blocked connection handle as
|
||||
** the first argument to register for a callback that will be invoked
|
||||
** when the blocking connections current transaction is concluded. ^The
|
||||
** when the blocking connection's current transaction is concluded. ^The
|
||||
** callback is invoked from within the [sqlite3_step] or [sqlite3_close]
|
||||
** call that concludes the blocking connection's transaction.
|
||||
**
|
||||
|
|
@ -9536,7 +9607,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p);
|
|||
** blocked connection already has a registered unlock-notify callback,
|
||||
** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
|
||||
** called with a NULL pointer as its second argument, then any existing
|
||||
** unlock-notify callback is canceled. ^The blocked connections
|
||||
** unlock-notify callback is canceled. ^The blocked connection's
|
||||
** unlock-notify callback may also be canceled by closing the blocked
|
||||
** connection using [sqlite3_close()].
|
||||
**
|
||||
|
|
@ -9934,7 +10005,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
|||
** support constraints. In this configuration (which is the default) if
|
||||
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire
|
||||
** statement is rolled back as if [ON CONFLICT | OR ABORT] had been
|
||||
** specified as part of the users SQL statement, regardless of the actual
|
||||
** specified as part of the user's SQL statement, regardless of the actual
|
||||
** ON CONFLICT mode specified.
|
||||
**
|
||||
** If X is non-zero, then the virtual table implementation guarantees
|
||||
|
|
@ -9968,7 +10039,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...);
|
|||
** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
|
||||
** <dd>Calls of the form
|
||||
** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the
|
||||
** the [xConnect] or [xCreate] methods of a [virtual table] implementation
|
||||
** [xConnect] or [xCreate] methods of a [virtual table] implementation
|
||||
** identify that virtual table as being safe to use from within triggers
|
||||
** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the
|
||||
** virtual table can do no serious harm even if it is controlled by a
|
||||
|
|
@ -10136,7 +10207,7 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
|
|||
** </table>
|
||||
**
|
||||
** ^For the purposes of comparing virtual table output values to see if the
|
||||
** values are same value for sorting purposes, two NULL values are considered
|
||||
** values are the same value for sorting purposes, two NULL values are considered
|
||||
** to be the same. In other words, the comparison operator is "IS"
|
||||
** (or "IS NOT DISTINCT FROM") and not "==".
|
||||
**
|
||||
|
|
@ -10146,7 +10217,7 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
|
|||
**
|
||||
** ^A virtual table implementation is always free to return rows in any order
|
||||
** it wants, as long as the "orderByConsumed" flag is not set. ^When the
|
||||
** the "orderByConsumed" flag is unset, the query planner will add extra
|
||||
** "orderByConsumed" flag is unset, the query planner will add extra
|
||||
** [bytecode] to ensure that the final results returned by the SQL query are
|
||||
** ordered correctly. The use of the "orderByConsumed" flag and the
|
||||
** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful
|
||||
|
|
@ -10243,7 +10314,7 @@ SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle);
|
|||
** sqlite3_vtab_in_next(X,P) should be one of the parameters to the
|
||||
** xFilter method which invokes these routines, and specifically
|
||||
** a parameter that was previously selected for all-at-once IN constraint
|
||||
** processing use the [sqlite3_vtab_in()] interface in the
|
||||
** processing using the [sqlite3_vtab_in()] interface in the
|
||||
** [xBestIndex|xBestIndex method]. ^(If the X parameter is not
|
||||
** an xFilter argument that was selected for all-at-once IN constraint
|
||||
** processing, then these routines return [SQLITE_ERROR].)^
|
||||
|
|
@ -10298,7 +10369,7 @@ SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut);
|
|||
** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V)
|
||||
** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th
|
||||
** constraint is not available. ^The sqlite3_vtab_rhs_value() interface
|
||||
** can return an result code other than SQLITE_OK or SQLITE_NOTFOUND if
|
||||
** can return a result code other than SQLITE_OK or SQLITE_NOTFOUND if
|
||||
** something goes wrong.
|
||||
**
|
||||
** The sqlite3_vtab_rhs_value() interface is usually only successful if
|
||||
|
|
@ -10326,8 +10397,8 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
|||
** KEYWORDS: {conflict resolution mode}
|
||||
**
|
||||
** These constants are returned by [sqlite3_vtab_on_conflict()] to
|
||||
** inform a [virtual table] implementation what the [ON CONFLICT] mode
|
||||
** is for the SQL statement being evaluated.
|
||||
** inform a [virtual table] implementation of the [ON CONFLICT] mode
|
||||
** for the SQL statement being evaluated.
|
||||
**
|
||||
** Note that the [SQLITE_IGNORE] constant is also used as a potential
|
||||
** return value from the [sqlite3_set_authorizer()] callback and that
|
||||
|
|
@ -10367,39 +10438,39 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
|||
** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt>
|
||||
** <dd>^The "double" variable pointed to by the V parameter will be set to the
|
||||
** query planner's estimate for the average number of rows output from each
|
||||
** iteration of the X-th loop. If the query planner's estimates was accurate,
|
||||
** iteration of the X-th loop. If the query planner's estimate was accurate,
|
||||
** then this value will approximate the quotient NVISIT/NLOOP and the
|
||||
** product of this value for all prior loops with the same SELECTID will
|
||||
** be the NLOOP value for the current loop.
|
||||
** be the NLOOP value for the current loop.</dd>
|
||||
**
|
||||
** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt>
|
||||
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
||||
** to a zero-terminated UTF-8 string containing the name of the index or table
|
||||
** used for the X-th loop.
|
||||
** used for the X-th loop.</dd>
|
||||
**
|
||||
** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt>
|
||||
** <dd>^The "const char *" variable pointed to by the V parameter will be set
|
||||
** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN]
|
||||
** description for the X-th loop.
|
||||
** description for the X-th loop.</dd>
|
||||
**
|
||||
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt>
|
||||
** <dd>^The "int" variable pointed to by the V parameter will be set to the
|
||||
** id for the X-th query plan element. The id value is unique within the
|
||||
** statement. The select-id is the same value as is output in the first
|
||||
** column of an [EXPLAIN QUERY PLAN] query.
|
||||
** column of an [EXPLAIN QUERY PLAN] query.</dd>
|
||||
**
|
||||
** [[SQLITE_SCANSTAT_PARENTID]] <dt>SQLITE_SCANSTAT_PARENTID</dt>
|
||||
** <dd>The "int" variable pointed to by the V parameter will be set to the
|
||||
** the id of the parent of the current query element, if applicable, or
|
||||
** id of the parent of the current query element, if applicable, or
|
||||
** to zero if the query element has no parent. This is the same value as
|
||||
** returned in the second column of an [EXPLAIN QUERY PLAN] query.
|
||||
** returned in the second column of an [EXPLAIN QUERY PLAN] query.</dd>
|
||||
**
|
||||
** [[SQLITE_SCANSTAT_NCYCLE]] <dt>SQLITE_SCANSTAT_NCYCLE</dt>
|
||||
** <dd>The sqlite3_int64 output value is set to the number of cycles,
|
||||
** according to the processor time-stamp counter, that elapsed while the
|
||||
** query element was being processed. This value is not available for
|
||||
** all query elements - if it is unavailable the output variable is
|
||||
** set to -1.
|
||||
** set to -1.</dd>
|
||||
** </dl>
|
||||
*/
|
||||
#define SQLITE_SCANSTAT_NLOOP 0
|
||||
|
|
@ -10440,8 +10511,8 @@ SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **
|
|||
** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter.
|
||||
**
|
||||
** Parameter "idx" identifies the specific query element to retrieve statistics
|
||||
** for. Query elements are numbered starting from zero. A value of -1 may be
|
||||
** to query for statistics regarding the entire query. ^If idx is out of range
|
||||
** for. Query elements are numbered starting from zero. A value of -1 may
|
||||
** retrieve statistics for the entire query. ^If idx is out of range
|
||||
** - less than -1 or greater than or equal to the total number of query
|
||||
** elements used to implement the statement - a non-zero value is returned and
|
||||
** the variable that pOut points to is unchanged.
|
||||
|
|
@ -10484,7 +10555,7 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);
|
|||
** METHOD: sqlite3
|
||||
**
|
||||
** ^If a write-transaction is open on [database connection] D when the
|
||||
** [sqlite3_db_cacheflush(D)] interface invoked, any dirty
|
||||
** [sqlite3_db_cacheflush(D)] interface is invoked, any dirty
|
||||
** pages in the pager-cache that are not currently in use are written out
|
||||
** to disk. A dirty page may be in use if a database cursor created by an
|
||||
** active SQL statement is reading from it, or if it is page 1 of a database
|
||||
|
|
@ -10598,8 +10669,8 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*);
|
|||
** triggers; and so forth.
|
||||
**
|
||||
** When the [sqlite3_blob_write()] API is used to update a blob column,
|
||||
** the pre-update hook is invoked with SQLITE_DELETE. This is because the
|
||||
** in this case the new values are not available. In this case, when a
|
||||
** the pre-update hook is invoked with SQLITE_DELETE, because
|
||||
** the new values are not yet available. In this case, when a
|
||||
** callback made with op==SQLITE_DELETE is actually a write using the
|
||||
** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
|
||||
** the index of the column being written. In other cases, where the
|
||||
|
|
@ -10852,7 +10923,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
|
|||
** For an ordinary on-disk database file, the serialization is just a
|
||||
** copy of the disk file. For an in-memory database or a "TEMP" database,
|
||||
** the serialization is the same sequence of bytes which would be written
|
||||
** to disk if that database where backed up to disk.
|
||||
** to disk if that database were backed up to disk.
|
||||
**
|
||||
** The usual case is that sqlite3_serialize() copies the serialization of
|
||||
** the database into memory obtained from [sqlite3_malloc64()] and returns
|
||||
|
|
@ -10861,7 +10932,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c
|
|||
** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations
|
||||
** are made, and the sqlite3_serialize() function will return a pointer
|
||||
** to the contiguous memory representation of the database that SQLite
|
||||
** is currently using for that database, or NULL if the no such contiguous
|
||||
** is currently using for that database, or NULL if no such contiguous
|
||||
** memory representation of the database exists. A contiguous memory
|
||||
** representation of the database will usually only exist if there has
|
||||
** been a prior call to [sqlite3_deserialize(D,S,...)] with the same
|
||||
|
|
@ -10932,7 +11003,7 @@ SQLITE_API unsigned char *sqlite3_serialize(
|
|||
** database is currently in a read transaction or is involved in a backup
|
||||
** operation.
|
||||
**
|
||||
** It is not possible to deserialized into the TEMP database. If the
|
||||
** It is not possible to deserialize into the TEMP database. If the
|
||||
** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the
|
||||
** function returns SQLITE_ERROR.
|
||||
**
|
||||
|
|
@ -10954,7 +11025,7 @@ SQLITE_API int sqlite3_deserialize(
|
|||
sqlite3 *db, /* The database connection */
|
||||
const char *zSchema, /* Which DB to reopen with the deserialization */
|
||||
unsigned char *pData, /* The serialized database content */
|
||||
sqlite3_int64 szDb, /* Number bytes in the deserialization */
|
||||
sqlite3_int64 szDb, /* Number of bytes in the deserialization */
|
||||
sqlite3_int64 szBuf, /* Total size of buffer pData[] */
|
||||
unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */
|
||||
);
|
||||
|
|
@ -10962,7 +11033,7 @@ SQLITE_API int sqlite3_deserialize(
|
|||
/*
|
||||
** CAPI3REF: Flags for sqlite3_deserialize()
|
||||
**
|
||||
** The following are allowed values for 6th argument (the F argument) to
|
||||
** The following are allowed values for the 6th argument (the F argument) to
|
||||
** the [sqlite3_deserialize(D,S,P,N,M,F)] interface.
|
||||
**
|
||||
** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization
|
||||
|
|
@ -11487,9 +11558,10 @@ SQLITE_API void sqlite3session_table_filter(
|
|||
** is inserted while a session object is enabled, then later deleted while
|
||||
** the same session object is disabled, no INSERT record will appear in the
|
||||
** changeset, even though the delete took place while the session was disabled.
|
||||
** Or, if one field of a row is updated while a session is disabled, and
|
||||
** another field of the same row is updated while the session is enabled, the
|
||||
** resulting changeset will contain an UPDATE change that updates both fields.
|
||||
** Or, if one field of a row is updated while a session is enabled, and
|
||||
** then another field of the same row is updated while the session is disabled,
|
||||
** the resulting changeset will contain an UPDATE change that updates both
|
||||
** fields.
|
||||
*/
|
||||
SQLITE_API int sqlite3session_changeset(
|
||||
sqlite3_session *pSession, /* Session object */
|
||||
|
|
@ -11561,8 +11633,9 @@ SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession
|
|||
** database zFrom the contents of the two compatible tables would be
|
||||
** identical.
|
||||
**
|
||||
** It an error if database zFrom does not exist or does not contain the
|
||||
** required compatible table.
|
||||
** Unless the call to this function is a no-op as described above, it is an
|
||||
** error if database zFrom does not exist or does not contain the required
|
||||
** compatible table.
|
||||
**
|
||||
** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
|
||||
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
|
||||
|
|
@ -11697,7 +11770,7 @@ SQLITE_API int sqlite3changeset_start_v2(
|
|||
** The following flags may passed via the 4th parameter to
|
||||
** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]:
|
||||
**
|
||||
** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
|
||||
** <dt>SQLITE_CHANGESETSTART_INVERT <dd>
|
||||
** Invert the changeset while iterating through it. This is equivalent to
|
||||
** inverting a changeset using sqlite3changeset_invert() before applying it.
|
||||
** It is an error to specify this flag with a patchset.
|
||||
|
|
@ -12012,19 +12085,6 @@ SQLITE_API int sqlite3changeset_concat(
|
|||
void **ppOut /* OUT: Buffer containing output changeset */
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
** CAPI3REF: Upgrade the Schema of a Changeset/Patchset
|
||||
*/
|
||||
SQLITE_API int sqlite3changeset_upgrade(
|
||||
sqlite3 *db,
|
||||
const char *zDb,
|
||||
int nIn, const void *pIn, /* Input changeset */
|
||||
int *pnOut, void **ppOut /* OUT: Inverse of input */
|
||||
);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** CAPI3REF: Changegroup Handle
|
||||
**
|
||||
|
|
|
|||
153
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_userauth.go
generated
vendored
153
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_userauth.go
generated
vendored
|
|
@ -16,53 +16,10 @@ package sqlite3
|
|||
#else
|
||||
#include <sqlite3.h>
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
|
||||
static int
|
||||
_sqlite3_user_authenticate(sqlite3* db, const char* zUsername, const char* aPW, int nPW)
|
||||
{
|
||||
return sqlite3_user_authenticate(db, zUsername, aPW, nPW);
|
||||
}
|
||||
|
||||
static int
|
||||
_sqlite3_user_add(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin)
|
||||
{
|
||||
return sqlite3_user_add(db, zUsername, aPW, nPW, isAdmin);
|
||||
}
|
||||
|
||||
static int
|
||||
_sqlite3_user_change(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin)
|
||||
{
|
||||
return sqlite3_user_change(db, zUsername, aPW, nPW, isAdmin);
|
||||
}
|
||||
|
||||
static int
|
||||
_sqlite3_user_delete(sqlite3* db, const char* zUsername)
|
||||
{
|
||||
return sqlite3_user_delete(db, zUsername);
|
||||
}
|
||||
|
||||
static int
|
||||
_sqlite3_auth_enabled(sqlite3* db)
|
||||
{
|
||||
int exists = -1;
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
sqlite3_prepare_v2(db, "select count(type) from sqlite_master WHERE type='table' and name='sqlite_user';", -1, &stmt, NULL);
|
||||
|
||||
while ( sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
exists = sqlite3_column_int(stmt, 0);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
return exists;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -70,8 +27,9 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
ErrUnauthorized = errors.New("SQLITE_AUTH: Unauthorized")
|
||||
ErrAdminRequired = errors.New("SQLITE_AUTH: Unauthorized; Admin Privileges Required")
|
||||
ErrUnauthorized = errors.New("SQLITE_AUTH: Unauthorized")
|
||||
ErrAdminRequired = errors.New("SQLITE_AUTH: Unauthorized; Admin Privileges Required")
|
||||
errUserAuthNoLongerSupported = errors.New("sqlite3: the sqlite_userauth tag is no longer supported as the userauth extension is no longer supported by the SQLite authors, see https://github.com/mattn/go-sqlite3/issues/1341")
|
||||
)
|
||||
|
||||
// Authenticate will perform an authentication of the provided username
|
||||
|
|
@ -88,15 +46,7 @@ var (
|
|||
// If the SQLITE_USER table is not present in the database file, then
|
||||
// this interface is a harmless no-op returning SQLITE_OK.
|
||||
func (c *SQLiteConn) Authenticate(username, password string) error {
|
||||
rv := c.authenticate(username, password)
|
||||
switch rv {
|
||||
case C.SQLITE_ERROR, C.SQLITE_AUTH:
|
||||
return ErrUnauthorized
|
||||
case C.SQLITE_OK:
|
||||
return nil
|
||||
default:
|
||||
return c.lastError()
|
||||
}
|
||||
return errUserAuthNoLongerSupported
|
||||
}
|
||||
|
||||
// authenticate provides the actual authentication to SQLite.
|
||||
|
|
@ -109,17 +59,7 @@ func (c *SQLiteConn) Authenticate(username, password string) error {
|
|||
// C.SQLITE_ERROR (1)
|
||||
// C.SQLITE_AUTH (23)
|
||||
func (c *SQLiteConn) authenticate(username, password string) int {
|
||||
// Allocate C Variables
|
||||
cuser := C.CString(username)
|
||||
cpass := C.CString(password)
|
||||
|
||||
// Free C Variables
|
||||
defer func() {
|
||||
C.free(unsafe.Pointer(cuser))
|
||||
C.free(unsafe.Pointer(cpass))
|
||||
}()
|
||||
|
||||
return int(C._sqlite3_user_authenticate(c.db, cuser, cpass, C.int(len(password))))
|
||||
return 1
|
||||
}
|
||||
|
||||
// AuthUserAdd can be used (by an admin user only)
|
||||
|
|
@ -131,20 +71,7 @@ func (c *SQLiteConn) authenticate(username, password string) int {
|
|||
// for any ATTACH-ed databases. Any call to AuthUserAdd by a
|
||||
// non-admin user results in an error.
|
||||
func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error {
|
||||
isAdmin := 0
|
||||
if admin {
|
||||
isAdmin = 1
|
||||
}
|
||||
|
||||
rv := c.authUserAdd(username, password, isAdmin)
|
||||
switch rv {
|
||||
case C.SQLITE_ERROR, C.SQLITE_AUTH:
|
||||
return ErrAdminRequired
|
||||
case C.SQLITE_OK:
|
||||
return nil
|
||||
default:
|
||||
return c.lastError()
|
||||
}
|
||||
return errUserAuthNoLongerSupported
|
||||
}
|
||||
|
||||
// authUserAdd enables the User Authentication if not enabled.
|
||||
|
|
@ -162,17 +89,7 @@ func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error {
|
|||
// C.SQLITE_ERROR (1)
|
||||
// C.SQLITE_AUTH (23)
|
||||
func (c *SQLiteConn) authUserAdd(username, password string, admin int) int {
|
||||
// Allocate C Variables
|
||||
cuser := C.CString(username)
|
||||
cpass := C.CString(password)
|
||||
|
||||
// Free C Variables
|
||||
defer func() {
|
||||
C.free(unsafe.Pointer(cuser))
|
||||
C.free(unsafe.Pointer(cpass))
|
||||
}()
|
||||
|
||||
return int(C._sqlite3_user_add(c.db, cuser, cpass, C.int(len(password)), C.int(admin)))
|
||||
return 1
|
||||
}
|
||||
|
||||
// AuthUserChange can be used to change a users
|
||||
|
|
@ -181,20 +98,7 @@ func (c *SQLiteConn) authUserAdd(username, password string, admin int) int {
|
|||
// credentials or admin privilege setting. No user may change their own
|
||||
// admin privilege setting.
|
||||
func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error {
|
||||
isAdmin := 0
|
||||
if admin {
|
||||
isAdmin = 1
|
||||
}
|
||||
|
||||
rv := c.authUserChange(username, password, isAdmin)
|
||||
switch rv {
|
||||
case C.SQLITE_ERROR, C.SQLITE_AUTH:
|
||||
return ErrAdminRequired
|
||||
case C.SQLITE_OK:
|
||||
return nil
|
||||
default:
|
||||
return c.lastError()
|
||||
}
|
||||
return errUserAuthNoLongerSupported
|
||||
}
|
||||
|
||||
// authUserChange allows to modify a user.
|
||||
|
|
@ -215,17 +119,7 @@ func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error
|
|||
// C.SQLITE_ERROR (1)
|
||||
// C.SQLITE_AUTH (23)
|
||||
func (c *SQLiteConn) authUserChange(username, password string, admin int) int {
|
||||
// Allocate C Variables
|
||||
cuser := C.CString(username)
|
||||
cpass := C.CString(password)
|
||||
|
||||
// Free C Variables
|
||||
defer func() {
|
||||
C.free(unsafe.Pointer(cuser))
|
||||
C.free(unsafe.Pointer(cpass))
|
||||
}()
|
||||
|
||||
return int(C._sqlite3_user_change(c.db, cuser, cpass, C.int(len(password)), C.int(admin)))
|
||||
return 1
|
||||
}
|
||||
|
||||
// AuthUserDelete can be used (by an admin user only)
|
||||
|
|
@ -234,15 +128,7 @@ func (c *SQLiteConn) authUserChange(username, password string, admin int) int {
|
|||
// the database cannot be converted into a no-authentication-required
|
||||
// database.
|
||||
func (c *SQLiteConn) AuthUserDelete(username string) error {
|
||||
rv := c.authUserDelete(username)
|
||||
switch rv {
|
||||
case C.SQLITE_ERROR, C.SQLITE_AUTH:
|
||||
return ErrAdminRequired
|
||||
case C.SQLITE_OK:
|
||||
return nil
|
||||
default:
|
||||
return c.lastError()
|
||||
}
|
||||
return errUserAuthNoLongerSupported
|
||||
}
|
||||
|
||||
// authUserDelete can be used to delete a user.
|
||||
|
|
@ -258,25 +144,12 @@ func (c *SQLiteConn) AuthUserDelete(username string) error {
|
|||
// C.SQLITE_ERROR (1)
|
||||
// C.SQLITE_AUTH (23)
|
||||
func (c *SQLiteConn) authUserDelete(username string) int {
|
||||
// Allocate C Variables
|
||||
cuser := C.CString(username)
|
||||
|
||||
// Free C Variables
|
||||
defer func() {
|
||||
C.free(unsafe.Pointer(cuser))
|
||||
}()
|
||||
|
||||
return int(C._sqlite3_user_delete(c.db, cuser))
|
||||
return 1
|
||||
}
|
||||
|
||||
// AuthEnabled checks if the database is protected by user authentication
|
||||
func (c *SQLiteConn) AuthEnabled() (exists bool) {
|
||||
rv := c.authEnabled()
|
||||
if rv == 1 {
|
||||
exists = true
|
||||
}
|
||||
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
// authEnabled perform the actual check for user authentication.
|
||||
|
|
@ -289,7 +162,7 @@ func (c *SQLiteConn) AuthEnabled() (exists bool) {
|
|||
// 0 - Disabled
|
||||
// 1 - Enabled
|
||||
func (c *SQLiteConn) authEnabled() int {
|
||||
return int(C._sqlite3_auth_enabled(c.db))
|
||||
return 0
|
||||
}
|
||||
|
||||
// EOF
|
||||
|
|
|
|||
4
vendor/github.com/mattn/go-sqlite3/sqlite3ext.h
generated
vendored
4
vendor/github.com/mattn/go-sqlite3/sqlite3ext.h
generated
vendored
|
|
@ -371,6 +371,8 @@ struct sqlite3_api_routines {
|
|||
/* Version 3.44.0 and later */
|
||||
void *(*get_clientdata)(sqlite3*,const char*);
|
||||
int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*));
|
||||
/* Version 3.50.0 and later */
|
||||
int (*setlk_timeout)(sqlite3*,int,int);
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -704,6 +706,8 @@ typedef int (*sqlite3_loadext_entry)(
|
|||
/* Version 3.44.0 and later */
|
||||
#define sqlite3_get_clientdata sqlite3_api->get_clientdata
|
||||
#define sqlite3_set_clientdata sqlite3_api->set_clientdata
|
||||
/* Version 3.50.0 and later */
|
||||
#define sqlite3_setlk_timeout sqlite3_api->setlk_timeout
|
||||
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
||||
|
||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
||||
|
|
|
|||
3
vendor/github.com/olekukonko/cat/.gitignore
generated
vendored
Normal file
3
vendor/github.com/olekukonko/cat/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.idea
|
||||
.github
|
||||
lab
|
||||
21
vendor/github.com/olekukonko/cat/LICENSE
generated
vendored
Normal file
21
vendor/github.com/olekukonko/cat/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2025 Oleku Konko
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
168
vendor/github.com/olekukonko/cat/README.md
generated
vendored
Normal file
168
vendor/github.com/olekukonko/cat/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
# 🐱 `cat` - The Fast & Fluent String Concatenation Library for Go
|
||||
|
||||
> **"Because building strings shouldn't feel like herding cats"** 😼
|
||||
|
||||
## Why `cat`?
|
||||
|
||||
Go's `strings.Builder` is great, but building complex strings often feels clunky. `cat` makes string concatenation:
|
||||
|
||||
- **Faster** - Optimized paths for common types, zero-allocation conversions
|
||||
- **Fluent** - Chainable methods for beautiful, readable code
|
||||
- **Flexible** - Handles any type, nested structures, and custom formatting
|
||||
- **Smart** - Automatic pooling, size estimation, and separator handling
|
||||
|
||||
```go
|
||||
// Without cat
|
||||
var b strings.Builder
|
||||
b.WriteString("Hello, ")
|
||||
b.WriteString(user.Name)
|
||||
b.WriteString("! You have ")
|
||||
b.WriteString(strconv.Itoa(count))
|
||||
b.WriteString(" new messages.")
|
||||
result := b.String()
|
||||
|
||||
// With cat
|
||||
result := cat.Concat("Hello, ", user.Name, "! You have ", count, " new messages.")
|
||||
```
|
||||
|
||||
## 🔥 Hot Features
|
||||
|
||||
### 1. Fluent Builder API
|
||||
|
||||
Build strings like a boss with method chaining:
|
||||
|
||||
```go
|
||||
s := cat.New(", ").
|
||||
Add("apple").
|
||||
If(user.IsVIP, "golden kiwi").
|
||||
Add("orange").
|
||||
Sep(" | "). // Change separator mid-way
|
||||
Add("banana").
|
||||
String()
|
||||
// "apple, golden kiwi, orange | banana"
|
||||
```
|
||||
|
||||
### 2. Zero-Allocation Magic
|
||||
|
||||
- **Pooled builders** (optional) reduce GC pressure
|
||||
- **Unsafe byte conversions** (opt-in) avoid `[]byte`→`string` copies
|
||||
- **Stack buffers** for numbers instead of heap allocations
|
||||
|
||||
```go
|
||||
// Enable performance features
|
||||
cat.Pool(true) // Builder pooling
|
||||
cat.SetUnsafeBytes(true) // Zero-copy []byte conversion
|
||||
```
|
||||
|
||||
### 3. Handles Any Type - Even Nested Ones!
|
||||
|
||||
No more manual type conversions:
|
||||
|
||||
```go
|
||||
data := map[string]any{
|
||||
"id": 12345,
|
||||
"tags": []string{"go", "fast", "efficient"},
|
||||
}
|
||||
|
||||
fmt.Println(cat.JSONPretty(data))
|
||||
// {
|
||||
// "id": 12345,
|
||||
// "tags": ["go", "fast", "efficient"]
|
||||
// }
|
||||
```
|
||||
|
||||
### 4. Concatenation for Every Use Case
|
||||
|
||||
```go
|
||||
// Simple joins
|
||||
cat.With(", ", "apple", "banana", "cherry") // "apple, banana, cherry"
|
||||
|
||||
// File paths
|
||||
cat.Path("dir", "sub", "file.txt") // "dir/sub/file.txt"
|
||||
|
||||
// CSV
|
||||
cat.CSV(1, 2, 3) // "1,2,3"
|
||||
|
||||
// Conditional elements
|
||||
cat.Start("Hello").If(user != nil, " ", user.Name) // "Hello" or "Hello Alice"
|
||||
|
||||
// Repeated patterns
|
||||
cat.RepeatWith("-+", "X", 3) // "X-+X-+X"
|
||||
```
|
||||
|
||||
### 5. Smarter Than Your Average String Lib
|
||||
|
||||
```go
|
||||
// Automatic nesting handling
|
||||
nested := []any{"a", []any{"b", "c"}, "d"}
|
||||
cat.FlattenWith(",", nested) // "a,b,c,d"
|
||||
|
||||
// Precise size estimation (minimizes allocations)
|
||||
b := cat.New(", ").Grow(estimatedSize) // Preallocate exactly what you need
|
||||
|
||||
// Reflection support for any type
|
||||
cat.Reflect(anyComplexStruct) // "{Field1:value Field2:[1 2 3]}"
|
||||
```
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
```bash
|
||||
go get github.com/your-repo/cat
|
||||
```
|
||||
|
||||
```go
|
||||
import "github.com/your-repo/cat"
|
||||
|
||||
func main() {
|
||||
// Simple concatenation
|
||||
msg := cat.Concat("User ", userID, " has ", count, " items")
|
||||
|
||||
// Pooled builder (for high-performance loops)
|
||||
builder := cat.New(", ")
|
||||
defer builder.Release() // Return to pool
|
||||
result := builder.Add(items...).String()
|
||||
}
|
||||
```
|
||||
|
||||
## 🤔 Why Not Just Use...?
|
||||
|
||||
- `fmt.Sprintf` - Slow, many allocations
|
||||
- `strings.Join` - Only works with strings
|
||||
- `bytes.Buffer` - No separator support, manual type handling
|
||||
- `string +` - Even worse performance, especially in loops
|
||||
|
||||
## 💡 Pro Tips
|
||||
|
||||
1. **Enable pooling** in high-throughput scenarios
|
||||
2. **Preallocate** with `.Grow()` when you know the final size
|
||||
3. Use **`If()`** for conditional elements in fluent chains
|
||||
4. Try **`SetUnsafeBytes(true)`** if you can guarantee byte slices won't mutate
|
||||
5. **Release builders** when pooling is enabled
|
||||
|
||||
## 🐱👤 Advanced Usage
|
||||
|
||||
```go
|
||||
// Custom value formatting
|
||||
type User struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
func (u User) String() string {
|
||||
return cat.With(" ", u.Name, cat.Wrap("(", u.Age, ")"))
|
||||
}
|
||||
|
||||
// JSON-like output
|
||||
func JSONPretty(v any) string {
|
||||
return cat.WrapWith(",\n ", "{\n ", "\n}", prettyFields(v))
|
||||
}
|
||||
```
|
||||
|
||||
```text
|
||||
/\_/\
|
||||
( o.o ) > Concatenate with purr-fection!
|
||||
> ^ <
|
||||
|
||||
```
|
||||
|
||||
**`cat`** - Because life's too short for ugly string building code. 😻
|
||||
124
vendor/github.com/olekukonko/cat/builder.go
generated
vendored
Normal file
124
vendor/github.com/olekukonko/cat/builder.go
generated
vendored
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
package cat
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Builder is a fluent concatenation helper. It is safe for concurrent use by
|
||||
// multiple goroutines only if each goroutine uses a distinct *Builder.
|
||||
// If pooling is enabled via Pool(true), call Release() when done.
|
||||
// The Builder uses an internal strings.Builder for efficient string concatenation
|
||||
// and manages a separator that is inserted between added values.
|
||||
// It supports chaining methods for a fluent API style.
|
||||
type Builder struct {
|
||||
buf strings.Builder
|
||||
sep string
|
||||
needsSep bool
|
||||
}
|
||||
|
||||
// New begins a new Builder with a separator. If pooling is enabled,
|
||||
// the Builder is reused and MUST be released with b.Release() when done.
|
||||
// If sep is empty, uses DefaultSep().
|
||||
// Optional initial arguments x are added immediately after creation.
|
||||
// Pooling is controlled globally via Pool(true/false); when enabled, Builders
|
||||
// are recycled to reduce allocations in high-throughput scenarios.
|
||||
func New(sep string, x ...any) *Builder {
|
||||
var b *Builder
|
||||
if poolEnabled.Load() {
|
||||
b = builderPool.Get().(*Builder)
|
||||
b.buf.Reset()
|
||||
b.sep = sep
|
||||
b.needsSep = false
|
||||
} else {
|
||||
b = &Builder{sep: sep}
|
||||
}
|
||||
|
||||
// Process initial arguments *after* the builder is prepared.
|
||||
if len(x) > 0 {
|
||||
b.Add(x...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Start begins a new Builder with no separator (using an empty string as sep).
|
||||
// It is a convenience function that wraps New(empty, x...), where empty is a constant empty string.
|
||||
// This allows starting a concatenation without any separator between initial or subsequent additions.
|
||||
// If pooling is enabled via Pool(true), the returned Builder MUST be released with b.Release() when done.
|
||||
// Optional variadic arguments x are passed directly to New and added immediately after creation.
|
||||
// Useful for fluent chains where no default separator is desired from the start.
|
||||
func Start(x ...any) *Builder {
|
||||
return New(empty, x...)
|
||||
}
|
||||
|
||||
// Grow pre-sizes the internal buffer.
|
||||
// This can be used to preallocate capacity based on an estimated total size,
|
||||
// reducing reallocations during subsequent Add calls.
|
||||
// It chains, returning the Builder for fluent use.
|
||||
func (b *Builder) Grow(n int) *Builder { b.buf.Grow(n); return b }
|
||||
|
||||
// Add appends values to the builder.
|
||||
// It inserts the current separator before each new value if needed (i.e., after the first addition).
|
||||
// Values are converted to strings using the optimized write function, which handles
|
||||
// common types efficiently without allocations where possible.
|
||||
// Supports any number of arguments of any type.
|
||||
// Chains, returning the Builder for fluent use.
|
||||
func (b *Builder) Add(args ...any) *Builder {
|
||||
for _, arg := range args {
|
||||
if b.needsSep && b.sep != empty {
|
||||
b.buf.WriteString(b.sep)
|
||||
}
|
||||
write(&b.buf, arg)
|
||||
b.needsSep = true
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// If appends values to the builder only if the condition is true.
|
||||
// Behaves like Add when condition is true; does nothing otherwise.
|
||||
// Useful for conditional concatenation in chains.
|
||||
// Chains, returning the Builder for fluent use.
|
||||
func (b *Builder) If(condition bool, args ...any) *Builder {
|
||||
if condition {
|
||||
b.Add(args...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Sep changes the separator for subsequent additions.
|
||||
// Future Add calls will use this new separator.
|
||||
// Does not affect already added content.
|
||||
// If sep is empty, no separator will be added between future values.
|
||||
// Chains, returning the Builder for fluent use.
|
||||
func (b *Builder) Sep(sep string) *Builder { b.sep = sep; return b }
|
||||
|
||||
// String returns the concatenated result.
|
||||
// This does not release the Builder; if pooling is enabled, call Release separately
|
||||
// if you are done with the Builder.
|
||||
// Can be called multiple times; the internal buffer remains unchanged.
|
||||
func (b *Builder) String() string { return b.buf.String() }
|
||||
|
||||
// Output returns the concatenated result and releases the Builder if pooling is enabled.
|
||||
// This is a convenience method to get the string and clean up in one call.
|
||||
// After Output, the Builder should not be used further if pooled, as it may be recycled.
|
||||
// If pooling is disabled, it behaves like String without release.
|
||||
func (b *Builder) Output() string {
|
||||
out := b.buf.String()
|
||||
b.Release() // Release takes care of the poolEnabled check
|
||||
return out
|
||||
}
|
||||
|
||||
// Release returns the Builder to the pool if pooling is enabled.
|
||||
// You should call this exactly once per New() when Pool(true) is active.
|
||||
// Resets the internal state (buffer, separator, needsSep) before pooling to avoid
|
||||
// retaining data or large allocations.
|
||||
// If pooling is disabled, this is a no-op.
|
||||
// Safe to call multiple times, but typically called once at the end of use.
|
||||
func (b *Builder) Release() {
|
||||
if poolEnabled.Load() {
|
||||
// Avoid retaining large buffers.
|
||||
b.buf.Reset()
|
||||
b.sep = empty
|
||||
b.needsSep = false
|
||||
builderPool.Put(b)
|
||||
}
|
||||
}
|
||||
117
vendor/github.com/olekukonko/cat/cat.go
generated
vendored
Normal file
117
vendor/github.com/olekukonko/cat/cat.go
generated
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
// Package cat provides efficient and flexible string concatenation utilities.
|
||||
// It includes optimized functions for concatenating various types, builders for fluent chaining,
|
||||
// and configuration options for defaults, pooling, and unsafe optimizations.
|
||||
// The package aims to minimize allocations and improve performance in string building scenarios.
|
||||
package cat
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Constants used throughout the package for separators, defaults, and configuration.
|
||||
// These include common string literals for separators, empty strings, and special representations,
|
||||
// as well as limits like recursion depth. Defining them as constants allows for compile-time
|
||||
// optimizations, readability, and consistent usage in functions like Space, Path, CSV, and reflection handlers.
|
||||
// cat.go (updated constants section)
|
||||
const (
|
||||
empty = "" // Empty string constant, used for checks and defaults.
|
||||
space = " " // Single space, default separator.
|
||||
slash = "/" // Forward slash, for paths.
|
||||
dot = "." // Period, for extensions or decimals.
|
||||
comma = "," // Comma, for CSV or lists.
|
||||
equal = "=" // Equals, for comparisons.
|
||||
newline = "\n" // Newline, for multi-line strings.
|
||||
|
||||
// SQL-specific constants
|
||||
and = "AND" // AND operator, for SQL conditions.
|
||||
inOpen = " IN (" // Opening for SQL IN clause
|
||||
inClose = ")" // Closing for SQL IN clause
|
||||
asSQL = " AS " // SQL AS for aliasing
|
||||
count = "COUNT(" // SQL COUNT function prefix
|
||||
sum = "SUM(" // SQL SUM function prefix
|
||||
avg = "AVG(" // SQL AVG function prefix
|
||||
maxOpen = "MAX(" // SQL MAX function prefix
|
||||
minOpen = "MIN(" // SQL MIN function prefix
|
||||
caseSQL = "CASE " // SQL CASE keyword
|
||||
when = "WHEN " // SQL WHEN clause
|
||||
then = " THEN " // SQL THEN clause
|
||||
elseSQL = " ELSE " // SQL ELSE clause
|
||||
end = " END" // SQL END for CASE
|
||||
countAll = "COUNT(*)" // SQL COUNT(*) for all rows
|
||||
parenOpen = "(" // Opening parenthesis
|
||||
parenClose = ")" // Closing parenthesis
|
||||
|
||||
maxRecursionDepth = 32 // Maximum recursion depth for nested structure handling.
|
||||
nilString = "<nil>" // String representation for nil values.
|
||||
unexportedString = "<?>" // Placeholder for unexported fields.
|
||||
)
|
||||
|
||||
// Numeric is a generic constraint interface for numeric types.
|
||||
// It includes all signed/unsigned integers and floats.
|
||||
// Used in generic functions like Number and NumberWith to constrain to numbers.
|
||||
type Numeric interface {
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64
|
||||
}
|
||||
|
||||
// poolEnabled controls whether New() reuses Builder instances from a pool.
|
||||
// Atomic.Bool for thread-safe toggle.
|
||||
// When true, Builders from New must be Released to avoid leaks.
|
||||
var poolEnabled atomic.Bool
|
||||
|
||||
// builderPool stores reusable *Builder to reduce GC pressure on hot paths.
|
||||
// Uses sync.Pool for efficient allocation/reuse.
|
||||
// New func creates a fresh &Builder when pool is empty.
|
||||
var builderPool = sync.Pool{
|
||||
New: func() any { return &Builder{} },
|
||||
}
|
||||
|
||||
// Pool enables or disables Builder pooling for New()/Release().
|
||||
// When enabled, you MUST call b.Release() after b.String() to return it.
|
||||
// Thread-safe via atomic.Store.
|
||||
// Enable for high-throughput scenarios to reduce allocations.
|
||||
func Pool(enable bool) { poolEnabled.Store(enable) }
|
||||
|
||||
// unsafeBytesFlag controls zero-copy []byte -> string behavior via atomics.
|
||||
// Int32 used for atomic operations: 1 = enabled, 0 = disabled.
|
||||
// Affects bytesToString function for zero-copy conversions using unsafe.
|
||||
var unsafeBytesFlag atomic.Int32 // 1 = true, 0 = false
|
||||
|
||||
// SetUnsafeBytes toggles zero-copy []byte -> string conversions globally.
|
||||
// When enabled, bytesToString uses unsafe.String for zero-allocation conversion.
|
||||
// Thread-safe via atomic.Store.
|
||||
// Use with caution: assumes the byte slice is not modified after conversion.
|
||||
// Compatible with Go 1.20+; fallback to string(bts) if disabled.
|
||||
func SetUnsafeBytes(enable bool) {
|
||||
if enable {
|
||||
unsafeBytesFlag.Store(1)
|
||||
} else {
|
||||
unsafeBytesFlag.Store(0)
|
||||
}
|
||||
}
|
||||
|
||||
// IsUnsafeBytes reports whether zero-copy []byte -> string is enabled.
|
||||
// Thread-safe via atomic.Load.
|
||||
// Returns true if flag is 1, false otherwise.
|
||||
// Useful for checking current configuration.
|
||||
func IsUnsafeBytes() bool { return unsafeBytesFlag.Load() == 1 }
|
||||
|
||||
// deterministicMaps controls whether map keys are sorted for deterministic output in string conversions.
|
||||
// It uses atomic.Bool for thread-safe access.
|
||||
var deterministicMaps atomic.Bool
|
||||
|
||||
// SetDeterministicMaps controls whether map keys are sorted for deterministic output
|
||||
// in reflection-based handling (e.g., in writeReflect for maps).
|
||||
// When enabled, keys are sorted using a string-based comparison for consistent string representations.
|
||||
// Thread-safe via atomic.Store.
|
||||
// Useful for reproducible outputs in testing or logging.
|
||||
func SetDeterministicMaps(enable bool) {
|
||||
deterministicMaps.Store(enable)
|
||||
}
|
||||
|
||||
// IsDeterministicMaps returns current map sorting setting.
|
||||
// Thread-safe via atomic.Load.
|
||||
// Returns true if deterministic sorting is enabled, false otherwise.
|
||||
func IsDeterministicMaps() bool {
|
||||
return deterministicMaps.Load()
|
||||
}
|
||||
590
vendor/github.com/olekukonko/cat/concat.go
generated
vendored
Normal file
590
vendor/github.com/olekukonko/cat/concat.go
generated
vendored
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
package cat
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Append appends args to dst and returns the grown slice.
|
||||
// Callers can reuse dst across calls to amortize allocs.
|
||||
// It uses an internal Builder for efficient concatenation of the args (no separators),
|
||||
// then appends the result to the dst byte slice.
|
||||
// Preallocates based on a size estimate to minimize reallocations.
|
||||
// Benefits from Builder pooling if enabled.
|
||||
// Useful for building byte slices incrementally without separators.
|
||||
func Append(dst []byte, args ...any) []byte {
|
||||
return AppendWith(empty, dst, args...)
|
||||
}
|
||||
|
||||
// AppendWith appends args to dst and returns the grown slice.
|
||||
// Callers can reuse dst across calls to amortize allocs.
|
||||
// Similar to Append, but inserts the specified sep between each arg.
|
||||
// Preallocates based on a size estimate including separators.
|
||||
// Benefits from Builder pooling if enabled.
|
||||
// Useful for building byte slices incrementally with custom separators.
|
||||
func AppendWith(sep string, dst []byte, args ...any) []byte {
|
||||
if len(args) == 0 {
|
||||
return dst
|
||||
}
|
||||
b := New(sep)
|
||||
b.Grow(estimateWith(sep, args))
|
||||
b.Add(args...)
|
||||
out := b.Output()
|
||||
return append(dst, out...)
|
||||
}
|
||||
|
||||
// AppendBytes joins byte slices without separators.
|
||||
// Only for compatibility with low-level byte processing.
|
||||
// Directly appends each []byte arg to dst without any conversion or separators.
|
||||
// Efficient for pure byte concatenation; no allocations if dst has capacity.
|
||||
// Returns the extended dst slice.
|
||||
// Does not use Builder, as it's simple append operations.
|
||||
func AppendBytes(dst []byte, args ...[]byte) []byte {
|
||||
if len(args) == 0 {
|
||||
return dst
|
||||
}
|
||||
for _, b := range args {
|
||||
dst = append(dst, b...)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// AppendTo writes arguments to an existing strings.Builder.
|
||||
// More efficient than creating new builders.
|
||||
// Appends each arg to the provided strings.Builder using the optimized write function.
|
||||
// No separators are added; for direct concatenation.
|
||||
// Useful when you already have a strings.Builder and want to add more values efficiently.
|
||||
// Does not use cat.Builder, as it appends to an existing strings.Builder.
|
||||
func AppendTo(b *strings.Builder, args ...any) {
|
||||
for _, arg := range args {
|
||||
write(b, arg)
|
||||
}
|
||||
}
|
||||
|
||||
// AppendStrings writes strings to an existing strings.Builder.
|
||||
// Directly writes each string arg to the provided strings.Builder.
|
||||
// No type checks or conversions; assumes all args are strings.
|
||||
// Efficient for appending known strings without separators.
|
||||
// Does not use cat.Builder, as it appends to an existing strings.Builder.
|
||||
func AppendStrings(b *strings.Builder, ss ...string) {
|
||||
for _, s := range ss {
|
||||
b.WriteString(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Between concatenates values wrapped between x and y (no separator between args).
|
||||
// Equivalent to BetweenWith with an empty separator.
|
||||
func Between(x, y any, args ...any) string {
|
||||
return BetweenWith(empty, x, y, args...)
|
||||
}
|
||||
|
||||
// BetweenWith concatenates values wrapped between x and y, using sep between x, args, and y.
|
||||
// Uses a pooled Builder if enabled; releases it after use.
|
||||
// Equivalent to With(sep, x, args..., y).
|
||||
func BetweenWith(sep string, x, y any, args ...any) string {
|
||||
b := New(sep)
|
||||
// Estimate size for all parts to avoid re-allocation.
|
||||
b.Grow(estimate([]any{x, y}) + estimateWith(sep, args))
|
||||
|
||||
b.Add(x)
|
||||
b.Add(args...)
|
||||
b.Add(y)
|
||||
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// CSV joins arguments with "," separators (no space).
|
||||
// Convenience wrapper for With using a comma as separator.
|
||||
// Useful for simple CSV string generation without spaces.
|
||||
func CSV(args ...any) string { return With(comma, args...) }
|
||||
|
||||
// Comma joins arguments with ", " separators.
|
||||
// Convenience wrapper for With using ", " as separator.
|
||||
// Useful for human-readable lists with comma and space.
|
||||
func Comma(args ...any) string { return With(comma+space, args...) }
|
||||
|
||||
// Concat concatenates any values (no separators).
|
||||
// Usage: cat.Concat("a", 1, true) → "a1true"
|
||||
// Equivalent to With with an empty separator.
|
||||
func Concat(args ...any) string {
|
||||
return With(empty, args...)
|
||||
}
|
||||
|
||||
// ConcatWith concatenates any values with separator.
|
||||
// Alias for With; joins args with the provided sep.
|
||||
func ConcatWith(sep string, args ...any) string {
|
||||
return With(sep, args...)
|
||||
}
|
||||
|
||||
// Flatten joins nested values into a single concatenation using empty.
|
||||
// Convenience for FlattenWith using empty.
|
||||
func Flatten(args ...any) string {
|
||||
return FlattenWith(empty, args...)
|
||||
}
|
||||
|
||||
// FlattenWith joins nested values into a single concatenation with sep, avoiding
|
||||
// intermediate slice allocations where possible.
|
||||
// It recursively flattens any nested []any arguments, concatenating all leaf items
|
||||
// with sep between them. Skips empty nested slices to avoid extra separators.
|
||||
// Leaf items (non-slices) are converted using the optimized write function.
|
||||
// Uses a pooled Builder if enabled; releases it after use.
|
||||
// Preallocates based on a recursive estimate for efficiency.
|
||||
// Example: FlattenWith(",", 1, []any{2, []any{3,4}}, 5) → "1,2,3,4,5"
|
||||
func FlattenWith(sep string, args ...any) string {
|
||||
if len(args) == 0 {
|
||||
return empty
|
||||
}
|
||||
|
||||
// Recursive estimate for preallocation.
|
||||
totalSize := recursiveEstimate(sep, args)
|
||||
|
||||
b := New(sep)
|
||||
b.Grow(totalSize)
|
||||
recursiveAdd(b, args)
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// Group joins multiple groups with empty between groups (no intra-group separators).
|
||||
// Convenience for GroupWith using empty.
|
||||
func Group(groups ...[]any) string {
|
||||
return GroupWith(empty, groups...)
|
||||
}
|
||||
|
||||
// GroupWith joins multiple groups with a separator between groups (no intra-group separators).
|
||||
// Concatenates each group internally without separators, then joins non-empty groups with sep.
|
||||
// Preestimates total size for allocation; uses pooled Builder if enabled.
|
||||
// Optimized for single group: direct Concat.
|
||||
// Useful for grouping related items with inter-group separation.
|
||||
func GroupWith(sep string, groups ...[]any) string {
|
||||
if len(groups) == 0 {
|
||||
return empty
|
||||
}
|
||||
if len(groups) == 1 {
|
||||
return Concat(groups[0]...)
|
||||
}
|
||||
|
||||
total := 0
|
||||
nonEmpty := 0
|
||||
for _, g := range groups {
|
||||
if len(g) == 0 {
|
||||
continue
|
||||
}
|
||||
if nonEmpty > 0 {
|
||||
total += len(sep)
|
||||
}
|
||||
total += estimate(g)
|
||||
nonEmpty++
|
||||
}
|
||||
|
||||
b := New(empty)
|
||||
b.Grow(total)
|
||||
first := true
|
||||
for _, g := range groups {
|
||||
if len(g) == 0 {
|
||||
continue
|
||||
}
|
||||
if !first && sep != empty {
|
||||
b.buf.WriteString(sep)
|
||||
}
|
||||
first = false
|
||||
for _, a := range g {
|
||||
write(&b.buf, a)
|
||||
}
|
||||
}
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// Indent prefixes the concatenation of args with depth levels of two spaces per level.
|
||||
// Example: Indent(2, "hello") => " hello"
|
||||
// If depth <= 0, equivalent to Concat(args...).
|
||||
// Uses " " repeated depth times as prefix, followed by concatenated args (no separators).
|
||||
// Benefits from pooling via Concat.
|
||||
func Indent(depth int, args ...any) string {
|
||||
if depth <= 0 {
|
||||
return Concat(args...)
|
||||
}
|
||||
prefix := strings.Repeat(" ", depth)
|
||||
return Prefix(prefix, args...)
|
||||
}
|
||||
|
||||
// Join joins strings (matches stdlib strings.Join behavior).
|
||||
// Usage: cat.Join("a", "b") → "a b" (using empty)
|
||||
// Joins the variadic string args with the current empty.
|
||||
// Useful for compatibility with stdlib but using package default sep.
|
||||
func Join(elems ...string) string {
|
||||
return strings.Join(elems, empty)
|
||||
}
|
||||
|
||||
// JoinWith joins strings with separator (variadic version).
|
||||
// Directly uses strings.Join on the variadic string args with sep.
|
||||
// Efficient for known strings; no conversions needed.
|
||||
func JoinWith(sep string, elems ...string) string {
|
||||
return strings.Join(elems, sep)
|
||||
}
|
||||
|
||||
// Lines joins arguments with newline separators.
|
||||
// Convenience for With using "\n" as separator.
|
||||
// Useful for building multi-line strings.
|
||||
func Lines(args ...any) string { return With(newline, args...) }
|
||||
|
||||
// Number concatenates numeric values without separators.
|
||||
// Generic over Numeric types.
|
||||
// Equivalent to NumberWith with empty sep.
|
||||
func Number[T Numeric](a ...T) string {
|
||||
return NumberWith(empty, a...)
|
||||
}
|
||||
|
||||
// NumberWith concatenates numeric values with the provided separator.
|
||||
// Generic over Numeric types.
|
||||
// If no args, returns empty string.
|
||||
// Uses pooled Builder if enabled, with rough growth estimate (8 bytes per item).
|
||||
// Relies on valueToString for numeric conversion.
|
||||
func NumberWith[T Numeric](sep string, a ...T) string {
|
||||
if len(a) == 0 {
|
||||
return empty
|
||||
}
|
||||
|
||||
b := New(sep)
|
||||
b.Grow(len(a) * 8)
|
||||
for _, v := range a {
|
||||
b.Add(v)
|
||||
}
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// Path joins arguments with "/" separators.
|
||||
// Convenience for With using "/" as separator.
|
||||
// Useful for building file paths or URLs.
|
||||
func Path(args ...any) string { return With(slash, args...) }
|
||||
|
||||
// Prefix concatenates with a prefix (no separator).
|
||||
// Equivalent to PrefixWith with empty sep.
|
||||
func Prefix(p any, args ...any) string {
|
||||
return PrefixWith(empty, p, args...)
|
||||
}
|
||||
|
||||
// PrefixWith concatenates with a prefix and separator.
|
||||
// Adds p, then sep (if args present and sep not empty), then joins args with sep.
|
||||
// Uses pooled Builder if enabled.
|
||||
func PrefixWith(sep string, p any, args ...any) string {
|
||||
b := New(sep)
|
||||
b.Grow(estimateWith(sep, args) + estimate([]any{p}))
|
||||
b.Add(p)
|
||||
b.Add(args...)
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// PrefixEach applies the same prefix to each argument and joins the pairs with sep.
|
||||
// Example: PrefixEach("pre-", ",", "a","b") => "pre-a,pre-b"
|
||||
// Preestimates size including prefixes and seps.
|
||||
// Uses pooled Builder if enabled; manually adds sep between pairs, no sep between p and a.
|
||||
// Returns empty if no args.
|
||||
func PrefixEach(p any, sep string, args ...any) string {
|
||||
if len(args) == 0 {
|
||||
return empty
|
||||
}
|
||||
pSize := estimate([]any{p})
|
||||
total := len(sep)*(len(args)-1) + estimate(args) + pSize*len(args)
|
||||
|
||||
b := New(empty)
|
||||
b.Grow(total)
|
||||
for i, a := range args {
|
||||
if i > 0 && sep != empty {
|
||||
b.buf.WriteString(sep)
|
||||
}
|
||||
write(&b.buf, p)
|
||||
write(&b.buf, a)
|
||||
}
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// Pair joins exactly two values (no separator).
|
||||
// Equivalent to PairWith with empty sep.
|
||||
func Pair(a, b any) string {
|
||||
return PairWith(empty, a, b)
|
||||
}
|
||||
|
||||
// PairWith joins exactly two values with a separator.
|
||||
// Optimized for two args: uses With(sep, a, b).
|
||||
func PairWith(sep string, a, b any) string {
|
||||
return With(sep, a, b)
|
||||
}
|
||||
|
||||
// Quote wraps each argument in double quotes, separated by spaces.
|
||||
// Equivalent to QuoteWith with '"' as quote.
|
||||
func Quote(args ...any) string {
|
||||
return QuoteWith('"', args...)
|
||||
}
|
||||
|
||||
// QuoteWith wraps each argument with the specified quote byte, separated by spaces.
|
||||
// Wraps each arg with quote, writes arg, closes with quote; joins with space.
|
||||
// Preestimates with quotes and spaces.
|
||||
// Uses pooled Builder if enabled.
|
||||
func QuoteWith(quote byte, args ...any) string {
|
||||
if len(args) == 0 {
|
||||
return empty
|
||||
}
|
||||
total := estimate(args) + 2*len(args) + len(space)*(len(args)-1)
|
||||
|
||||
b := New(empty)
|
||||
b.Grow(total)
|
||||
need := false
|
||||
for _, a := range args {
|
||||
if need {
|
||||
b.buf.WriteString(space)
|
||||
}
|
||||
b.buf.WriteByte(quote)
|
||||
write(&b.buf, a)
|
||||
b.buf.WriteByte(quote)
|
||||
need = true
|
||||
}
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// Repeat concatenates val n times (no sep between instances).
|
||||
// Equivalent to RepeatWith with empty sep.
|
||||
func Repeat(val any, n int) string {
|
||||
return RepeatWith(empty, val, n)
|
||||
}
|
||||
|
||||
// RepeatWith concatenates val n times with sep between each instance.
|
||||
// If n <= 0, returns an empty string.
|
||||
// Optimized to make exactly one allocation; converts val once.
|
||||
// Uses pooled Builder if enabled.
|
||||
func RepeatWith(sep string, val any, n int) string {
|
||||
if n <= 0 {
|
||||
return empty
|
||||
}
|
||||
if n == 1 {
|
||||
return valueToString(val)
|
||||
}
|
||||
b := New(sep)
|
||||
b.Grow(n*estimate([]any{val}) + (n-1)*len(sep))
|
||||
for i := 0; i < n; i++ {
|
||||
b.Add(val)
|
||||
}
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// Reflect converts a reflect.Value to its string representation.
|
||||
// It handles all kinds of reflected values including primitives, structs, slices, maps, etc.
|
||||
// For nil values, it returns the nilString constant ("<nil>").
|
||||
// For unexported or inaccessible fields, it returns unexportedString ("<?>").
|
||||
// The output follows Go's syntax conventions where applicable (e.g., slices as [a, b], maps as {k:v}).
|
||||
func Reflect(r reflect.Value) string {
|
||||
if !r.IsValid() {
|
||||
return nilString
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
writeReflect(&b, r.Interface(), 0)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Space concatenates arguments with space separators.
|
||||
// Convenience for With using " " as separator.
|
||||
func Space(args ...any) string { return With(space, args...) }
|
||||
|
||||
// Dot concatenates arguments with dot separators.
|
||||
// Convenience for With using " " as separator.
|
||||
func Dot(args ...any) string { return With(dot, args...) }
|
||||
|
||||
// Suffix concatenates with a suffix (no separator).
|
||||
// Equivalent to SuffixWith with empty sep.
|
||||
func Suffix(s any, args ...any) string {
|
||||
return SuffixWith(empty, s, args...)
|
||||
}
|
||||
|
||||
// SuffixWith concatenates with a suffix and separator.
|
||||
// Joins args with sep, then adds sep (if args present and sep not empty), then s.
|
||||
// Uses pooled Builder if enabled.
|
||||
func SuffixWith(sep string, s any, args ...any) string {
|
||||
b := New(sep)
|
||||
b.Grow(estimateWith(sep, args) + estimate([]any{s}))
|
||||
b.Add(args...)
|
||||
b.Add(s)
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// SuffixEach applies the same suffix to each argument and joins the pairs with sep.
|
||||
// Example: SuffixEach("-suf", " | ", "a","b") => "a-suf | b-suf"
|
||||
// Preestimates size including suffixes and seps.
|
||||
// Uses pooled Builder if enabled; manually adds sep between pairs, no sep between a and s.
|
||||
// Returns empty if no args.
|
||||
func SuffixEach(s any, sep string, args ...any) string {
|
||||
if len(args) == 0 {
|
||||
return empty
|
||||
}
|
||||
sSize := estimate([]any{s})
|
||||
total := len(sep)*(len(args)-1) + estimate(args) + sSize*len(args)
|
||||
|
||||
b := New(empty)
|
||||
b.Grow(total)
|
||||
for i, a := range args {
|
||||
if i > 0 && sep != empty {
|
||||
b.buf.WriteString(sep)
|
||||
}
|
||||
write(&b.buf, a)
|
||||
write(&b.buf, s)
|
||||
}
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// Sprint concatenates any values (no separators).
|
||||
// Usage: Sprint("a", 1, true) → "a1true"
|
||||
// Equivalent to Concat or With with an empty separator.
|
||||
func Sprint(args ...any) string {
|
||||
if len(args) == 0 {
|
||||
return empty
|
||||
}
|
||||
if len(args) == 1 {
|
||||
return valueToString(args[0])
|
||||
}
|
||||
|
||||
// For multiple args, use the existing Concat functionality
|
||||
return Concat(args...)
|
||||
}
|
||||
|
||||
// Trio joins exactly three values (no separator).
|
||||
// Equivalent to TrioWith with empty sep
|
||||
func Trio(a, b, c any) string {
|
||||
return TrioWith(empty, a, b, c)
|
||||
}
|
||||
|
||||
// TrioWith joins exactly three values with a separator.
|
||||
// Optimized for three args: uses With(sep, a, b, c).
|
||||
func TrioWith(sep string, a, b, c any) string {
|
||||
return With(sep, a, b, c)
|
||||
}
|
||||
|
||||
// With concatenates arguments with the specified separator.
|
||||
// Core concatenation function with sep.
|
||||
// Optimized for zero or one arg: empty or direct valueToString.
|
||||
// Fast path for all strings: exact preallocation, direct writes via raw strings.Builder (minimal branches/allocs).
|
||||
// Fallback: pooled Builder with estimateWith, adds args with sep.
|
||||
// Benefits from pooling if enabled for mixed types.
|
||||
func With(sep string, args ...any) string {
|
||||
switch len(args) {
|
||||
case 0:
|
||||
return empty
|
||||
case 1:
|
||||
return valueToString(args[0])
|
||||
}
|
||||
|
||||
// Fast path for all strings: use raw strings.Builder for speed, no pooling needed.
|
||||
allStrings := true
|
||||
totalLen := len(sep) * (len(args) - 1)
|
||||
for _, a := range args {
|
||||
if s, ok := a.(string); ok {
|
||||
totalLen += len(s)
|
||||
} else {
|
||||
allStrings = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allStrings {
|
||||
var b strings.Builder
|
||||
b.Grow(totalLen)
|
||||
b.WriteString(args[0].(string))
|
||||
for i := 1; i < len(args); i++ {
|
||||
if sep != empty {
|
||||
b.WriteString(sep)
|
||||
}
|
||||
b.WriteString(args[i].(string))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Fallback for mixed types: use pooled Builder.
|
||||
b := New(sep)
|
||||
b.Grow(estimateWith(sep, args))
|
||||
b.Add(args...)
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// Wrap encloses concatenated args between before and after strings (no inner separator).
|
||||
// Equivalent to Concat(before, args..., after).
|
||||
func Wrap(before, after string, args ...any) string {
|
||||
b := Start()
|
||||
b.Grow(len(before) + len(after) + estimate(args))
|
||||
|
||||
b.Add(before)
|
||||
b.Add(args...)
|
||||
b.Add(after)
|
||||
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// WrapEach wraps each argument individually with before/after, concatenated without separators.
|
||||
// Applies before + arg + after to each arg.
|
||||
// Preestimates size; uses pooled Builder if enabled.
|
||||
// Returns empty if no args.
|
||||
// Useful for wrapping multiple items identically without joins.
|
||||
func WrapEach(before, after string, args ...any) string {
|
||||
if len(args) == 0 {
|
||||
return empty
|
||||
}
|
||||
total := (len(before)+len(after))*len(args) + estimate(args)
|
||||
|
||||
b := Start() // Use pooled builder, but we will write manually.
|
||||
b.Grow(total)
|
||||
for _, a := range args {
|
||||
write(&b.buf, before)
|
||||
write(&b.buf, a)
|
||||
write(&b.buf, after)
|
||||
}
|
||||
// No separators were ever added, so this is safe.
|
||||
b.needsSep = true // Correctly set state in case of reuse.
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// WrapWith encloses concatenated args between before and after strings,
|
||||
// joining the arguments with the provided separator.
|
||||
// If no args, returns before + after.
|
||||
// Builds inner with With(sep, args...), then Concat(before, inner, after).
|
||||
// Benefits from pooling via With and Concat.
|
||||
func WrapWith(sep, before, after string, args ...any) string {
|
||||
if len(args) == 0 {
|
||||
return before + after
|
||||
}
|
||||
// First, efficiently build the inner part.
|
||||
inner := With(sep, args...)
|
||||
|
||||
// Then, wrap it without allocating another slice.
|
||||
b := Start()
|
||||
b.Grow(len(before) + len(inner) + len(after))
|
||||
|
||||
b.Add(before)
|
||||
b.Add(inner)
|
||||
b.Add(after)
|
||||
|
||||
return b.Output()
|
||||
}
|
||||
|
||||
// Pad surrounds a string with spaces on both sides.
|
||||
// Ensures proper spacing for SQL operators like "=", "AND", etc.
|
||||
// Example: Pad("=") returns " = " for cleaner formatting.
|
||||
func Pad(s string) string {
|
||||
return Concat(space, s, space)
|
||||
}
|
||||
|
||||
// PadWith adds a separator before the string and a space after it.
|
||||
// Useful for formatting SQL parts with custom leading separators.
|
||||
// Example: PadWith(",", "column") returns ",column ".
|
||||
func PadWith(sep, s string) string {
|
||||
return Concat(sep, s, space)
|
||||
}
|
||||
|
||||
// Parens wraps content in parentheses
|
||||
// Useful for grouping SQL conditions or expressions
|
||||
// Example: Parens("a = b AND c = d") → "(a = b AND c = d)"
|
||||
func Parens(content string) string {
|
||||
return Concat(parenOpen, content, parenClose)
|
||||
}
|
||||
|
||||
// ParensWith wraps multiple arguments in parentheses with a separator
|
||||
// Example: ParensWith(" AND ", "a = b", "c = d") → "(a = b AND c = d)"
|
||||
func ParensWith(sep string, args ...any) string {
|
||||
return Concat(parenOpen, With(sep, args...), parenClose)
|
||||
}
|
||||
376
vendor/github.com/olekukonko/cat/fn.go
generated
vendored
Normal file
376
vendor/github.com/olekukonko/cat/fn.go
generated
vendored
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
package cat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// write writes a value to the given strings.Builder using fast paths to avoid temporary allocations.
|
||||
// It handles common types like strings, byte slices, integers, floats, and booleans directly for efficiency.
|
||||
// For other types, it falls back to fmt.Fprint, which may involve allocations.
|
||||
// This function is optimized for performance in string concatenation scenarios, prioritizing
|
||||
// common cases like strings and numbers at the top of the type switch for compiler optimization.
|
||||
// Note: For integers and floats, it uses stack-allocated buffers and strconv.Append* functions to
|
||||
// convert numbers to strings without heap allocations.
|
||||
func write(b *strings.Builder, arg any) {
|
||||
writeValue(b, arg, 0)
|
||||
}
|
||||
|
||||
// writeValue appends the string representation of arg to b, handling recursion with a depth limit.
|
||||
// It serves as a recursive helper for write, directly handling primitives and delegating complex
|
||||
// types to writeReflect. The depth parameter prevents excessive recursion in deeply nested structures.
|
||||
func writeValue(b *strings.Builder, arg any, depth int) {
|
||||
// Handle recursion depth limit
|
||||
if depth > maxRecursionDepth {
|
||||
b.WriteString("...")
|
||||
return
|
||||
}
|
||||
|
||||
// Handle nil values
|
||||
if arg == nil {
|
||||
b.WriteString(nilString)
|
||||
return
|
||||
}
|
||||
|
||||
// Fast path type switch for all primitive types
|
||||
switch v := arg.(type) {
|
||||
case string:
|
||||
b.WriteString(v)
|
||||
case []byte:
|
||||
b.WriteString(bytesToString(v))
|
||||
case int:
|
||||
var buf [20]byte
|
||||
b.Write(strconv.AppendInt(buf[:0], int64(v), 10))
|
||||
case int64:
|
||||
var buf [20]byte
|
||||
b.Write(strconv.AppendInt(buf[:0], v, 10))
|
||||
case int32:
|
||||
var buf [11]byte
|
||||
b.Write(strconv.AppendInt(buf[:0], int64(v), 10))
|
||||
case int16:
|
||||
var buf [6]byte
|
||||
b.Write(strconv.AppendInt(buf[:0], int64(v), 10))
|
||||
case int8:
|
||||
var buf [4]byte
|
||||
b.Write(strconv.AppendInt(buf[:0], int64(v), 10))
|
||||
case uint:
|
||||
var buf [20]byte
|
||||
b.Write(strconv.AppendUint(buf[:0], uint64(v), 10))
|
||||
case uint64:
|
||||
var buf [20]byte
|
||||
b.Write(strconv.AppendUint(buf[:0], v, 10))
|
||||
case uint32:
|
||||
var buf [10]byte
|
||||
b.Write(strconv.AppendUint(buf[:0], uint64(v), 10))
|
||||
case uint16:
|
||||
var buf [5]byte
|
||||
b.Write(strconv.AppendUint(buf[:0], uint64(v), 10))
|
||||
case uint8:
|
||||
var buf [3]byte
|
||||
b.Write(strconv.AppendUint(buf[:0], uint64(v), 10))
|
||||
case float64:
|
||||
var buf [24]byte
|
||||
b.Write(strconv.AppendFloat(buf[:0], v, 'f', -1, 64))
|
||||
case float32:
|
||||
var buf [24]byte
|
||||
b.Write(strconv.AppendFloat(buf[:0], float64(v), 'f', -1, 32))
|
||||
case bool:
|
||||
if v {
|
||||
b.WriteString("true")
|
||||
} else {
|
||||
b.WriteString("false")
|
||||
}
|
||||
case fmt.Stringer:
|
||||
b.WriteString(v.String())
|
||||
case error:
|
||||
b.WriteString(v.Error())
|
||||
default:
|
||||
// Fallback to reflection-based handling
|
||||
writeReflect(b, arg, depth)
|
||||
}
|
||||
}
|
||||
|
||||
// writeReflect handles all complex types safely.
|
||||
func writeReflect(b *strings.Builder, arg any, depth int) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
b.WriteString("[!reflect panic!]")
|
||||
}
|
||||
}()
|
||||
|
||||
val := reflect.ValueOf(arg)
|
||||
if val.Kind() == reflect.Ptr {
|
||||
if val.IsNil() {
|
||||
b.WriteString(nilString)
|
||||
return
|
||||
}
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
b.WriteByte('[')
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
if i > 0 {
|
||||
b.WriteString(", ") // Use comma-space for readability
|
||||
}
|
||||
writeValue(b, val.Index(i).Interface(), depth+1)
|
||||
}
|
||||
b.WriteByte(']')
|
||||
|
||||
case reflect.Struct:
|
||||
typ := val.Type()
|
||||
b.WriteByte('{') // Use {} for structs to follow Go convention
|
||||
first := true
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
fieldValue := val.Field(i)
|
||||
if !fieldValue.CanInterface() {
|
||||
continue // Skip unexported fields
|
||||
}
|
||||
if !first {
|
||||
b.WriteByte(' ') // Use space as separator
|
||||
}
|
||||
first = false
|
||||
b.WriteString(typ.Field(i).Name)
|
||||
b.WriteByte(':')
|
||||
|
||||
writeValue(b, fieldValue.Interface(), depth+1)
|
||||
}
|
||||
b.WriteByte('}')
|
||||
|
||||
case reflect.Map:
|
||||
b.WriteByte('{')
|
||||
keys := val.MapKeys()
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
// A simple string-based sort for keys
|
||||
return fmt.Sprint(keys[i].Interface()) < fmt.Sprint(keys[j].Interface())
|
||||
})
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
b.WriteByte(' ') // Use space as separator
|
||||
}
|
||||
writeValue(b, key.Interface(), depth+1)
|
||||
b.WriteByte(':')
|
||||
writeValue(b, val.MapIndex(key).Interface(), depth+1)
|
||||
}
|
||||
b.WriteByte('}')
|
||||
|
||||
case reflect.Interface:
|
||||
if val.IsNil() {
|
||||
b.WriteString(nilString)
|
||||
return
|
||||
}
|
||||
writeValue(b, val.Elem().Interface(), depth+1)
|
||||
|
||||
default:
|
||||
fmt.Fprint(b, arg)
|
||||
}
|
||||
}
|
||||
|
||||
// valueToString converts any value to a string representation.
|
||||
// It uses optimized paths for common types to avoid unnecessary allocations.
|
||||
// For types like integers and floats, it directly uses strconv functions.
|
||||
// This function is useful for single-argument conversions or as a helper in other parts of the package.
|
||||
// Unlike write, it returns a string instead of appending to a builder.
|
||||
func valueToString(arg any) string {
|
||||
switch v := arg.(type) {
|
||||
case string:
|
||||
return v
|
||||
case []byte:
|
||||
return bytesToString(v)
|
||||
case int:
|
||||
return strconv.Itoa(v)
|
||||
case int64:
|
||||
return strconv.FormatInt(v, 10)
|
||||
case int32:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case uint:
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(v, 10)
|
||||
case float64:
|
||||
return strconv.FormatFloat(v, 'f', -1, 64)
|
||||
case bool:
|
||||
if v {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
case fmt.Stringer:
|
||||
return v.String()
|
||||
case error:
|
||||
return v.Error()
|
||||
default:
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
}
|
||||
|
||||
// estimateWith calculates a conservative estimate of the total string length when concatenating
|
||||
// the given arguments with a separator. This is used for preallocating capacity in strings.Builder
|
||||
// to minimize reallocations during building.
|
||||
// It accounts for the length of separators and estimates the length of each argument based on its type.
|
||||
// If no arguments are provided, it returns 0.
|
||||
func estimateWith(sep string, args []any) int {
|
||||
if len(args) == 0 {
|
||||
return 0
|
||||
}
|
||||
size := len(sep) * (len(args) - 1)
|
||||
size += estimate(args)
|
||||
return size
|
||||
}
|
||||
|
||||
// estimate calculates a conservative estimate of the combined string length of the given arguments.
|
||||
// It iterates over each argument and adds an estimated length based on its type:
|
||||
// - Strings and byte slices: exact length.
|
||||
// - Numbers: calculated digit count using numLen or uNumLen.
|
||||
// - Floats and others: fixed conservative estimates (e.g., 16 or 24 bytes).
|
||||
// This helper is used internally by estimateWith and focuses solely on the arguments without separators.
|
||||
func estimate(args []any) int {
|
||||
var size int
|
||||
for _, a := range args {
|
||||
switch v := a.(type) {
|
||||
case string:
|
||||
size += len(v)
|
||||
case []byte:
|
||||
size += len(v)
|
||||
case int:
|
||||
size += numLen(int64(v))
|
||||
case int8:
|
||||
size += numLen(int64(v))
|
||||
case int16:
|
||||
size += numLen(int64(v))
|
||||
case int32:
|
||||
size += numLen(int64(v))
|
||||
case int64:
|
||||
size += numLen(v)
|
||||
case uint:
|
||||
size += uNumLen(uint64(v))
|
||||
case uint8:
|
||||
size += uNumLen(uint64(v))
|
||||
case uint16:
|
||||
size += uNumLen(uint64(v))
|
||||
case uint32:
|
||||
size += uNumLen(uint64(v))
|
||||
case uint64:
|
||||
size += uNumLen(v)
|
||||
case float32:
|
||||
size += 16
|
||||
case float64:
|
||||
size += 24
|
||||
case bool:
|
||||
size += 5 // "false"
|
||||
case fmt.Stringer, error:
|
||||
size += 16 // conservative
|
||||
default:
|
||||
size += 16 // conservative
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// numLen returns the number of characters required to represent the signed integer n as a string.
|
||||
// It handles negative numbers by adding 1 for the '-' sign and uses a loop to count digits.
|
||||
// Special handling for math.MinInt64 to avoid overflow when negating.
|
||||
// Returns 1 for 0, and up to 20 for the largest values.
|
||||
func numLen(n int64) int {
|
||||
if n == 0 {
|
||||
return 1
|
||||
}
|
||||
c := 0
|
||||
if n < 0 {
|
||||
c = 1 // for '-'
|
||||
// NOTE: math.MinInt64 negated overflows; handle by adding one digit and returning 20.
|
||||
if n == -1<<63 {
|
||||
return 20
|
||||
}
|
||||
n = -n
|
||||
}
|
||||
for n > 0 {
|
||||
n /= 10
|
||||
c++
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// uNumLen returns the number of characters required to represent the unsigned integer n as a string.
|
||||
// It uses a loop to count digits.
|
||||
// Returns 1 for 0, and up to 20 for the largest uint64 values.
|
||||
func uNumLen(n uint64) int {
|
||||
if n == 0 {
|
||||
return 1
|
||||
}
|
||||
c := 0
|
||||
for n > 0 {
|
||||
n /= 10
|
||||
c++
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// bytesToString converts a byte slice to a string efficiently.
|
||||
// If the package's UnsafeBytes flag is set (via IsUnsafeBytes()), it uses unsafe operations
|
||||
// to create a string backed by the same memory as the byte slice, avoiding a copy.
|
||||
// This is zero-allocation when unsafe is enabled.
|
||||
// Falls back to standard string(bts) conversion otherwise.
|
||||
// For empty slices, it returns a constant empty string.
|
||||
// Compatible with Go 1.20+ unsafe functions like unsafe.String and unsafe.SliceData.
|
||||
func bytesToString(bts []byte) string {
|
||||
if len(bts) == 0 {
|
||||
return empty
|
||||
}
|
||||
if IsUnsafeBytes() {
|
||||
// Go 1.20+: unsafe.String with SliceData (1.20 introduced, 1.22 added SliceData).
|
||||
return unsafe.String(unsafe.SliceData(bts), len(bts))
|
||||
}
|
||||
return string(bts)
|
||||
}
|
||||
|
||||
// recursiveEstimate calculates the estimated string length for potentially nested arguments,
|
||||
// including the lengths of separators between elements. It recurses on nested []any slices,
|
||||
// flattening the structure while accounting for separators only between non-empty subparts.
|
||||
// This function is useful for preallocating capacity in builders for nested concatenation operations.
|
||||
func recursiveEstimate(sep string, args []any) int {
|
||||
if len(args) == 0 {
|
||||
return 0
|
||||
}
|
||||
size := 0
|
||||
needsSep := false
|
||||
for _, a := range args {
|
||||
switch v := a.(type) {
|
||||
case []any:
|
||||
subSize := recursiveEstimate(sep, v)
|
||||
if subSize > 0 {
|
||||
if needsSep {
|
||||
size += len(sep)
|
||||
}
|
||||
size += subSize
|
||||
needsSep = true
|
||||
}
|
||||
default:
|
||||
if needsSep {
|
||||
size += len(sep)
|
||||
}
|
||||
size += estimate([]any{a})
|
||||
needsSep = true
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// recursiveAdd appends the string representations of potentially nested arguments to the builder.
|
||||
// It recurses on nested []any slices, effectively flattening the structure by adding leaf values
|
||||
// directly via b.Add without inserting separators (separators are handled externally if needed).
|
||||
// This function is designed for efficient concatenation of nested argument lists.
|
||||
func recursiveAdd(b *Builder, args []any) {
|
||||
for _, a := range args {
|
||||
switch v := a.(type) {
|
||||
case []any:
|
||||
recursiveAdd(b, v)
|
||||
default:
|
||||
b.Add(a)
|
||||
}
|
||||
}
|
||||
}
|
||||
161
vendor/github.com/olekukonko/cat/sql.go
generated
vendored
Normal file
161
vendor/github.com/olekukonko/cat/sql.go
generated
vendored
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
package cat
|
||||
|
||||
// On builds a SQL ON clause comparing two columns across tables.
|
||||
// Formats as: "table1.column1 = table2.column2" with proper spacing.
|
||||
// Useful in JOIN conditions to match keys between tables.
|
||||
func On(table1, column1, table2, column2 string) string {
|
||||
return With(space,
|
||||
With(dot, table1, column1),
|
||||
Pad(equal),
|
||||
With(dot, table2, column2),
|
||||
)
|
||||
}
|
||||
|
||||
// Using builds a SQL condition comparing two aliased columns.
|
||||
// Formats as: "alias1.column1 = alias2.column2" for JOINs or filters.
|
||||
// Helps when working with table aliases in complex queries.
|
||||
func Using(alias1, column1, alias2, column2 string) string {
|
||||
return With(space,
|
||||
With(dot, alias1, column1),
|
||||
Pad(equal),
|
||||
With(dot, alias2, column2),
|
||||
)
|
||||
}
|
||||
|
||||
// And joins multiple SQL conditions with the AND operator.
|
||||
// Adds spacing to ensure clean SQL output (e.g., "cond1 AND cond2").
|
||||
// Accepts variadic arguments for flexible condition chaining.
|
||||
func And(conditions ...any) string {
|
||||
return With(Pad(and), conditions...)
|
||||
}
|
||||
|
||||
// In creates a SQL IN clause with properly quoted values
|
||||
// Example: In("status", "active", "pending") → "status IN ('active', 'pending')"
|
||||
// Handles value quoting and comma separation automatically
|
||||
func In(column string, values ...string) string {
|
||||
if len(values) == 0 {
|
||||
return Concat(column, inOpen, inClose)
|
||||
}
|
||||
|
||||
quotedValues := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
quotedValues[i] = "'" + v + "'"
|
||||
}
|
||||
return Concat(column, inOpen, JoinWith(comma+space, quotedValues...), inClose)
|
||||
}
|
||||
|
||||
// As creates an aliased SQL expression
|
||||
// Example: As("COUNT(*)", "total_count") → "COUNT(*) AS total_count"
|
||||
func As(expression, alias string) string {
|
||||
return Concat(expression, asSQL, alias)
|
||||
}
|
||||
|
||||
// Count creates a COUNT expression with optional alias
|
||||
// Example: Count("id") → "COUNT(id)"
|
||||
// Example: Count("id", "total") → "COUNT(id) AS total"
|
||||
// Example: Count("DISTINCT user_id", "unique_users") → "COUNT(DISTINCT user_id) AS unique_users"
|
||||
func Count(column string, alias ...string) string {
|
||||
expression := Concat(count, column, parenClose)
|
||||
if len(alias) == 0 {
|
||||
return expression
|
||||
}
|
||||
return As(expression, alias[0])
|
||||
}
|
||||
|
||||
// CountAll creates COUNT(*) with optional alias
|
||||
// Example: CountAll() → "COUNT(*)"
|
||||
// Example: CountAll("total") → "COUNT(*) AS total"
|
||||
func CountAll(alias ...string) string {
|
||||
if len(alias) == 0 {
|
||||
return countAll
|
||||
}
|
||||
return As(countAll, alias[0])
|
||||
}
|
||||
|
||||
// Sum creates a SUM expression with optional alias
|
||||
// Example: Sum("amount") → "SUM(amount)"
|
||||
// Example: Sum("amount", "total") → "SUM(amount) AS total"
|
||||
func Sum(column string, alias ...string) string {
|
||||
expression := Concat(sum, column, parenClose)
|
||||
if len(alias) == 0 {
|
||||
return expression
|
||||
}
|
||||
return As(expression, alias[0])
|
||||
}
|
||||
|
||||
// Avg creates an AVG expression with optional alias
|
||||
// Example: Avg("score") → "AVG(score)"
|
||||
// Example: Avg("score", "average") → "AVG(score) AS average"
|
||||
func Avg(column string, alias ...string) string {
|
||||
expression := Concat(avg, column, parenClose)
|
||||
if len(alias) == 0 {
|
||||
return expression
|
||||
}
|
||||
return As(expression, alias[0])
|
||||
}
|
||||
|
||||
// Max creates a MAX expression with optional alias
|
||||
// Example: Max("price") → "MAX(price)"
|
||||
// Example: Max("price", "max_price") → "MAX(price) AS max_price"
|
||||
func Max(column string, alias ...string) string {
|
||||
expression := Concat(maxOpen, column, parenClose)
|
||||
if len(alias) == 0 {
|
||||
return expression
|
||||
}
|
||||
return As(expression, alias[0])
|
||||
}
|
||||
|
||||
// Min creates a MIN expression with optional alias
|
||||
// Example: Min("price") → "MIN(price)"
|
||||
// Example: Min("price", "min_price") → "MIN(price) AS min_price"
|
||||
func Min(column string, alias ...string) string {
|
||||
expression := Concat(minOpen, column, parenClose)
|
||||
if len(alias) == 0 {
|
||||
return expression
|
||||
}
|
||||
return As(expression, alias[0])
|
||||
}
|
||||
|
||||
// Case creates a SQL CASE expression with optional alias
|
||||
// Example: Case("WHEN status = 'active' THEN 1 ELSE 0 END", "is_active") → "CASE WHEN status = 'active' THEN 1 ELSE 0 END AS is_active"
|
||||
func Case(expression string, alias ...string) string {
|
||||
caseExpr := Concat(caseSQL, expression)
|
||||
if len(alias) == 0 {
|
||||
return caseExpr
|
||||
}
|
||||
return As(caseExpr, alias[0])
|
||||
}
|
||||
|
||||
// CaseWhen creates a complete SQL CASE expression from individual parts with proper value handling
|
||||
// Example: CaseWhen("status =", "'active'", "1", "0", "is_active") → "CASE WHEN status = 'active' THEN 1 ELSE 0 END AS is_active"
|
||||
// Example: CaseWhen("age >", "18", "'adult'", "'minor'", "age_group") → "CASE WHEN age > 18 THEN 'adult' ELSE 'minor' END AS age_group"
|
||||
func CaseWhen(conditionPart string, conditionValue, thenValue, elseValue any, alias ...string) string {
|
||||
condition := Concat(conditionPart, valueToString(conditionValue))
|
||||
expression := Concat(
|
||||
when, condition, then, valueToString(thenValue), elseSQL, valueToString(elseValue), end,
|
||||
)
|
||||
return Case(expression, alias...)
|
||||
}
|
||||
|
||||
// CaseWhenMulti creates a SQL CASE expression with multiple WHEN clauses
|
||||
// Example: CaseWhenMulti([]string{"status =", "age >"}, []any{"'active'", 18}, []any{1, "'adult'"}, 0, "result") → "CASE WHEN status = 'active' THEN 1 WHEN age > 18 THEN 'adult' ELSE 0 END AS result"
|
||||
func CaseWhenMulti(conditionParts []string, conditionValues, thenValues []any, elseValue any, alias ...string) string {
|
||||
if len(conditionParts) != len(conditionValues) || len(conditionParts) != len(thenValues) {
|
||||
return "" // or handle error
|
||||
}
|
||||
|
||||
var whenClauses []string
|
||||
for i := 0; i < len(conditionParts); i++ {
|
||||
condition := Concat(conditionParts[i], valueToString(conditionValues[i]))
|
||||
whenClause := Concat(when, condition, then, valueToString(thenValues[i]))
|
||||
whenClauses = append(whenClauses, whenClause)
|
||||
}
|
||||
|
||||
expression := Concat(
|
||||
JoinWith(space, whenClauses...),
|
||||
elseSQL,
|
||||
valueToString(elseValue),
|
||||
end,
|
||||
)
|
||||
return Case(expression, alias...)
|
||||
}
|
||||
7
vendor/github.com/olekukonko/ll/README.md
generated
vendored
7
vendor/github.com/olekukonko/ll/README.md
generated
vendored
|
|
@ -222,6 +222,13 @@ logger.Info("Slog log") // Output: level=INFO msg="Slog log" namespace=app class
|
|||
ll.Stack("Critical error") // Output: [app] ERROR: Critical error [stack=...] (see example/stack.png)
|
||||
```
|
||||
|
||||
4**General Output**
|
||||
Logs a output in structured way for inspection of public & private values.
|
||||
```go
|
||||
ll.Handler(lh.NewColorizedHandler(os.Stdout))
|
||||
ll.Output(&SomeStructWithPrivateValues{})
|
||||
```
|
||||
|
||||
#### Performance Tracking
|
||||
Measure execution time for performance analysis.
|
||||
```go
|
||||
|
|
|
|||
421
vendor/github.com/olekukonko/ll/concat.go
generated
vendored
421
vendor/github.com/olekukonko/ll/concat.go
generated
vendored
|
|
@ -1,421 +0,0 @@
|
|||
package ll
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/olekukonko/ll/lx"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRecursionDepth = 20 // Maximum depth for recursive type handling to prevent stack overflow
|
||||
nilString = "<nil>" // String representation for nil values
|
||||
unexportedString = "<?>" // String representation for unexported fields
|
||||
)
|
||||
|
||||
// Concat efficiently concatenates values without a separator using the default logger.
|
||||
// It converts each argument to a string and joins them directly, optimizing for performance
|
||||
// in logging scenarios. Thread-safe as it does not modify shared state.
|
||||
// Example:
|
||||
//
|
||||
// msg := ll.Concat("Hello", 42, true) // Returns "Hello42true"
|
||||
func Concat(args ...any) string {
|
||||
return concat(args...)
|
||||
}
|
||||
|
||||
// ConcatSpaced concatenates values with a space separator using the default logger.
|
||||
// It converts each argument to a string and joins them with spaces, suitable for log message
|
||||
// formatting. Thread-safe as it does not modify shared state.
|
||||
// Example:
|
||||
//
|
||||
// msg := ll.ConcatSpaced("Hello", 42, true) // Returns "Hello 42 true"
|
||||
func ConcatSpaced(args ...any) string {
|
||||
return concatSpaced(args...)
|
||||
}
|
||||
|
||||
// ConcatAll concatenates elements with a separator, prefix, and suffix using the default logger.
|
||||
// It combines before, main, and after arguments with the specified separator, optimizing memory
|
||||
// allocation for logging. Thread-safe as it does not modify shared state.
|
||||
// Example:
|
||||
//
|
||||
// msg := ll.ConcatAll(",", []any{"prefix"}, []any{"suffix"}, "main")
|
||||
// // Returns "prefix,main,suffix"
|
||||
func ConcatAll(sep string, before, after []any, args ...any) string {
|
||||
return concatenate(sep, before, after, args...)
|
||||
}
|
||||
|
||||
// concat efficiently concatenates values without a separator.
|
||||
// It converts each argument to a string and joins them directly, optimizing for performance
|
||||
// in logging scenarios. Used internally by Concat and other logging functions.
|
||||
// Example:
|
||||
//
|
||||
// msg := concat("Hello", 42, true) // Returns "Hello42true"
|
||||
func concat(args ...any) string {
|
||||
return concatWith("", args...)
|
||||
}
|
||||
|
||||
// concatSpaced concatenates values with a space separator.
|
||||
// It converts each argument to a string and joins them with spaces, suitable for formatting
|
||||
// log messages. Used internally by ConcatSpaced.
|
||||
// Example:
|
||||
//
|
||||
// msg := concatSpaced("Hello", 42, true) // Returns "Hello 42 true"
|
||||
func concatSpaced(args ...any) string {
|
||||
return concatWith(lx.Space, args...)
|
||||
}
|
||||
|
||||
// concatWith concatenates values with a specified separator using optimized type handling.
|
||||
// It builds a string from arguments, handling various types efficiently (strings, numbers,
|
||||
// structs, etc.), and is used by concat and concatSpaced for log message construction.
|
||||
// Thread-safe as it does not modify shared state.
|
||||
// Example:
|
||||
//
|
||||
// msg := concatWith(",", "Hello", 42, true) // Returns "Hello,42,true"
|
||||
func concatWith(sep string, args ...any) string {
|
||||
switch len(args) {
|
||||
case 0:
|
||||
return ""
|
||||
case 1:
|
||||
return concatToString(args[0])
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.Grow(concatEstimateArgs(sep, args))
|
||||
|
||||
for i, arg := range args {
|
||||
if i > 0 {
|
||||
b.WriteString(sep)
|
||||
}
|
||||
concatWriteValue(&b, arg, 0)
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// concatenate concatenates elements with separators, prefixes, and suffixes efficiently.
|
||||
// It combines before, main, and after arguments with the specified separator, optimizing
|
||||
// memory allocation for complex log message formatting. Used internally by ConcatAll.
|
||||
// Example:
|
||||
//
|
||||
// msg := concatenate(",", []any{"prefix"}, []any{"suffix"}, "main")
|
||||
// // Returns "prefix,main,suffix"
|
||||
func concatenate(sep string, before []any, after []any, args ...any) string {
|
||||
totalLen := len(before) + len(after) + len(args)
|
||||
switch totalLen {
|
||||
case 0:
|
||||
return ""
|
||||
case 1:
|
||||
switch {
|
||||
case len(before) > 0:
|
||||
return concatToString(before[0])
|
||||
case len(args) > 0:
|
||||
return concatToString(args[0])
|
||||
default:
|
||||
return concatToString(after[0])
|
||||
}
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.Grow(concatEstimateTotal(sep, before, after, args))
|
||||
|
||||
// Write before elements
|
||||
concatWriteGroup(&b, sep, before)
|
||||
|
||||
// Write main arguments
|
||||
if len(before) > 0 && len(args) > 0 {
|
||||
b.WriteString(sep)
|
||||
}
|
||||
concatWriteGroup(&b, sep, args)
|
||||
|
||||
// Write after elements
|
||||
if len(after) > 0 && (len(before) > 0 || len(args) > 0) {
|
||||
b.WriteString(sep)
|
||||
}
|
||||
concatWriteGroup(&b, sep, after)
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// concatWriteGroup writes a group of arguments to a strings.Builder with a separator.
|
||||
// It handles each argument by converting it to a string, used internally by concatenate
|
||||
// to process before, main, or after groups in log message construction.
|
||||
// Example:
|
||||
//
|
||||
// var b strings.Builder
|
||||
// concatWriteGroup(&b, ",", []any{"a", 42}) // Writes "a,42" to b
|
||||
func concatWriteGroup(b *strings.Builder, sep string, group []any) {
|
||||
for i, arg := range group {
|
||||
if i > 0 {
|
||||
b.WriteString(sep)
|
||||
}
|
||||
concatWriteValue(b, arg, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// concatToString converts a single argument to a string efficiently.
|
||||
// It handles common types (string, []byte, fmt.Stringer) with minimal overhead and falls
|
||||
// back to fmt.Sprint for other types. Used internally by concat and concatenate.
|
||||
// Example:
|
||||
//
|
||||
// s := concatToString("Hello") // Returns "Hello"
|
||||
// s := concatToString([]byte{65, 66}) // Returns "AB"
|
||||
func concatToString(arg any) string {
|
||||
switch v := arg.(type) {
|
||||
case string:
|
||||
return v
|
||||
case []byte:
|
||||
return *(*string)(unsafe.Pointer(&v))
|
||||
case fmt.Stringer:
|
||||
return v.String()
|
||||
case error:
|
||||
return v.Error()
|
||||
default:
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
}
|
||||
|
||||
// concatEstimateTotal estimates the total string length for concatenate.
|
||||
// It calculates the expected size of the concatenated string, including before, main, and
|
||||
// after arguments with separators, to preallocate the strings.Builder capacity.
|
||||
// Example:
|
||||
//
|
||||
// size := concatEstimateTotal(",", []any{"prefix"}, []any{"suffix"}, "main")
|
||||
// // Returns estimated length for "prefix,main,suffix"
|
||||
func concatEstimateTotal(sep string, before, after, args []any) int {
|
||||
size := 0
|
||||
if len(before) > 0 {
|
||||
size += concatEstimateArgs(sep, before)
|
||||
}
|
||||
if len(args) > 0 {
|
||||
if size > 0 {
|
||||
size += len(sep)
|
||||
}
|
||||
size += concatEstimateArgs(sep, args)
|
||||
}
|
||||
if len(after) > 0 {
|
||||
if size > 0 {
|
||||
size += len(sep)
|
||||
}
|
||||
size += concatEstimateArgs(sep, after)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// concatEstimateArgs estimates the string length for a group of arguments.
|
||||
// It sums the estimated sizes of each argument plus separators, used by concatEstimateTotal
|
||||
// and concatWith to optimize memory allocation for log message construction.
|
||||
// Example:
|
||||
//
|
||||
// size := concatEstimateArgs(",", []any{"hello", 42}) // Returns estimated length for "hello,42"
|
||||
func concatEstimateArgs(sep string, args []any) int {
|
||||
if len(args) == 0 {
|
||||
return 0
|
||||
}
|
||||
size := len(sep) * (len(args) - 1)
|
||||
for _, arg := range args {
|
||||
size += concatEstimateSize(arg)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// concatEstimateSize estimates the string length for a single argument.
|
||||
// It provides size estimates for various types (strings, numbers, booleans, etc.) to
|
||||
// optimize strings.Builder capacity allocation in logging functions.
|
||||
// Example:
|
||||
//
|
||||
// size := concatEstimateSize("hello") // Returns 5
|
||||
// size := concatEstimateSize(42) // Returns ~2
|
||||
func concatEstimateSize(arg any) int {
|
||||
switch v := arg.(type) {
|
||||
case string:
|
||||
return len(v)
|
||||
case []byte:
|
||||
return len(v)
|
||||
case int:
|
||||
return concatNumLen(int64(v))
|
||||
case int64:
|
||||
return concatNumLen(v)
|
||||
case int32:
|
||||
return concatNumLen(int64(v))
|
||||
case int16:
|
||||
return concatNumLen(int64(v))
|
||||
case int8:
|
||||
return concatNumLen(int64(v))
|
||||
case uint:
|
||||
return concatNumLen(uint64(v))
|
||||
case uint64:
|
||||
return concatNumLen(v)
|
||||
case uint32:
|
||||
return concatNumLen(uint64(v))
|
||||
case uint16:
|
||||
return concatNumLen(uint64(v))
|
||||
case uint8:
|
||||
return concatNumLen(uint64(v))
|
||||
case float64:
|
||||
return 24 // Max digits for float64
|
||||
case float32:
|
||||
return 16 // Max digits for float32
|
||||
case bool:
|
||||
if v {
|
||||
return 4 // "true"
|
||||
}
|
||||
return 5 // "false"
|
||||
case fmt.Stringer:
|
||||
return 16 // Conservative estimate
|
||||
default:
|
||||
return 16 // Default estimate
|
||||
}
|
||||
}
|
||||
|
||||
// concatNumLen estimates the string length for a signed or unsigned integer.
|
||||
// It returns a conservative estimate (20 digits) for int64 or uint64 values, including
|
||||
// a sign for negative numbers, used by concatEstimateSize for memory allocation.
|
||||
// Example:
|
||||
//
|
||||
// size := concatNumLen(int64(-123)) // Returns 20
|
||||
// size := concatNumLen(uint64(123)) // Returns 20
|
||||
func concatNumLen[T int64 | uint64](v T) int {
|
||||
if v < 0 {
|
||||
return 20 // Max digits for int64 + sign
|
||||
}
|
||||
return 20 // Max digits for uint64
|
||||
}
|
||||
|
||||
// concatWriteValue writes a formatted value to a strings.Builder with recursion depth tracking.
|
||||
// It handles various types (strings, numbers, structs, slices, etc.) and prevents infinite
|
||||
// recursion by limiting depth. Used internally by concatWith and concatWriteGroup for log
|
||||
// message formatting.
|
||||
// Example:
|
||||
//
|
||||
// var b strings.Builder
|
||||
// concatWriteValue(&b, "hello", 0) // Writes "hello" to b
|
||||
// concatWriteValue(&b, []int{1, 2}, 0) // Writes "[1,2]" to b
|
||||
func concatWriteValue(b *strings.Builder, arg any, depth int) {
|
||||
if depth > maxRecursionDepth {
|
||||
b.WriteString("...")
|
||||
return
|
||||
}
|
||||
|
||||
if arg == nil {
|
||||
b.WriteString(nilString)
|
||||
return
|
||||
}
|
||||
|
||||
if s, ok := arg.(fmt.Stringer); ok {
|
||||
b.WriteString(s.String())
|
||||
return
|
||||
}
|
||||
|
||||
switch v := arg.(type) {
|
||||
case string:
|
||||
b.WriteString(v)
|
||||
case []byte:
|
||||
b.Write(v)
|
||||
case int:
|
||||
b.WriteString(strconv.FormatInt(int64(v), 10))
|
||||
case int64:
|
||||
b.WriteString(strconv.FormatInt(v, 10))
|
||||
case int32:
|
||||
b.WriteString(strconv.FormatInt(int64(v), 10))
|
||||
case int16:
|
||||
b.WriteString(strconv.FormatInt(int64(v), 10))
|
||||
case int8:
|
||||
b.WriteString(strconv.FormatInt(int64(v), 10))
|
||||
case uint:
|
||||
b.WriteString(strconv.FormatUint(uint64(v), 10))
|
||||
case uint64:
|
||||
b.WriteString(strconv.FormatUint(v, 10))
|
||||
case uint32:
|
||||
b.WriteString(strconv.FormatUint(uint64(v), 10))
|
||||
case uint16:
|
||||
b.WriteString(strconv.FormatUint(uint64(v), 10))
|
||||
case uint8:
|
||||
b.WriteString(strconv.FormatUint(uint64(v), 10))
|
||||
case float64:
|
||||
b.WriteString(strconv.FormatFloat(v, 'f', -1, 64))
|
||||
case float32:
|
||||
b.WriteString(strconv.FormatFloat(float64(v), 'f', -1, 32))
|
||||
case bool:
|
||||
if v {
|
||||
b.WriteString("true")
|
||||
} else {
|
||||
b.WriteString("false")
|
||||
}
|
||||
default:
|
||||
val := reflect.ValueOf(arg)
|
||||
if val.Kind() == reflect.Ptr {
|
||||
if val.IsNil() {
|
||||
b.WriteString(nilString)
|
||||
return
|
||||
}
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
concatFormatSlice(b, val, depth)
|
||||
case reflect.Struct:
|
||||
concatFormatStruct(b, val, depth)
|
||||
default:
|
||||
fmt.Fprint(b, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// concatFormatSlice formats a slice or array for logging.
|
||||
// It writes the elements in a bracketed, comma-separated format, handling nested types
|
||||
// recursively with depth tracking. Used internally by concatWriteValue for log message formatting.
|
||||
// Example:
|
||||
//
|
||||
// var b strings.Builder
|
||||
// val := reflect.ValueOf([]int{1, 2})
|
||||
// concatFormatSlice(&b, val, 0) // Writes "[1,2]" to b
|
||||
func concatFormatSlice(b *strings.Builder, val reflect.Value, depth int) {
|
||||
b.WriteByte('[')
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
if i > 0 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
concatWriteValue(b, val.Index(i).Interface(), depth+1)
|
||||
}
|
||||
b.WriteByte(']')
|
||||
}
|
||||
|
||||
// concatFormatStruct formats a struct for logging.
|
||||
// It writes the struct’s exported fields in a bracketed, name:value format, handling nested
|
||||
// types recursively with depth tracking. Unexported fields are represented as "<?>".
|
||||
// Used internally by concatWriteValue for log message formatting.
|
||||
// Example:
|
||||
//
|
||||
// var b strings.Builder
|
||||
// val := reflect.ValueOf(struct{ Name string }{Name: "test"})
|
||||
// concatFormatStruct(&b, val, 0) // Writes "[Name:test]" to b
|
||||
func concatFormatStruct(b *strings.Builder, val reflect.Value, depth int) {
|
||||
typ := val.Type()
|
||||
b.WriteByte('[')
|
||||
|
||||
first := true
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
fieldValue := val.Field(i)
|
||||
|
||||
if !first {
|
||||
b.WriteString("; ")
|
||||
}
|
||||
first = false
|
||||
|
||||
b.WriteString(field.Name)
|
||||
b.WriteByte(':')
|
||||
|
||||
if !fieldValue.CanInterface() {
|
||||
b.WriteString(unexportedString)
|
||||
continue
|
||||
}
|
||||
|
||||
concatWriteValue(b, fieldValue.Interface(), depth+1)
|
||||
}
|
||||
|
||||
b.WriteByte(']')
|
||||
}
|
||||
11
vendor/github.com/olekukonko/ll/field.go
generated
vendored
11
vendor/github.com/olekukonko/ll/field.go
generated
vendored
|
|
@ -2,6 +2,7 @@ package ll
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/olekukonko/cat"
|
||||
"github.com/olekukonko/ll/lx"
|
||||
"os"
|
||||
"strings"
|
||||
|
|
@ -50,7 +51,7 @@ func (fb *FieldBuilder) Info(args ...any) {
|
|||
return
|
||||
}
|
||||
// Log at Info level with the builder’s fields, no stack trace
|
||||
fb.logger.log(lx.LevelInfo, lx.ClassText, concatSpaced(args...), fb.fields, false)
|
||||
fb.logger.log(lx.LevelInfo, lx.ClassText, cat.Space(args...), fb.fields, false)
|
||||
}
|
||||
|
||||
// Infof logs a message at Info level with the builder’s fields.
|
||||
|
|
@ -85,7 +86,7 @@ func (fb *FieldBuilder) Debug(args ...any) {
|
|||
return
|
||||
}
|
||||
// Log at Debug level with the builder’s fields, no stack trace
|
||||
fb.logger.log(lx.LevelDebug, lx.ClassText, concatSpaced(args...), fb.fields, false)
|
||||
fb.logger.log(lx.LevelDebug, lx.ClassText, cat.Space(args...), fb.fields, false)
|
||||
}
|
||||
|
||||
// Debugf logs a message at Debug level with the builder’s fields.
|
||||
|
|
@ -120,7 +121,7 @@ func (fb *FieldBuilder) Warn(args ...any) {
|
|||
return
|
||||
}
|
||||
// Log at Warn level with the builder’s fields, no stack trace
|
||||
fb.logger.log(lx.LevelWarn, lx.ClassText, concatSpaced(args...), fb.fields, false)
|
||||
fb.logger.log(lx.LevelWarn, lx.ClassText, cat.Space(args...), fb.fields, false)
|
||||
}
|
||||
|
||||
// Warnf logs a message at Warn level with the builder’s fields.
|
||||
|
|
@ -154,7 +155,7 @@ func (fb *FieldBuilder) Error(args ...any) {
|
|||
return
|
||||
}
|
||||
// Log at Error level with the builder’s fields, no stack trace
|
||||
fb.logger.log(lx.LevelError, lx.ClassText, concatSpaced(args...), fb.fields, false)
|
||||
fb.logger.log(lx.LevelError, lx.ClassText, cat.Space(args...), fb.fields, false)
|
||||
}
|
||||
|
||||
// Errorf logs a message at Error level with the builder’s fields.
|
||||
|
|
@ -188,7 +189,7 @@ func (fb *FieldBuilder) Stack(args ...any) {
|
|||
return
|
||||
}
|
||||
// Log at Error level with the builder’s fields and a stack trace
|
||||
fb.logger.log(lx.LevelError, lx.ClassText, concatSpaced(args...), fb.fields, true)
|
||||
fb.logger.log(lx.LevelError, lx.ClassText, cat.Space(args...), fb.fields, true)
|
||||
}
|
||||
|
||||
// Stackf logs a message at Error level with a stack trace and the builder’s fields.
|
||||
|
|
|
|||
23
vendor/github.com/olekukonko/ll/global.go
generated
vendored
23
vendor/github.com/olekukonko/ll/global.go
generated
vendored
|
|
@ -1,11 +1,12 @@
|
|||
package ll
|
||||
|
||||
import (
|
||||
"github.com/olekukonko/ll/lh"
|
||||
"github.com/olekukonko/ll/lx"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/olekukonko/ll/lh"
|
||||
"github.com/olekukonko/ll/lx"
|
||||
)
|
||||
|
||||
// defaultLogger is the global logger instance for package-level logging functions.
|
||||
|
|
@ -468,13 +469,7 @@ func Len() int64 {
|
|||
// duration := ll.Measure(func() { time.Sleep(time.Millisecond) })
|
||||
// // Output: [] INFO: function executed [duration=~1ms]
|
||||
func Measure(fns ...func()) time.Duration {
|
||||
start := time.Now()
|
||||
for _, fn := range fns {
|
||||
fn()
|
||||
}
|
||||
duration := time.Since(start)
|
||||
defaultLogger.Fields("duration", duration).Infof("function executed")
|
||||
return duration
|
||||
return defaultLogger.Measure(fns...)
|
||||
}
|
||||
|
||||
// Benchmark logs the duration since a start time at Info level using the default logger.
|
||||
|
|
@ -486,7 +481,7 @@ func Measure(fns ...func()) time.Duration {
|
|||
// time.Sleep(time.Millisecond)
|
||||
// ll.Benchmark(start) // Output: [] INFO: benchmark [start=... end=... duration=...]
|
||||
func Benchmark(start time.Time) {
|
||||
defaultLogger.Fields("start", start, "end", time.Now(), "duration", time.Now().Sub(start)).Infof("benchmark")
|
||||
defaultLogger.Benchmark(start)
|
||||
}
|
||||
|
||||
// Clone returns a new logger with the same configuration as the default logger.
|
||||
|
|
@ -657,3 +652,11 @@ func Mark(names ...string) {
|
|||
defaultLogger.mark(2, names...)
|
||||
|
||||
}
|
||||
|
||||
// Output logs data in a human-readable JSON format at Info level, including caller file and line information.
|
||||
// It is similar to Dbg but formats the output as JSON for better readability. It is thread-safe and respects
|
||||
// the logger’s configuration (e.g., enabled, level, suspend, handler, middleware).
|
||||
func Output(values ...interface{}) {
|
||||
o := NewInspector(defaultLogger)
|
||||
o.Log(2, values...)
|
||||
}
|
||||
|
|
|
|||
239
vendor/github.com/olekukonko/ll/inspector.go
generated
vendored
Normal file
239
vendor/github.com/olekukonko/ll/inspector.go
generated
vendored
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
package ll
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/olekukonko/ll/lx"
|
||||
)
|
||||
|
||||
// Inspector is a utility for Logger that provides advanced inspection and logging of data
|
||||
// in human-readable JSON format. It uses reflection to access and represent unexported fields,
|
||||
// nested structs, embedded structs, and pointers, making it useful for debugging complex data structures.
|
||||
type Inspector struct {
|
||||
logger *Logger
|
||||
}
|
||||
|
||||
// NewInspector returns a new Inspector instance associated with the provided logger.
|
||||
func NewInspector(logger *Logger) *Inspector {
|
||||
return &Inspector{logger: logger}
|
||||
}
|
||||
|
||||
// Log outputs the given values as indented JSON at the Info level, prefixed with the caller's
|
||||
// file name and line number. It handles structs (including unexported fields, nested, and embedded),
|
||||
// pointers, errors, and other types. The skip parameter determines how many stack frames to skip
|
||||
// when identifying the caller; typically set to 2 to account for the call to Log and its wrapper.
|
||||
//
|
||||
// Example usage within a Logger method:
|
||||
//
|
||||
// o := NewInspector(l)
|
||||
// o.Log(2, someStruct) // Logs JSON representation with caller info
|
||||
func (o *Inspector) Log(skip int, values ...interface{}) {
|
||||
// Skip if logger is suspended or Info level is disabled
|
||||
if o.logger.suspend.Load() || !o.logger.shouldLog(lx.LevelInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
// Retrieve caller information for logging context
|
||||
_, file, line, ok := runtime.Caller(skip)
|
||||
if !ok {
|
||||
o.logger.log(lx.LevelError, lx.ClassText, "Inspector: Unable to parse runtime caller", nil, false)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract short filename for concise output
|
||||
shortFile := file
|
||||
if idx := strings.LastIndex(file, "/"); idx >= 0 {
|
||||
shortFile = file[idx+1:]
|
||||
}
|
||||
|
||||
// Process each value individually
|
||||
for _, value := range values {
|
||||
var jsonData []byte
|
||||
var err error
|
||||
|
||||
// Use reflection for struct types to handle unexported and nested fields
|
||||
val := reflect.ValueOf(value)
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
}
|
||||
if val.Kind() == reflect.Struct {
|
||||
valueMap := o.structToMap(val)
|
||||
jsonData, err = json.MarshalIndent(valueMap, "", " ")
|
||||
} else if errVal, ok := value.(error); ok {
|
||||
// Special handling for errors to represent them as a simple map
|
||||
value = map[string]string{"error": errVal.Error()}
|
||||
jsonData, err = json.MarshalIndent(value, "", " ")
|
||||
} else {
|
||||
// Fall back to standard JSON marshaling for non-struct types
|
||||
jsonData, err = json.MarshalIndent(value, "", " ")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
o.logger.log(lx.LevelError, lx.ClassText, fmt.Sprintf("Inspector: JSON encoding error: %v", err), nil, false)
|
||||
continue
|
||||
}
|
||||
|
||||
// Construct log message with file, line, and JSON data
|
||||
msg := fmt.Sprintf("[%s:%d] DUMP: %s", shortFile, line, string(jsonData))
|
||||
o.logger.log(lx.LevelInfo, lx.ClassText, msg, nil, false)
|
||||
}
|
||||
}
|
||||
|
||||
// structToMap recursively converts a struct's reflect.Value to a map[string]interface{}.
|
||||
// It includes unexported fields (named with parentheses), prefixes pointers with '*',
|
||||
// flattens anonymous embedded structs without json tags, and uses unsafe pointers to access
|
||||
// unexported primitive fields when reflect.CanInterface() returns false.
|
||||
func (o *Inspector) structToMap(val reflect.Value) map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
if !val.IsValid() {
|
||||
return result
|
||||
}
|
||||
|
||||
typ := val.Type()
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field := val.Field(i)
|
||||
fieldType := typ.Field(i)
|
||||
|
||||
// Determine field name: prefer json tag if present and not "-", else use struct field name
|
||||
baseName := fieldType.Name
|
||||
jsonTag := fieldType.Tag.Get("json")
|
||||
hasJsonTag := false
|
||||
if jsonTag != "" {
|
||||
if idx := strings.Index(jsonTag, ","); idx != -1 {
|
||||
jsonTag = jsonTag[:idx]
|
||||
}
|
||||
if jsonTag != "-" {
|
||||
baseName = jsonTag
|
||||
hasJsonTag = true
|
||||
}
|
||||
}
|
||||
|
||||
// Enclose unexported field names in parentheses
|
||||
fieldName := baseName
|
||||
if !fieldType.IsExported() {
|
||||
fieldName = "(" + baseName + ")"
|
||||
}
|
||||
|
||||
// Handle pointer fields
|
||||
isPtr := fieldType.Type.Kind() == reflect.Ptr
|
||||
if isPtr {
|
||||
fieldName = "*" + fieldName
|
||||
if field.IsNil() {
|
||||
result[fieldName] = nil
|
||||
continue
|
||||
}
|
||||
field = field.Elem()
|
||||
}
|
||||
|
||||
// Recurse for struct fields
|
||||
if field.Kind() == reflect.Struct {
|
||||
subMap := o.structToMap(field)
|
||||
isNested := !fieldType.Anonymous || hasJsonTag
|
||||
if isNested {
|
||||
result[fieldName] = subMap
|
||||
} else {
|
||||
// Flatten embedded struct fields into the parent map, avoiding overwrites
|
||||
for k, v := range subMap {
|
||||
if _, exists := result[k]; !exists {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle primitive fields
|
||||
if field.CanInterface() {
|
||||
result[fieldName] = field.Interface()
|
||||
} else {
|
||||
// Use unsafe access for unexported primitives
|
||||
ptr := getDataPtr(field)
|
||||
switch field.Kind() {
|
||||
case reflect.String:
|
||||
result[fieldName] = *(*string)(ptr)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
result[fieldName] = o.getIntFromUnexportedField(field)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
result[fieldName] = o.getUintFromUnexportedField(field)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
result[fieldName] = o.getFloatFromUnexportedField(field)
|
||||
case reflect.Bool:
|
||||
result[fieldName] = *(*bool)(ptr)
|
||||
default:
|
||||
result[fieldName] = fmt.Sprintf("<unexported %s>", field.Type().String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// emptyInterface represents the internal structure of an empty interface{}.
|
||||
// This is used for unsafe pointer manipulation to access unexported field data.
|
||||
type emptyInterface struct {
|
||||
typ unsafe.Pointer
|
||||
word unsafe.Pointer
|
||||
}
|
||||
|
||||
// getDataPtr returns an unsafe.Pointer to the underlying data of a reflect.Value.
|
||||
// This enables direct access to unexported fields via unsafe operations.
|
||||
func getDataPtr(v reflect.Value) unsafe.Pointer {
|
||||
return (*emptyInterface)(unsafe.Pointer(&v)).word
|
||||
}
|
||||
|
||||
// getIntFromUnexportedField extracts a signed integer value from an unexported field
|
||||
// using unsafe pointer access. It supports int, int8, int16, int32, and int64 kinds,
|
||||
// returning the value as int64. Returns 0 for unsupported kinds.
|
||||
func (o *Inspector) getIntFromUnexportedField(field reflect.Value) int64 {
|
||||
ptr := getDataPtr(field)
|
||||
switch field.Kind() {
|
||||
case reflect.Int:
|
||||
return int64(*(*int)(ptr))
|
||||
case reflect.Int8:
|
||||
return int64(*(*int8)(ptr))
|
||||
case reflect.Int16:
|
||||
return int64(*(*int16)(ptr))
|
||||
case reflect.Int32:
|
||||
return int64(*(*int32)(ptr))
|
||||
case reflect.Int64:
|
||||
return *(*int64)(ptr)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// getUintFromUnexportedField extracts an unsigned integer value from an unexported field
|
||||
// using unsafe pointer access. It supports uint, uint8, uint16, uint32, and uint64 kinds,
|
||||
// returning the value as uint64. Returns 0 for unsupported kinds.
|
||||
func (o *Inspector) getUintFromUnexportedField(field reflect.Value) uint64 {
|
||||
ptr := getDataPtr(field)
|
||||
switch field.Kind() {
|
||||
case reflect.Uint:
|
||||
return uint64(*(*uint)(ptr))
|
||||
case reflect.Uint8:
|
||||
return uint64(*(*uint8)(ptr))
|
||||
case reflect.Uint16:
|
||||
return uint64(*(*uint16)(ptr))
|
||||
case reflect.Uint32:
|
||||
return uint64(*(*uint32)(ptr))
|
||||
case reflect.Uint64:
|
||||
return *(*uint64)(ptr)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// getFloatFromUnexportedField extracts a floating-point value from an unexported field
|
||||
// using unsafe pointer access. It supports float32 and float64 kinds, returning the value
|
||||
// as float64. Returns 0 for unsupported kinds.
|
||||
func (o *Inspector) getFloatFromUnexportedField(field reflect.Value) float64 {
|
||||
ptr := getDataPtr(field)
|
||||
switch field.Kind() {
|
||||
case reflect.Float32:
|
||||
return float64(*(*float32)(ptr))
|
||||
case reflect.Float64:
|
||||
return *(*float64)(ptr)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
9
vendor/github.com/olekukonko/ll/lh/colorized.go
generated
vendored
9
vendor/github.com/olekukonko/ll/lh/colorized.go
generated
vendored
|
|
@ -2,12 +2,14 @@ package lh
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/olekukonko/ll/lx"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/olekukonko/ll/lx"
|
||||
)
|
||||
|
||||
// Palette defines ANSI color codes for various log components.
|
||||
|
|
@ -81,6 +83,7 @@ type ColorizedHandler struct {
|
|||
palette Palette // Color scheme for formatting
|
||||
showTime bool // Whether to display timestamps
|
||||
timeFormat string // Format for timestamps (defaults to time.RFC3339)
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// ColorOption defines a configuration function for ColorizedHandler.
|
||||
|
|
@ -130,6 +133,10 @@ func NewColorizedHandler(w io.Writer, opts ...ColorOption) *ColorizedHandler {
|
|||
//
|
||||
// handler.Handle(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Writes colored output
|
||||
func (h *ColorizedHandler) Handle(e *lx.Entry) error {
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
switch e.Class {
|
||||
case lx.ClassDump:
|
||||
// Handle hex dump entries
|
||||
|
|
|
|||
8
vendor/github.com/olekukonko/ll/lh/text.go
generated
vendored
8
vendor/github.com/olekukonko/ll/lh/text.go
generated
vendored
|
|
@ -2,11 +2,13 @@ package lh
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/olekukonko/ll/lx"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/olekukonko/ll/lx"
|
||||
)
|
||||
|
||||
// TextHandler is a handler that outputs log entries as plain text.
|
||||
|
|
@ -17,6 +19,7 @@ type TextHandler struct {
|
|||
w io.Writer // Destination for formatted log output
|
||||
showTime bool // Whether to display timestamps
|
||||
timeFormat string // Format for timestamps (defaults to time.RFC3339)
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewTextHandler creates a new TextHandler writing to the specified writer.
|
||||
|
|
@ -55,6 +58,9 @@ func (h *TextHandler) Timestamped(enable bool, format ...string) {
|
|||
//
|
||||
// handler.Handle(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Writes "INFO: test"
|
||||
func (h *TextHandler) Handle(e *lx.Entry) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
// Special handling for dump output
|
||||
if e.Class == lx.ClassDump {
|
||||
return h.handleDumpOutput(e)
|
||||
|
|
|
|||
113
vendor/github.com/olekukonko/ll/ll.go
generated
vendored
113
vendor/github.com/olekukonko/ll/ll.go
generated
vendored
|
|
@ -5,8 +5,6 @@ import (
|
|||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/olekukonko/ll/lh"
|
||||
"github.com/olekukonko/ll/lx"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
|
|
@ -16,6 +14,10 @@ import (
|
|||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/olekukonko/cat"
|
||||
"github.com/olekukonko/ll/lh"
|
||||
"github.com/olekukonko/ll/lx"
|
||||
)
|
||||
|
||||
// Logger manages logging configuration and behavior, encapsulating state such as enablement,
|
||||
|
|
@ -24,7 +26,7 @@ import (
|
|||
type Logger struct {
|
||||
mu sync.RWMutex // Guards concurrent access to fields
|
||||
enabled bool // Determines if logging is enabled
|
||||
suspend bool // uses suspend path for most actions eg. skipping namespace checks
|
||||
suspend atomic.Bool // uses suspend path for most actions eg. skipping namespace checks
|
||||
level lx.LevelType // Minimum log level (e.g., Debug, Info, Warn, Error)
|
||||
namespaces *lx.Namespace // Manages namespace enable/disable states
|
||||
currentPath string // Current namespace path (e.g., "parent/child")
|
||||
|
|
@ -97,7 +99,11 @@ func (l *Logger) AddContext(key string, value interface{}) *Logger {
|
|||
// logger.Benchmark(start) // Output: [app] INFO: benchmark [start=... end=... duration=...]
|
||||
func (l *Logger) Benchmark(start time.Time) time.Duration {
|
||||
duration := time.Since(start)
|
||||
l.Fields("start", start, "end", time.Now(), "duration", duration).Infof("benchmark")
|
||||
l.Fields(
|
||||
"duration_ms", duration.Milliseconds(),
|
||||
"duration", duration.String(),
|
||||
).Infof("benchmark completed")
|
||||
|
||||
return duration
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +226,7 @@ func (l *Logger) Dbg(values ...interface{}) {
|
|||
// logger.Debug("Debugging") // Output: [app] DEBUG: Debugging
|
||||
func (l *Logger) Debug(args ...any) {
|
||||
// check if suspended
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -229,7 +235,7 @@ func (l *Logger) Debug(args ...any) {
|
|||
return
|
||||
}
|
||||
|
||||
l.log(lx.LevelDebug, lx.ClassText, concatSpaced(args...), nil, false)
|
||||
l.log(lx.LevelDebug, lx.ClassText, cat.Space(args...), nil, false)
|
||||
}
|
||||
|
||||
// Debugf logs a formatted message at Debug level, delegating to Debug. It is thread-safe.
|
||||
|
|
@ -239,7 +245,7 @@ func (l *Logger) Debug(args ...any) {
|
|||
// logger.Debugf("Debug %s", "message") // Output: [app] DEBUG: Debug message
|
||||
func (l *Logger) Debugf(format string, args ...any) {
|
||||
// check if suspended
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -344,6 +350,21 @@ func (l *Logger) Dump(values ...interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
// Output logs data in a human-readable JSON format at Info level, including caller file and line information.
|
||||
// It is similar to Dbg but formats the output as JSON for better readability. It is thread-safe and respects
|
||||
// the logger's configuration (e.g., enabled, level, suspend, handler, middleware).
|
||||
// Example:
|
||||
//
|
||||
// logger := New("app").Enable()
|
||||
// x := map[string]int{"key": 42}
|
||||
// logger.Output(x) // Output: [app] INFO: [file.go:123] JSON: {"key": 42}
|
||||
//
|
||||
// Logger method to provide access to Output functionality
|
||||
func (l *Logger) Output(values ...interface{}) {
|
||||
o := NewInspector(l)
|
||||
o.Log(2, values...)
|
||||
}
|
||||
|
||||
// Enable activates logging, allowing logs to be emitted if other conditions (e.g., level,
|
||||
// namespace) are met. It is thread-safe using a write lock and returns the logger for chaining.
|
||||
// Example:
|
||||
|
|
@ -432,7 +453,7 @@ func (l *Logger) Err(errs ...error) {
|
|||
// logger.Error("Error occurred") // Output: [app] ERROR: Error occurred
|
||||
func (l *Logger) Error(args ...any) {
|
||||
// check if suspended
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -440,7 +461,7 @@ func (l *Logger) Error(args ...any) {
|
|||
if !l.shouldLog(lx.LevelError) {
|
||||
return
|
||||
}
|
||||
l.log(lx.LevelError, lx.ClassText, concatSpaced(args...), nil, false)
|
||||
l.log(lx.LevelError, lx.ClassText, cat.Space(args...), nil, false)
|
||||
}
|
||||
|
||||
// Errorf logs a formatted message at Error level, delegating to Error. It is thread-safe.
|
||||
|
|
@ -450,7 +471,7 @@ func (l *Logger) Error(args ...any) {
|
|||
// logger.Errorf("Error %s", "occurred") // Output: [app] ERROR: Error occurred
|
||||
func (l *Logger) Errorf(format string, args ...any) {
|
||||
// check if suspended
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -465,7 +486,7 @@ func (l *Logger) Errorf(format string, args ...any) {
|
|||
// logger.Fatal("Fatal error") // Output: [app] ERROR: Fatal error [stack=...], then exits
|
||||
func (l *Logger) Fatal(args ...any) {
|
||||
// check if suspended
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -474,7 +495,7 @@ func (l *Logger) Fatal(args ...any) {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
l.log(lx.LevelError, lx.ClassText, concatSpaced(args...), nil, true)
|
||||
l.log(lx.LevelError, lx.ClassText, cat.Space(args...), nil, true)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
|
@ -486,7 +507,7 @@ func (l *Logger) Fatal(args ...any) {
|
|||
// logger.Fatalf("Fatal %s", "error") // Output: [app] ERROR: Fatal error [stack=...], then exits
|
||||
func (l *Logger) Fatalf(format string, args ...any) {
|
||||
// check if suspended
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -503,7 +524,7 @@ func (l *Logger) Field(fields map[string]interface{}) *FieldBuilder {
|
|||
fb := &FieldBuilder{logger: l, fields: make(map[string]interface{})}
|
||||
|
||||
// check if suspended
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return fb
|
||||
}
|
||||
|
||||
|
|
@ -524,7 +545,7 @@ func (l *Logger) Field(fields map[string]interface{}) *FieldBuilder {
|
|||
func (l *Logger) Fields(pairs ...any) *FieldBuilder {
|
||||
fb := &FieldBuilder{logger: l, fields: make(map[string]interface{})}
|
||||
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return fb
|
||||
}
|
||||
|
||||
|
|
@ -650,7 +671,7 @@ func (l *Logger) Indent(depth int) *Logger {
|
|||
// logger := New("app").Enable().Style(lx.NestedPath)
|
||||
// logger.Info("Started") // Output: [app]: INFO: Started
|
||||
func (l *Logger) Info(args ...any) {
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -658,7 +679,7 @@ func (l *Logger) Info(args ...any) {
|
|||
return
|
||||
}
|
||||
|
||||
l.log(lx.LevelInfo, lx.ClassText, concatSpaced(args...), nil, false)
|
||||
l.log(lx.LevelInfo, lx.ClassText, cat.Space(args...), nil, false)
|
||||
}
|
||||
|
||||
// Infof logs a formatted message at Info level, delegating to Info. It is thread-safe.
|
||||
|
|
@ -667,7 +688,7 @@ func (l *Logger) Info(args ...any) {
|
|||
// logger := New("app").Enable().Style(lx.NestedPath)
|
||||
// logger.Infof("Started %s", "now") // Output: [app]: INFO: Started now
|
||||
func (l *Logger) Infof(format string, args ...any) {
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -771,12 +792,20 @@ func (l *Logger) mark(skip int, names ...string) {
|
|||
// // Output: [app] INFO: function executed [duration=~1ms]
|
||||
func (l *Logger) Measure(fns ...func()) time.Duration {
|
||||
start := time.Now()
|
||||
// Execute all provided functions
|
||||
|
||||
for _, fn := range fns {
|
||||
fn()
|
||||
if fn != nil {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
duration := time.Since(start)
|
||||
l.Fields("duration", duration).Infof("function executed")
|
||||
l.Fields(
|
||||
"duration_ns", duration.Nanoseconds(),
|
||||
"duration", duration.String(),
|
||||
"duration_ms", fmt.Sprintf("%.3fms", float64(duration.Nanoseconds())/1e6),
|
||||
).Infof("execution completed")
|
||||
|
||||
return duration
|
||||
}
|
||||
|
||||
|
|
@ -789,7 +818,7 @@ func (l *Logger) Measure(fns ...func()) time.Duration {
|
|||
// child := parent.Namespace("child")
|
||||
// child.Info("Child log") // Output: [parent/child] INFO: Child log
|
||||
func (l *Logger) Namespace(name string) *Logger {
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return l
|
||||
}
|
||||
|
||||
|
|
@ -897,9 +926,9 @@ func (l *Logger) NamespaceEnabled(relativePath string) bool {
|
|||
// logger.Panic("Panic error") // Output: [app] ERROR: Panic error [stack=...], then panics
|
||||
func (l *Logger) Panic(args ...any) {
|
||||
// Build message by concatenating arguments with spaces
|
||||
msg := concatSpaced(args...)
|
||||
msg := cat.Space(args...)
|
||||
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
panic(msg)
|
||||
}
|
||||
|
||||
|
|
@ -942,7 +971,7 @@ func (l *Logger) Prefix(prefix string) *Logger {
|
|||
// logger := New("app").Enable()
|
||||
// logger.Print("message", "value") // Output: [app] INFO: message value
|
||||
func (l *Logger) Print(args ...any) {
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -950,7 +979,7 @@ func (l *Logger) Print(args ...any) {
|
|||
if !l.shouldLog(lx.LevelInfo) {
|
||||
return
|
||||
}
|
||||
l.log(lx.LevelNone, lx.ClassRaw, concatSpaced(args...), nil, false)
|
||||
l.log(lx.LevelNone, lx.ClassRaw, cat.Space(args...), nil, false)
|
||||
}
|
||||
|
||||
// Println logs a message at Info level without format specifiers, minimizing allocations
|
||||
|
|
@ -960,7 +989,7 @@ func (l *Logger) Print(args ...any) {
|
|||
// logger := New("app").Enable()
|
||||
// logger.Println("message", "value") // Output: [app] INFO: message value
|
||||
func (l *Logger) Println(args ...any) {
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -968,7 +997,7 @@ func (l *Logger) Println(args ...any) {
|
|||
if !l.shouldLog(lx.LevelInfo) {
|
||||
return
|
||||
}
|
||||
l.log(lx.LevelNone, lx.ClassRaw, concatenate(lx.Space, nil, []any{lx.Newline}, args...), nil, false)
|
||||
l.log(lx.LevelNone, lx.ClassRaw, cat.SuffixWith(lx.Space, lx.Newline, args...), nil, false)
|
||||
}
|
||||
|
||||
// Printf logs a formatted message at Info level, delegating to Print. It is thread-safe.
|
||||
|
|
@ -977,7 +1006,7 @@ func (l *Logger) Println(args ...any) {
|
|||
// logger := New("app").Enable()
|
||||
// logger.Printf("Message %s", "value") // Output: [app] INFO: Message value
|
||||
func (l *Logger) Printf(format string, args ...any) {
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1004,9 +1033,7 @@ func (l *Logger) Remove(m *Middleware) {
|
|||
// logger.Resume()
|
||||
// logger.Info("Resumed") // Output: [app] INFO: Resumed
|
||||
func (l *Logger) Resume() *Logger {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.suspend = false // Clear suspend flag to resume logging
|
||||
l.suspend.Store(false)
|
||||
return l
|
||||
}
|
||||
|
||||
|
|
@ -1032,9 +1059,7 @@ func (l *Logger) Separator(separator string) *Logger {
|
|||
// logger.Suspend()
|
||||
// logger.Info("Ignored") // No output
|
||||
func (l *Logger) Suspend() *Logger {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.suspend = true // Set suspend flag to pause logging
|
||||
l.suspend.Store(true)
|
||||
return l
|
||||
}
|
||||
|
||||
|
|
@ -1047,9 +1072,7 @@ func (l *Logger) Suspend() *Logger {
|
|||
// fmt.Println("Logging is suspended") // Prints message
|
||||
// }
|
||||
func (l *Logger) Suspended() bool {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.suspend // Return current suspend state
|
||||
return l.suspend.Load()
|
||||
}
|
||||
|
||||
// Stack logs messages at Error level with a stack trace for each provided argument.
|
||||
|
|
@ -1059,7 +1082,7 @@ func (l *Logger) Suspended() bool {
|
|||
// logger := New("app").Enable()
|
||||
// logger.Stack("Critical error") // Output: [app] ERROR: Critical error [stack=...]
|
||||
func (l *Logger) Stack(args ...any) {
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1069,7 +1092,7 @@ func (l *Logger) Stack(args ...any) {
|
|||
}
|
||||
|
||||
for _, arg := range args {
|
||||
l.log(lx.LevelError, lx.ClassText, concat(arg), nil, true)
|
||||
l.log(lx.LevelError, lx.ClassText, cat.Concat(arg), nil, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1080,7 +1103,7 @@ func (l *Logger) Stack(args ...any) {
|
|||
// logger := New("app").Enable()
|
||||
// logger.Stackf("Critical %s", "error") // Output: [app] ERROR: Critical error [stack=...]
|
||||
func (l *Logger) Stackf(format string, args ...any) {
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1171,7 +1194,7 @@ func (l *Logger) Use(fn lx.Handler) *Middleware {
|
|||
// logger := New("app").Enable()
|
||||
// logger.Warn("Warning") // Output: [app] WARN: Warning
|
||||
func (l *Logger) Warn(args ...any) {
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1180,7 +1203,7 @@ func (l *Logger) Warn(args ...any) {
|
|||
return
|
||||
}
|
||||
|
||||
l.log(lx.LevelWarn, lx.ClassText, concatSpaced(args...), nil, false)
|
||||
l.log(lx.LevelWarn, lx.ClassText, cat.Space(args...), nil, false)
|
||||
}
|
||||
|
||||
// Warnf logs a formatted message at Warn level, delegating to Warn. It is thread-safe.
|
||||
|
|
@ -1189,7 +1212,7 @@ func (l *Logger) Warn(args ...any) {
|
|||
// logger := New("app").Enable()
|
||||
// logger.Warnf("Warning %s", "issued") // Output: [app] WARN: Warning issued
|
||||
func (l *Logger) Warnf(format string, args ...any) {
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1363,7 +1386,7 @@ func (l *Logger) shouldLog(level lx.LevelType) bool {
|
|||
}
|
||||
|
||||
// check for suspend mode
|
||||
if l.suspend {
|
||||
if l.suspend.Load() {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
|||
93
vendor/github.com/olekukonko/ll/lx/lx.go
generated
vendored
93
vendor/github.com/olekukonko/ll/lx/lx.go
generated
vendored
|
|
@ -1,6 +1,7 @@
|
|||
package lx
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -31,11 +32,28 @@ const (
|
|||
// These constants define the severity levels for log messages, used to filter logs based
|
||||
// on the logger’s minimum level. They are ordered to allow comparison (e.g., LevelDebug < LevelWarn).
|
||||
const (
|
||||
LevelNone LevelType = iota // Debug level for detailed diagnostic information
|
||||
LevelInfo // Info level for general operational messages
|
||||
LevelWarn // Warn level for warning conditions
|
||||
LevelError // Error level for error conditions requiring attention
|
||||
LevelDebug // None level for logs without a specific severity (e.g., raw output)
|
||||
LevelNone LevelType = iota // Debug level for detailed diagnostic information
|
||||
LevelInfo // Info level for general operational messages
|
||||
LevelWarn // Warn level for warning conditions
|
||||
LevelError // Error level for error conditions requiring attention
|
||||
LevelDebug // None level for logs without a specific severity (e.g., raw output)
|
||||
LevelUnknown // None level for logs without a specific severity (e.g., raw output)
|
||||
)
|
||||
|
||||
// String constants for each level
|
||||
const (
|
||||
DebugString = "DEBUG"
|
||||
InfoString = "INFO"
|
||||
WarnString = "WARN"
|
||||
ErrorString = "ERROR"
|
||||
NoneString = "NONE"
|
||||
UnknownString = "UNKNOWN"
|
||||
|
||||
TextString = "TEXT"
|
||||
JSONString = "JSON"
|
||||
DumpString = "DUMP"
|
||||
SpecialString = "SPECIAL"
|
||||
RawString = "RAW"
|
||||
)
|
||||
|
||||
// Log class constants, defining the type of log entry.
|
||||
|
|
@ -47,6 +65,7 @@ const (
|
|||
ClassDump // Dump entries for hex/ASCII dumps
|
||||
ClassSpecial // Special entries for custom or non-standard logs
|
||||
ClassRaw // Raw entries for unformatted output
|
||||
ClassUnknown // Raw entries for unformatted output
|
||||
)
|
||||
|
||||
// Namespace style constants.
|
||||
|
|
@ -72,17 +91,37 @@ type LevelType int
|
|||
func (l LevelType) String() string {
|
||||
switch l {
|
||||
case LevelDebug:
|
||||
return "DEBUG"
|
||||
return DebugString
|
||||
case LevelInfo:
|
||||
return "INFO"
|
||||
return InfoString
|
||||
case LevelWarn:
|
||||
return "WARN"
|
||||
return WarnString
|
||||
case LevelError:
|
||||
return "ERROR"
|
||||
return ErrorString
|
||||
case LevelNone:
|
||||
return "NONE"
|
||||
return NoneString
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
return UnknownString
|
||||
}
|
||||
}
|
||||
|
||||
// LevelParse converts a string to its corresponding LevelType.
|
||||
// It parses a string (case-insensitive) and returns the corresponding LevelType, defaulting to
|
||||
// LevelUnknown for unrecognized strings. Supports "WARNING" as an alias for "WARN".
|
||||
func LevelParse(s string) LevelType {
|
||||
switch strings.ToUpper(s) {
|
||||
case DebugString:
|
||||
return LevelDebug
|
||||
case InfoString:
|
||||
return LevelInfo
|
||||
case WarnString, "WARNING": // Allow both "WARN" and "WARNING"
|
||||
return LevelWarn
|
||||
case ErrorString:
|
||||
return LevelError
|
||||
case NoneString:
|
||||
return LevelNone
|
||||
default:
|
||||
return LevelUnknown
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -149,16 +188,36 @@ type ClassType int
|
|||
func (t ClassType) String() string {
|
||||
switch t {
|
||||
case ClassText:
|
||||
return "TEST" // Note: Likely a typo, should be "TEXT"
|
||||
return TextString
|
||||
case ClassJSON:
|
||||
return "JSON"
|
||||
return JSONString
|
||||
case ClassDump:
|
||||
return "DUMP"
|
||||
return DumpString
|
||||
case ClassSpecial:
|
||||
return "SPECIAL"
|
||||
return SpecialString
|
||||
case ClassRaw:
|
||||
return "RAW"
|
||||
return RawString
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
return UnknownString
|
||||
}
|
||||
}
|
||||
|
||||
// ParseClass converts a string to its corresponding ClassType.
|
||||
// It parses a string (case-insensitive) and returns the corresponding ClassType, defaulting to
|
||||
// ClassUnknown for unrecognized strings.
|
||||
func ParseClass(s string) ClassType {
|
||||
switch strings.ToUpper(s) {
|
||||
case TextString:
|
||||
return ClassText
|
||||
case JSONString:
|
||||
return ClassJSON
|
||||
case DumpString:
|
||||
return ClassDump
|
||||
case SpecialString:
|
||||
return ClassSpecial
|
||||
case RawString:
|
||||
return ClassRaw
|
||||
default:
|
||||
return ClassUnknown
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
vendor/github.com/olekukonko/tablewriter/.golangci.yml
generated
vendored
Normal file
55
vendor/github.com/olekukonko/tablewriter/.golangci.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# See for configurations: https://golangci-lint.run/usage/configuration/
|
||||
version: 2
|
||||
# See: https://golangci-lint.run/usage/formatters/
|
||||
formatters:
|
||||
default: none
|
||||
enable:
|
||||
- gofmt # https://pkg.go.dev/cmd/gofmt
|
||||
- gofumpt # https://github.com/mvdan/gofumpt
|
||||
|
||||
settings:
|
||||
gofmt:
|
||||
simplify: true # Simplify code: gofmt with `-s` option.
|
||||
|
||||
gofumpt:
|
||||
# Module path which contains the source code being formatted.
|
||||
# Default: ""
|
||||
module-path: github.com/olekukonko/tablewriter # Should match with module in go.mod
|
||||
# Choose whether to use the extra rules.
|
||||
# Default: false
|
||||
extra-rules: true
|
||||
|
||||
# See: https://golangci-lint.run/usage/linters/
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
- staticcheck
|
||||
- govet
|
||||
- gocritic
|
||||
# - unused # TODO: There are many unused functions, should I directly remove those ?
|
||||
- ineffassign
|
||||
- unconvert
|
||||
- mirror
|
||||
- usestdlibvars
|
||||
- loggercheck
|
||||
- exptostd
|
||||
- godot
|
||||
- perfsprint
|
||||
|
||||
# See: https://golangci-lint.run/usage/false-positives/
|
||||
exclusion:
|
||||
# paths:
|
||||
# rules:
|
||||
|
||||
settings:
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
- "-SA1019" # disabled because it warns about deprecated: kept for compatibility will be removed soon
|
||||
- "-ST1019" # disabled because it warns about deprecated: kept for compatibility will be removed soon
|
||||
- "-ST1021" # disabled because it warns to have comment on exported packages
|
||||
- "-ST1000" # disabled because it warns to have comment on exported functions
|
||||
- "-ST1020" # disabled because it warns to have at least one file in a package should have a package comment
|
||||
|
||||
godot:
|
||||
period: false
|
||||
246
vendor/github.com/olekukonko/tablewriter/MIGRATION.md
generated
vendored
246
vendor/github.com/olekukonko/tablewriter/MIGRATION.md
generated
vendored
|
|
@ -416,6 +416,244 @@ func main() {
|
|||
</table>
|
||||
```
|
||||
|
||||
|
||||
#### Custom Invoice Renderer
|
||||
|
||||
```go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/ll"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
)
|
||||
|
||||
// InvoiceRenderer implements tw.Renderer for a basic invoice style.
|
||||
type InvoiceRenderer struct {
|
||||
writer io.Writer
|
||||
logger *ll.Logger
|
||||
rendition tw.Rendition
|
||||
}
|
||||
|
||||
func NewInvoiceRenderer() *InvoiceRenderer {
|
||||
rendition := tw.Rendition{
|
||||
Borders: tw.BorderNone,
|
||||
Symbols: tw.NewSymbols(tw.StyleNone),
|
||||
Settings: tw.Settings{Separators: tw.SeparatorsNone, Lines: tw.LinesNone},
|
||||
Streaming: false,
|
||||
}
|
||||
defaultLogger := ll.New("simple-invoice-renderer")
|
||||
return &InvoiceRenderer{logger: defaultLogger, rendition: rendition}
|
||||
}
|
||||
|
||||
func (r *InvoiceRenderer) Logger(logger *ll.Logger) {
|
||||
if logger != nil {
|
||||
r.logger = logger
|
||||
}
|
||||
}
|
||||
|
||||
func (r *InvoiceRenderer) Config() tw.Rendition {
|
||||
return r.rendition
|
||||
}
|
||||
|
||||
func (r *InvoiceRenderer) Start(w io.Writer) error {
|
||||
r.writer = w
|
||||
r.logger.Debug("InvoiceRenderer: Start")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InvoiceRenderer) formatLine(cells []string, widths tw.Mapper[int, int], cellContexts map[int]tw.CellContext) string {
|
||||
var sb strings.Builder
|
||||
numCols := 0
|
||||
if widths != nil { // Ensure widths is not nil before calling Len
|
||||
numCols = widths.Len()
|
||||
}
|
||||
|
||||
for i := 0; i < numCols; i++ {
|
||||
data := ""
|
||||
if i < len(cells) {
|
||||
data = cells[i]
|
||||
}
|
||||
|
||||
width := 0
|
||||
if widths != nil { // Check again before Get
|
||||
width = widths.Get(i)
|
||||
}
|
||||
|
||||
align := tw.AlignDefault
|
||||
if cellContexts != nil { // Check cellContexts
|
||||
if ctx, ok := cellContexts[i]; ok {
|
||||
align = ctx.Align
|
||||
}
|
||||
}
|
||||
|
||||
paddedCell := tw.Pad(data, " ", width, align)
|
||||
sb.WriteString(paddedCell)
|
||||
|
||||
if i < numCols-1 {
|
||||
sb.WriteString(" ") // Column separator
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (r *InvoiceRenderer) Header(headers [][]string, ctx tw.Formatting) {
|
||||
if r.writer == nil {
|
||||
return
|
||||
}
|
||||
r.logger.Debugf("InvoiceRenderer: Header (lines: %d)", len(headers))
|
||||
|
||||
for _, headerLineCells := range headers {
|
||||
lineStr := r.formatLine(headerLineCells, ctx.Row.Widths, ctx.Row.Current)
|
||||
fmt.Fprintln(r.writer, lineStr)
|
||||
}
|
||||
|
||||
if len(headers) > 0 {
|
||||
totalWidth := 0
|
||||
if ctx.Row.Widths != nil {
|
||||
ctx.Row.Widths.Each(func(_ int, w int) { totalWidth += w })
|
||||
if ctx.Row.Widths.Len() > 1 {
|
||||
totalWidth += (ctx.Row.Widths.Len() - 1) * 3 // Separator spaces
|
||||
}
|
||||
}
|
||||
if totalWidth > 0 {
|
||||
fmt.Fprintln(r.writer, strings.Repeat("-", totalWidth))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *InvoiceRenderer) Row(row []string, ctx tw.Formatting) {
|
||||
if r.writer == nil {
|
||||
return
|
||||
}
|
||||
r.logger.Debug("InvoiceRenderer: Row")
|
||||
lineStr := r.formatLine(row, ctx.Row.Widths, ctx.Row.Current)
|
||||
fmt.Fprintln(r.writer, lineStr)
|
||||
}
|
||||
|
||||
func (r *InvoiceRenderer) Footer(footers [][]string, ctx tw.Formatting) {
|
||||
if r.writer == nil {
|
||||
return
|
||||
}
|
||||
r.logger.Debugf("InvoiceRenderer: Footer (lines: %d)", len(footers))
|
||||
|
||||
if len(footers) > 0 {
|
||||
totalWidth := 0
|
||||
if ctx.Row.Widths != nil {
|
||||
ctx.Row.Widths.Each(func(_ int, w int) { totalWidth += w })
|
||||
if ctx.Row.Widths.Len() > 1 {
|
||||
totalWidth += (ctx.Row.Widths.Len() - 1) * 3
|
||||
}
|
||||
}
|
||||
if totalWidth > 0 {
|
||||
fmt.Fprintln(r.writer, strings.Repeat("-", totalWidth))
|
||||
}
|
||||
}
|
||||
|
||||
for _, footerLineCells := range footers {
|
||||
lineStr := r.formatLine(footerLineCells, ctx.Row.Widths, ctx.Row.Current)
|
||||
fmt.Fprintln(r.writer, lineStr)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *InvoiceRenderer) Line(ctx tw.Formatting) {
|
||||
r.logger.Debug("InvoiceRenderer: Line (no-op)")
|
||||
// This simple renderer draws its own lines in Header/Footer.
|
||||
}
|
||||
|
||||
func (r *InvoiceRenderer) Close() error {
|
||||
r.logger.Debug("InvoiceRenderer: Close")
|
||||
r.writer = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
data := [][]string{
|
||||
{"Product A", "2", "10.00", "20.00"},
|
||||
{"Super Long Product Name B", "1", "125.50", "125.50"},
|
||||
{"Item C", "10", "1.99", "19.90"},
|
||||
}
|
||||
|
||||
header := []string{"Description", "Qty", "Unit Price", "Total Price"}
|
||||
footer := []string{"", "", "Subtotal:\nTax (10%):\nGRAND TOTAL:", "165.40\n16.54\n181.94"}
|
||||
invoiceRenderer := NewInvoiceRenderer()
|
||||
|
||||
// Create table and set custom renderer using Options
|
||||
table := tablewriter.NewTable(os.Stdout,
|
||||
tablewriter.WithRenderer(invoiceRenderer),
|
||||
tablewriter.WithAlignment([]tw.Align{
|
||||
tw.AlignLeft, tw.AlignCenter, tw.AlignRight, tw.AlignRight,
|
||||
}),
|
||||
)
|
||||
|
||||
table.Header(header)
|
||||
for _, v := range data {
|
||||
table.Append(v)
|
||||
}
|
||||
|
||||
// Use the Footer method with strings containing newlines for multi-line cells
|
||||
table.Footer(footer)
|
||||
|
||||
fmt.Println("Rendering with InvoiceRenderer:")
|
||||
table.Render()
|
||||
|
||||
// For comparison, render with default Blueprint renderer
|
||||
// Re-create the table or reset it to use a different renderer
|
||||
table2 := tablewriter.NewTable(os.Stdout,
|
||||
tablewriter.WithAlignment([]tw.Align{
|
||||
tw.AlignLeft, tw.AlignCenter, tw.AlignRight, tw.AlignRight,
|
||||
}),
|
||||
)
|
||||
|
||||
table2.Header(header)
|
||||
for _, v := range data {
|
||||
table2.Append(v)
|
||||
}
|
||||
table2.Footer(footer)
|
||||
fmt.Println("\nRendering with Default Blueprint Renderer (for comparison):")
|
||||
table2.Render()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
Rendering with InvoiceRenderer:
|
||||
DESCRIPTION QTY UNIT PRICE TOTAL PRICE
|
||||
--------------------------------------------------------------------
|
||||
Product A 2 10.00 20.00
|
||||
Super Long Product Name B 1 125.50 125.50
|
||||
Item C 10 1.99 19.90
|
||||
--------------------------------------------------------------------
|
||||
Subtotal: 165.40
|
||||
--------------------------------------------------------------------
|
||||
Tax (10%): 16.54
|
||||
--------------------------------------------------------------------
|
||||
GRAND TOTAL: 181.94
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
Rendering with Default Blueprint Renderer (for comparison):
|
||||
┌───────────────────────────┬─────┬──────────────┬─────────────┐
|
||||
│ DESCRIPTION │ QTY │ UNIT PRICE │ TOTAL PRICE │
|
||||
├───────────────────────────┼─────┼──────────────┼─────────────┤
|
||||
│ Product A │ 2 │ 10.00 │ 20.00 │
|
||||
│ Super Long Product Name B │ 1 │ 125.50 │ 125.50 │
|
||||
│ Item C │ 10 │ 1.99 │ 19.90 │
|
||||
├───────────────────────────┼─────┼──────────────┼─────────────┤
|
||||
│ │ │ Subtotal: │ 165.40 │
|
||||
│ │ │ Tax (10%): │ 16.54 │
|
||||
│ │ │ GRAND TOTAL: │ 181.94 │
|
||||
└───────────────────────────┴─────┴──────────────┴─────────────┘
|
||||
|
||||
```
|
||||
**Notes**:
|
||||
- The `renderer.NewBlueprint()` is sufficient for most text-based use cases.
|
||||
- Custom renderers require implementing all interface methods to handle table structure correctly. `tw.Formatting` (which includes `tw.RowContext`) provides cell content and metadata.
|
||||
|
|
@ -1914,7 +2152,7 @@ func main() {
|
|||
- **Direct ANSI Codes**: Embed codes (e.g., `\033[32m` for green) in strings for manual control (`zoo.go:convertCellsToStrings`).
|
||||
- **tw.Formatter**: Implement `Format() string` on custom types to control cell output, including colors (`tw/types.go:Formatter`).
|
||||
- **tw.CellFilter**: Use `Config.<Section>.Filter.Global` or `PerColumn` to apply transformations like coloring dynamically (`tw/cell.go:CellFilter`).
|
||||
- **Width Handling**: `tw.DisplayWidth()` correctly calculates display width of ANSI-coded strings, ignoring escape sequences (`tw/fn.go:DisplayWidth`).
|
||||
- **Width Handling**: `twdw.Width()` correctly calculates display width of ANSI-coded strings, ignoring escape sequences (`tw/fn.go:DisplayWidth`).
|
||||
- **No Built-In Color Presets**: Unlike v0.0.5’s potential `tablewriter.Colors`, v1.0.x requires manual ANSI code management or external libraries for color constants.
|
||||
|
||||
**Migration Tips**:
|
||||
|
|
@ -1928,7 +2166,7 @@ func main() {
|
|||
**Potential Pitfalls**:
|
||||
- **Terminal Support**: Some terminals may not support ANSI codes, causing artifacts; test in your environment or provide a non-colored fallback.
|
||||
- **Filter Overlap**: Combining `tw.CellFilter` with `AutoFormat` or other transformations can lead to unexpected results; prioritize filters for coloring (`zoo.go`).
|
||||
- **Width Miscalculation**: Incorrect ANSI code handling (e.g., missing `Reset`) can skew width calculations; use `tw.DisplayWidth` (`tw/fn.go`).
|
||||
- **Width Miscalculation**: Incorrect ANSI code handling (e.g., missing `Reset`) can skew width calculations; use `twdw.Width` (`tw/fn.go`).
|
||||
- **Streaming Consistency**: In streaming mode, ensure color codes are applied consistently across rows to avoid visual discrepancies (`stream.go`).
|
||||
- **Performance**: Applying filters to large datasets may add overhead; optimize filter logic for efficiency (`zoo.go`).
|
||||
|
||||
|
|
@ -2818,7 +3056,7 @@ func main() {
|
|||
**Notes**:
|
||||
- **Configuration**: Uses `tw.CellFilter` for per-column coloring, embedding ANSI codes (`tw/cell.go`).
|
||||
- **Migration from v0.0.5**: Replaces `SetColumnColor` with dynamic filters (`tablewriter.go`).
|
||||
- **Key Features**: Flexible color application; `tw.DisplayWidth` handles ANSI codes correctly (`tw/fn.go`).
|
||||
- **Key Features**: Flexible color application; `twdw.Width` handles ANSI codes correctly (`tw/fn.go`).
|
||||
- **Best Practices**: Test in ANSI-compatible terminals; use constants for code clarity.
|
||||
- **Potential Issues**: Non-ANSI terminals may show artifacts; provide fallbacks (`tw/fn.go`).
|
||||
|
||||
|
|
@ -3005,7 +3243,7 @@ This section addresses common migration issues with detailed solutions, covering
|
|||
| Merging not working | **Cause**: Streaming mode or mismatched data. **Solution**: Use batch mode for vertical/hierarchical merging; ensure identical content (`zoo.go`). |
|
||||
| Alignment ignored | **Cause**: `PerColumn` overrides `Global`. **Solution**: Check `Config.Section.Alignment.PerColumn` settings or `ConfigBuilder` calls (`tw/cell.go`). |
|
||||
| Padding affects widths | **Cause**: Padding included in `Config.Widths`. **Solution**: Adjust `Config.Widths` to account for `tw.CellPadding` (`zoo.go`). |
|
||||
| Colors not rendering | **Cause**: Non-ANSI terminal or incorrect codes. **Solution**: Test in ANSI-compatible terminal; use `tw.DisplayWidth` (`tw/fn.go`). |
|
||||
| Colors not rendering | **Cause**: Non-ANSI terminal or incorrect codes. **Solution**: Test in ANSI-compatible terminal; use `twdw.Width` (`tw/fn.go`). |
|
||||
| Caption missing | **Cause**: `Close()` not called in streaming or incorrect `Spot`. **Solution**: Ensure `Close()`; verify `tw.Caption.Spot` (`tablewriter.go`). |
|
||||
| Filters not applied | **Cause**: Incorrect `PerColumn` indexing or nil filters. **Solution**: Set filters correctly; test with sample data (`tw/cell.go`). |
|
||||
| Stringer cache overhead | **Cause**: Large datasets with diverse types. **Solution**: Disable `WithStringerCache` for small tables if not using `WithStringer` or if types vary greatly (`tablewriter.go`). |
|
||||
|
|
|
|||
45
vendor/github.com/olekukonko/tablewriter/README.md
generated
vendored
45
vendor/github.com/olekukonko/tablewriter/README.md
generated
vendored
|
|
@ -28,7 +28,7 @@ go get github.com/olekukonko/tablewriter@v0.0.5
|
|||
#### Latest Version
|
||||
The latest stable version
|
||||
```bash
|
||||
go get github.com/olekukonko/tablewriter@v1.0.7
|
||||
go get github.com/olekukonko/tablewriter@v1.1.0
|
||||
```
|
||||
|
||||
**Warning:** Version `v1.0.0` contains missing functionality and should not be used.
|
||||
|
|
@ -62,7 +62,7 @@ func main() {
|
|||
data := [][]string{
|
||||
{"Package", "Version", "Status"},
|
||||
{"tablewriter", "v0.0.5", "legacy"},
|
||||
{"tablewriter", "v1.0.7", "latest"},
|
||||
{"tablewriter", "v1.1.0", "latest"},
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
|
|
@ -77,7 +77,7 @@ func main() {
|
|||
│ PACKAGE │ VERSION │ STATUS │
|
||||
├─────────────┼─────────┼────────┤
|
||||
│ tablewriter │ v0.0.5 │ legacy │
|
||||
│ tablewriter │ v1.0.7 │ latest │
|
||||
│ tablewriter │ v1.1.0 │ latest │
|
||||
└─────────────┴─────────┴────────┘
|
||||
```
|
||||
|
||||
|
|
@ -86,6 +86,43 @@ func main() {
|
|||
|
||||
Create a table with `NewTable` or `NewWriter`, configure it using options or a `Config` struct, add data with `Append` or `Bulk`, and render to an `io.Writer`. Use renderers like `Blueprint` (ASCII), `HTML`, `Markdown`, `Colorized`, or `Ocean` (streaming).
|
||||
|
||||
Here's how the API primitives map to the generated ASCII table:
|
||||
|
||||
```
|
||||
API Call ASCII Table Component
|
||||
-------- ---------------------
|
||||
|
||||
table.Header([]string{"NAME", "AGE"}) ┌──────┬─────┐ ← Borders.Top
|
||||
│ NAME │ AGE │ ← Header row
|
||||
├──────┼─────┤ ← Lines.ShowTop (header separator)
|
||||
|
||||
table.Append([]string{"Alice", "25"}) │ Alice│ 25 │ ← Data row
|
||||
├──────┼─────┤ ← Separators.BetweenRows
|
||||
|
||||
table.Append([]string{"Bob", "30"}) │ Bob │ 30 │ ← Data row
|
||||
├──────┼─────┤ ← Lines.ShowBottom (footer separator)
|
||||
|
||||
table.Footer([]string{"Total", "2"}) │ Total│ 2 │ ← Footer row
|
||||
└──────┴─────┘ ← Borders.Bottom
|
||||
```
|
||||
|
||||
The core components include:
|
||||
|
||||
- **Renderer** - Implements the core interface for converting table data into output formats. Available renderers include Blueprint (ASCII), HTML, Markdown, Colorized (ASCII with color), Ocean (streaming ASCII), and SVG.
|
||||
|
||||
- **Config** - The root configuration struct that controls all table behavior and appearance
|
||||
- **Behavior** - Controls high-level rendering behaviors including auto-hiding empty columns, trimming row whitespace, header/footer visibility, and compact mode for optimized merged cell calculations
|
||||
- **CellConfig** - The comprehensive configuration template used for table sections (header, row, footer). Combines formatting, padding, alignment, filtering, callbacks, and width constraints with global and per-column control
|
||||
- **StreamConfig** - Configuration for streaming mode including enable/disable state and strict column validation
|
||||
|
||||
- **Rendition** - Defines how a renderer formats tables and contains the complete visual styling configuration
|
||||
- **Borders** - Control the outer frame visibility (top, bottom, left, right edges) of the table
|
||||
- **Lines** - Control horizontal boundary lines (above/below headers, above footers) that separate different table sections
|
||||
- **Separators** - Control the visibility of separators between rows and between columns within the table content
|
||||
- **Symbols** - Define the characters used for drawing table borders, corners, and junctions
|
||||
|
||||
These components can be configured with various `tablewriter.With*()` functional options when creating a new table.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Examples
|
||||
|
|
@ -1044,6 +1081,8 @@ func (t Time) Format() string {
|
|||
|
||||
- `AutoFormat` changes See [#261](https://github.com/olekukonko/tablewriter/issues/261)
|
||||
|
||||
## What is new
|
||||
- `Counting` changes See [#294](https://github.com/olekukonko/tablewriter/issues/294)
|
||||
|
||||
## Command-Line Tool
|
||||
|
||||
|
|
|
|||
13
vendor/github.com/olekukonko/tablewriter/config.go
generated
vendored
13
vendor/github.com/olekukonko/tablewriter/config.go
generated
vendored
|
|
@ -14,6 +14,7 @@ type Config struct {
|
|||
Stream tw.StreamConfig
|
||||
Behavior tw.Behavior
|
||||
Widths tw.CellWidth
|
||||
Counter tw.Counter
|
||||
}
|
||||
|
||||
// ConfigBuilder provides a fluent interface for building Config
|
||||
|
|
@ -199,11 +200,7 @@ func (b *ConfigBuilder) WithHeaderMergeMode(mergeMode int) *ConfigBuilder {
|
|||
// WithMaxWidth sets the maximum width for the entire table (0 means unlimited).
|
||||
// Negative values are treated as 0.
|
||||
func (b *ConfigBuilder) WithMaxWidth(width int) *ConfigBuilder {
|
||||
if width < 0 {
|
||||
b.config.MaxWidth = 0
|
||||
} else {
|
||||
b.config.MaxWidth = width
|
||||
}
|
||||
b.config.MaxWidth = max(width, 0)
|
||||
return b
|
||||
}
|
||||
|
||||
|
|
@ -688,6 +685,12 @@ func (bb *BehaviorConfigBuilder) WithCompactMerge(state tw.State) *BehaviorConfi
|
|||
return bb
|
||||
}
|
||||
|
||||
// WithAutoHeader enables/disables automatic header extraction for structs in Bulk.
|
||||
func (bb *BehaviorConfigBuilder) WithAutoHeader(state tw.State) *BehaviorConfigBuilder {
|
||||
bb.config.Structs.AutoHeader = state
|
||||
return bb
|
||||
}
|
||||
|
||||
// ColumnConfigBuilder configures column-specific settings
|
||||
type ColumnConfigBuilder struct {
|
||||
parent *ConfigBuilder
|
||||
|
|
|
|||
4
vendor/github.com/olekukonko/tablewriter/deprecated.go
generated
vendored
4
vendor/github.com/olekukonko/tablewriter/deprecated.go
generated
vendored
|
|
@ -1,6 +1,8 @@
|
|||
package tablewriter
|
||||
|
||||
import "github.com/olekukonko/tablewriter/tw"
|
||||
import (
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
)
|
||||
|
||||
// WithBorders configures the table's border settings by updating the renderer's border configuration.
|
||||
// This function is deprecated and will be removed in a future version.
|
||||
|
|
|
|||
102
vendor/github.com/olekukonko/tablewriter/option.go
generated
vendored
102
vendor/github.com/olekukonko/tablewriter/option.go
generated
vendored
|
|
@ -1,9 +1,12 @@
|
|||
package tablewriter
|
||||
|
||||
import (
|
||||
"github.com/olekukonko/ll"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
"reflect"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/olekukonko/ll"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
)
|
||||
|
||||
// Option defines a function type for configuring a Table instance.
|
||||
|
|
@ -496,6 +499,17 @@ func WithTrimSpace(state tw.State) Option {
|
|||
}
|
||||
}
|
||||
|
||||
// WithTrimLine sets whether empty visual lines within a cell are trimmed.
|
||||
// Logs the change if debugging is enabled.
|
||||
func WithTrimLine(state tw.State) Option {
|
||||
return func(target *Table) {
|
||||
target.config.Behavior.TrimLine = state
|
||||
if target.logger != nil {
|
||||
target.logger.Debugf("Option: WithTrimLine applied to Table: %v", state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithHeaderAutoFormat enables or disables automatic formatting for header cells.
|
||||
// Logs the change if debugging is enabled.
|
||||
func WithHeaderAutoFormat(state tw.State) Option {
|
||||
|
|
@ -605,14 +619,37 @@ func WithRendition(rendition tw.Rendition) Option {
|
|||
if target.logger != nil {
|
||||
target.logger.Debugf("Option: WithRendition: Applied to renderer via Renditioning.SetRendition(): %+v", rendition)
|
||||
}
|
||||
} else {
|
||||
if target.logger != nil {
|
||||
target.logger.Warnf("Option: WithRendition: Current renderer type %T does not implement tw.Renditioning. Rendition may not be applied as expected.", target.renderer)
|
||||
}
|
||||
} else if target.logger != nil {
|
||||
target.logger.Warnf("Option: WithRendition: Current renderer type %T does not implement tw.Renditioning. Rendition may not be applied as expected.", target.renderer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithEastAsian configures the global East Asian width calculation setting.
|
||||
// - enable=true: Enables East Asian width calculations. CJK and ambiguous characters
|
||||
// are typically measured as double width.
|
||||
// - enable=false: Disables East Asian width calculations. Characters are generally
|
||||
// measured as single width, subject to Unicode standards.
|
||||
//
|
||||
// This setting affects all subsequent display width calculations using the twdw package.
|
||||
func WithEastAsian(enable bool) Option {
|
||||
return func(target *Table) {
|
||||
twwidth.SetEastAsian(enable)
|
||||
}
|
||||
}
|
||||
|
||||
// WithCondition provides a way to set a custom global runewidth.Condition
|
||||
// that will be used for all subsequent display width calculations by the twwidth (twdw) package.
|
||||
//
|
||||
// The runewidth.Condition object allows for more fine-grained control over how rune widths
|
||||
// are determined, beyond just toggling EastAsianWidth. This could include settings for
|
||||
// ambiguous width characters or other future properties of runewidth.Condition.
|
||||
func WithCondition(condition *runewidth.Condition) Option {
|
||||
return func(target *Table) {
|
||||
twwidth.SetCondition(condition)
|
||||
}
|
||||
}
|
||||
|
||||
// WithSymbols sets the symbols used for drawing table borders and separators.
|
||||
// The symbols are applied to the table's renderer configuration, if a renderer is set.
|
||||
// If no renderer is set (target.renderer is nil), this option has no effect. .
|
||||
|
|
@ -627,15 +664,38 @@ func WithSymbols(symbols tw.Symbols) Option {
|
|||
if target.logger != nil {
|
||||
target.logger.Debugf("Option: WithRendition: Applied to renderer via Renditioning.SetRendition(): %+v", cfg)
|
||||
}
|
||||
} else {
|
||||
if target.logger != nil {
|
||||
target.logger.Warnf("Option: WithRendition: Current renderer type %T does not implement tw.Renditioning. Rendition may not be applied as expected.", target.renderer)
|
||||
}
|
||||
} else if target.logger != nil {
|
||||
target.logger.Warnf("Option: WithRendition: Current renderer type %T does not implement tw.Renditioning. Rendition may not be applied as expected.", target.renderer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithCounters enables line counting by wrapping the table's writer.
|
||||
// If a custom counter (that implements tw.Counter) is provided, it will be used.
|
||||
// If the provided counter is nil, a default tw.LineCounter will be used.
|
||||
// The final count can be retrieved via the table.Lines() method after Render() is called.
|
||||
func WithCounters(counters ...tw.Counter) Option {
|
||||
return func(target *Table) {
|
||||
// Iterate through the provided counters and add any non-nil ones.
|
||||
for _, c := range counters {
|
||||
if c != nil {
|
||||
target.counters = append(target.counters, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithLineCounter enables the default line counter.
|
||||
// A new instance of tw.LineCounter is added to the table's list of counters.
|
||||
// The total count can be retrieved via the table.Lines() method after Render() is called.
|
||||
func WithLineCounter() Option {
|
||||
return func(target *Table) {
|
||||
// Important: Create a new instance so tables don't share counters.
|
||||
target.counters = append(target.counters, &tw.LineCounter{})
|
||||
}
|
||||
}
|
||||
|
||||
// defaultConfig returns a default Config with sensible settings for headers, rows, footers, and behavior.
|
||||
func defaultConfig() Config {
|
||||
return Config{
|
||||
|
|
@ -682,11 +742,19 @@ func defaultConfig() Config {
|
|||
PerColumn: []tw.Align{},
|
||||
},
|
||||
},
|
||||
Stream: tw.StreamConfig{},
|
||||
Debug: false,
|
||||
Stream: tw.StreamConfig{
|
||||
Enable: false,
|
||||
StrictColumns: false,
|
||||
},
|
||||
Debug: false,
|
||||
Behavior: tw.Behavior{
|
||||
AutoHide: tw.Off,
|
||||
TrimSpace: tw.On,
|
||||
TrimLine: tw.On,
|
||||
Structs: tw.Struct{
|
||||
AutoHeader: tw.Off,
|
||||
Tags: []string{"json", "db"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -814,6 +882,14 @@ func mergeConfig(dst, src Config) Config {
|
|||
dst.Behavior.Compact = src.Behavior.Compact
|
||||
dst.Behavior.Header = src.Behavior.Header
|
||||
dst.Behavior.Footer = src.Behavior.Footer
|
||||
dst.Behavior.Footer = src.Behavior.Footer
|
||||
|
||||
dst.Behavior.Structs.AutoHeader = src.Behavior.Structs.AutoHeader
|
||||
|
||||
// check lent of tags
|
||||
if len(src.Behavior.Structs.Tags) > 0 {
|
||||
dst.Behavior.Structs.Tags = src.Behavior.Structs.Tags
|
||||
}
|
||||
|
||||
if src.Widths.Global != 0 {
|
||||
dst.Widths.Global = src.Widths.Global
|
||||
|
|
@ -842,6 +918,8 @@ func mergeStreamConfig(dst, src tw.StreamConfig) tw.StreamConfig {
|
|||
if src.Enable {
|
||||
dst.Enable = true
|
||||
}
|
||||
|
||||
dst.StrictColumns = src.StrictColumns
|
||||
return dst
|
||||
}
|
||||
|
||||
|
|
|
|||
29
vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go
generated
vendored
29
vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go
generated
vendored
|
|
@ -8,12 +8,13 @@
|
|||
package twwarp
|
||||
|
||||
import (
|
||||
"github.com/rivo/uniseg"
|
||||
"math"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth" // IMPORT YOUR NEW PACKAGE
|
||||
"github.com/rivo/uniseg"
|
||||
// "github.com/mattn/go-runewidth" // This can be removed if all direct uses are gone
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -59,7 +60,8 @@ func WrapString(s string, lim int) ([]string, int) {
|
|||
var lines []string
|
||||
max := 0
|
||||
for _, v := range words {
|
||||
max = runewidth.StringWidth(v)
|
||||
// max = runewidth.StringWidth(v) // OLD
|
||||
max = twwidth.Width(v) // NEW: Use twdw.Width
|
||||
if max > lim {
|
||||
lim = max
|
||||
}
|
||||
|
|
@ -82,12 +84,13 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
|
|||
return []string{""}, lim
|
||||
}
|
||||
if strings.TrimSpace(s) == "" { // All spaces
|
||||
if runewidth.StringWidth(s) <= lim {
|
||||
return []string{s}, runewidth.StringWidth(s)
|
||||
// if runewidth.StringWidth(s) <= lim { // OLD
|
||||
if twwidth.Width(s) <= lim { // NEW: Use twdw.Width
|
||||
// return []string{s}, runewidth.StringWidth(s) // OLD
|
||||
return []string{s}, twwidth.Width(s) // NEW: Use twdw.Width
|
||||
}
|
||||
// For very long all-space strings, "wrap" by truncating to the limit.
|
||||
if lim > 0 {
|
||||
// Use our new helper function to get a substring of the correct display width
|
||||
substring, _ := stringToDisplayWidth(s, lim)
|
||||
return []string{substring}, lim
|
||||
}
|
||||
|
|
@ -96,7 +99,6 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
|
|||
|
||||
var leadingSpaces, trailingSpaces, coreContent string
|
||||
firstNonSpace := strings.IndexFunc(s, func(r rune) bool { return !unicode.IsSpace(r) })
|
||||
// firstNonSpace will not be -1 due to TrimSpace check above.
|
||||
leadingSpaces = s[:firstNonSpace]
|
||||
lastNonSpace := strings.LastIndexFunc(s, func(r rune) bool { return !unicode.IsSpace(r) })
|
||||
trailingSpaces = s[lastNonSpace+1:]
|
||||
|
|
@ -116,7 +118,8 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
|
|||
|
||||
maxCoreWordWidth := 0
|
||||
for _, v := range words {
|
||||
w := runewidth.StringWidth(v)
|
||||
// w := runewidth.StringWidth(v) // OLD
|
||||
w := twwidth.Width(v) // NEW: Use twdw.Width
|
||||
if w > maxCoreWordWidth {
|
||||
maxCoreWordWidth = w
|
||||
}
|
||||
|
|
@ -153,15 +156,14 @@ func stringToDisplayWidth(s string, targetWidth int) (substring string, actualWi
|
|||
g := uniseg.NewGraphemes(s)
|
||||
for g.Next() {
|
||||
grapheme := g.Str()
|
||||
graphemeWidth := runewidth.StringWidth(grapheme) // Get width of the current grapheme cluster
|
||||
// graphemeWidth := runewidth.StringWidth(grapheme) // OLD
|
||||
graphemeWidth := twwidth.Width(grapheme) // NEW: Use twdw.Width
|
||||
|
||||
if currentWidth+graphemeWidth > targetWidth {
|
||||
// Adding this grapheme would exceed the target width
|
||||
break
|
||||
}
|
||||
|
||||
currentWidth += graphemeWidth
|
||||
// Get the end byte position of the current grapheme cluster
|
||||
_, e := g.Positions()
|
||||
endIndex = e
|
||||
}
|
||||
|
|
@ -186,14 +188,15 @@ func WrapWords(words []string, spc, lim, pen int) [][]string {
|
|||
}
|
||||
lengths := make([]int, n)
|
||||
for i := 0; i < n; i++ {
|
||||
lengths[i] = runewidth.StringWidth(words[i])
|
||||
// lengths[i] = runewidth.StringWidth(words[i]) // OLD
|
||||
lengths[i] = twwidth.Width(words[i]) // NEW: Use twdw.Width
|
||||
}
|
||||
nbrk := make([]int, n)
|
||||
cost := make([]int, n)
|
||||
for i := range cost {
|
||||
cost[i] = math.MaxInt32
|
||||
}
|
||||
remainderLen := lengths[n-1]
|
||||
remainderLen := lengths[n-1] // Uses updated lengths
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
if i < n-1 {
|
||||
remainderLen += spc + lengths[i]
|
||||
|
|
|
|||
323
vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go
generated
vendored
Normal file
323
vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go
generated
vendored
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
package twwidth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// condition holds the global runewidth configuration, including East Asian width settings.
|
||||
var condition *runewidth.Condition
|
||||
|
||||
// mu protects access to condition and widthCache for thread safety.
|
||||
var mu sync.Mutex
|
||||
|
||||
// ansi is a compiled regular expression for stripping ANSI escape codes from strings.
|
||||
var ansi = Filter()
|
||||
|
||||
func init() {
|
||||
condition = runewidth.NewCondition()
|
||||
widthCache = make(map[cacheKey]int)
|
||||
}
|
||||
|
||||
// cacheKey is used as a key for memoizing string width results in widthCache.
|
||||
type cacheKey struct {
|
||||
str string // Input string
|
||||
eastAsianWidth bool // East Asian width setting
|
||||
}
|
||||
|
||||
// widthCache stores memoized results of Width calculations to improve performance.
|
||||
var widthCache map[cacheKey]int
|
||||
|
||||
// Filter compiles and returns a regular expression for matching ANSI escape sequences,
|
||||
// including CSI (Control Sequence Introducer) and OSC (Operating System Command) sequences.
|
||||
// The returned regex can be used to strip ANSI codes from strings.
|
||||
func Filter() *regexp.Regexp {
|
||||
regESC := "\x1b" // ASCII escape character
|
||||
regBEL := "\x07" // ASCII bell character
|
||||
|
||||
// ANSI string terminator: either ESC+\ or BEL
|
||||
regST := "(" + regexp.QuoteMeta(regESC+"\\") + "|" + regexp.QuoteMeta(regBEL) + ")"
|
||||
// Control Sequence Introducer (CSI): ESC[ followed by parameters and a final byte
|
||||
regCSI := regexp.QuoteMeta(regESC+"[") + "[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]"
|
||||
// Operating System Command (OSC): ESC] followed by arbitrary content until a terminator
|
||||
regOSC := regexp.QuoteMeta(regESC+"]") + ".*?" + regST
|
||||
|
||||
// Combine CSI and OSC patterns into a single regex
|
||||
return regexp.MustCompile("(" + regCSI + "|" + regOSC + ")")
|
||||
}
|
||||
|
||||
// SetEastAsian enables or disables East Asian width handling for width calculations.
|
||||
// When the setting changes, the width cache is cleared to ensure accuracy.
|
||||
// This function is thread-safe.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// twdw.SetEastAsian(true) // Enable East Asian width handling
|
||||
func SetEastAsian(enable bool) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if condition.EastAsianWidth != enable {
|
||||
condition.EastAsianWidth = enable
|
||||
widthCache = make(map[cacheKey]int) // Clear cache on setting change
|
||||
}
|
||||
}
|
||||
|
||||
// SetCondition updates the global runewidth.Condition used for width calculations.
|
||||
// When the condition is changed, the width cache is cleared.
|
||||
// This function is thread-safe.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// newCond := runewidth.NewCondition()
|
||||
// newCond.EastAsianWidth = true
|
||||
// twdw.SetCondition(newCond)
|
||||
func SetCondition(newCond *runewidth.Condition) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
condition = newCond
|
||||
widthCache = make(map[cacheKey]int) // Clear cache on setting change
|
||||
}
|
||||
|
||||
// Width calculates the visual width of a string, excluding ANSI escape sequences,
|
||||
// using the go-runewidth package for accurate Unicode handling. It accounts for the
|
||||
// current East Asian width setting and caches results for performance.
|
||||
// This function is thread-safe.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// width := twdw.Width("Hello\x1b[31mWorld") // Returns 10
|
||||
func Width(str string) int {
|
||||
mu.Lock()
|
||||
key := cacheKey{str: str, eastAsianWidth: condition.EastAsianWidth}
|
||||
if w, found := widthCache[key]; found {
|
||||
mu.Unlock()
|
||||
return w
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
// Use a temporary condition to avoid holding the lock during calculation
|
||||
tempCond := runewidth.NewCondition()
|
||||
tempCond.EastAsianWidth = key.eastAsianWidth
|
||||
|
||||
stripped := ansi.ReplaceAllLiteralString(str, "")
|
||||
calculatedWidth := tempCond.StringWidth(stripped)
|
||||
|
||||
mu.Lock()
|
||||
widthCache[key] = calculatedWidth
|
||||
mu.Unlock()
|
||||
|
||||
return calculatedWidth
|
||||
}
|
||||
|
||||
// WidthNoCache calculates the visual width of a string without using or
|
||||
// updating the global cache. It uses the current global East Asian width setting.
|
||||
// This function is intended for internal use (e.g., benchmarking) and is thread-safe.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// width := twdw.WidthNoCache("Hello\x1b[31mWorld") // Returns 10
|
||||
func WidthNoCache(str string) int {
|
||||
mu.Lock()
|
||||
currentEA := condition.EastAsianWidth
|
||||
mu.Unlock()
|
||||
|
||||
tempCond := runewidth.NewCondition()
|
||||
tempCond.EastAsianWidth = currentEA
|
||||
|
||||
stripped := ansi.ReplaceAllLiteralString(str, "")
|
||||
return tempCond.StringWidth(stripped)
|
||||
}
|
||||
|
||||
// Display calculates the visual width of a string, excluding ANSI escape sequences,
|
||||
// using the provided runewidth condition. Unlike Width, it does not use caching
|
||||
// and is intended for cases where a specific condition is required.
|
||||
// This function is thread-safe with respect to the provided condition.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// cond := runewidth.NewCondition()
|
||||
// width := twdw.Display(cond, "Hello\x1b[31mWorld") // Returns 10
|
||||
func Display(cond *runewidth.Condition, str string) int {
|
||||
return cond.StringWidth(ansi.ReplaceAllLiteralString(str, ""))
|
||||
}
|
||||
|
||||
// Truncate shortens a string to fit within a specified visual width, optionally
|
||||
// appending a suffix (e.g., "..."). It preserves ANSI escape sequences and adds
|
||||
// a reset sequence (\x1b[0m) if needed to prevent formatting bleed. The function
|
||||
// respects the global East Asian width setting and is thread-safe.
|
||||
//
|
||||
// If maxWidth is negative, an empty string is returned. If maxWidth is zero and
|
||||
// a suffix is provided, the suffix is returned. If the string's visual width is
|
||||
// less than or equal to maxWidth, the string (and suffix, if provided and fits)
|
||||
// is returned unchanged.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// s := twdw.Truncate("Hello\x1b[31mWorld", 5, "...") // Returns "Hello..."
|
||||
// s = twdw.Truncate("Hello", 10) // Returns "Hello"
|
||||
func Truncate(s string, maxWidth int, suffix ...string) string {
|
||||
if maxWidth < 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
suffixStr := strings.Join(suffix, "")
|
||||
sDisplayWidth := Width(s) // Uses global cached Width
|
||||
suffixDisplayWidth := Width(suffixStr) // Uses global cached Width
|
||||
|
||||
// Case 1: Original string is visually empty.
|
||||
if sDisplayWidth == 0 {
|
||||
// If suffix is provided and fits within maxWidth (or if maxWidth is generous)
|
||||
if len(suffixStr) > 0 && suffixDisplayWidth <= maxWidth {
|
||||
return suffixStr
|
||||
}
|
||||
// If s has ANSI codes (len(s)>0) but maxWidth is 0, can't display them.
|
||||
if maxWidth == 0 && len(s) > 0 {
|
||||
return ""
|
||||
}
|
||||
return s // Returns "" or original ANSI codes
|
||||
}
|
||||
|
||||
// Case 2: maxWidth is 0, but string has content. Cannot display anything.
|
||||
if maxWidth == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Case 3: String fits completely or fits with suffix.
|
||||
// Here, maxWidth is the total budget for the line.
|
||||
if sDisplayWidth <= maxWidth {
|
||||
if len(suffixStr) == 0 { // No suffix.
|
||||
return s
|
||||
}
|
||||
// Suffix is provided. Check if s + suffix fits.
|
||||
if sDisplayWidth+suffixDisplayWidth <= maxWidth {
|
||||
return s + suffixStr
|
||||
}
|
||||
// s fits, but s + suffix is too long. Return s.
|
||||
return s
|
||||
}
|
||||
|
||||
// Case 4: String needs truncation (sDisplayWidth > maxWidth).
|
||||
// maxWidth is the total budget for the final string (content + suffix).
|
||||
|
||||
// Capture the global EastAsianWidth setting once for consistent use
|
||||
mu.Lock()
|
||||
currentGlobalEastAsianWidth := condition.EastAsianWidth
|
||||
mu.Unlock()
|
||||
|
||||
// Special case for EastAsian true: if only suffix fits, return suffix.
|
||||
// This was derived from previous test behavior.
|
||||
if len(suffixStr) > 0 && currentGlobalEastAsianWidth {
|
||||
provisionalContentWidth := maxWidth - suffixDisplayWidth
|
||||
if provisionalContentWidth == 0 { // Exactly enough space for suffix only
|
||||
return suffixStr // <<<< MODIFIED: No ANSI reset here
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the budget for the content part, reserving space for the suffix.
|
||||
targetContentForIteration := maxWidth
|
||||
if len(suffixStr) > 0 {
|
||||
targetContentForIteration -= suffixDisplayWidth
|
||||
}
|
||||
|
||||
// If content budget is negative, means not even suffix fits (or no suffix and no space).
|
||||
// However, if only suffix fits, it should be handled.
|
||||
if targetContentForIteration < 0 {
|
||||
// Can we still fit just the suffix?
|
||||
if len(suffixStr) > 0 && suffixDisplayWidth <= maxWidth {
|
||||
if strings.Contains(s, "\x1b[") {
|
||||
return "\x1b[0m" + suffixStr
|
||||
}
|
||||
return suffixStr
|
||||
}
|
||||
return "" // Cannot fit anything.
|
||||
}
|
||||
// If targetContentForIteration is 0, loop won't run, result will be empty string, then suffix is added.
|
||||
|
||||
var contentBuf bytes.Buffer
|
||||
var currentContentDisplayWidth int
|
||||
var ansiSeqBuf bytes.Buffer
|
||||
inAnsiSequence := false
|
||||
ansiWrittenToContent := false
|
||||
|
||||
localRunewidthCond := runewidth.NewCondition()
|
||||
localRunewidthCond.EastAsianWidth = currentGlobalEastAsianWidth
|
||||
|
||||
for _, r := range s {
|
||||
if r == '\x1b' {
|
||||
inAnsiSequence = true
|
||||
ansiSeqBuf.Reset()
|
||||
ansiSeqBuf.WriteRune(r)
|
||||
} else if inAnsiSequence {
|
||||
ansiSeqBuf.WriteRune(r)
|
||||
seqBytes := ansiSeqBuf.Bytes()
|
||||
seqLen := len(seqBytes)
|
||||
terminated := false
|
||||
if seqLen >= 2 {
|
||||
introducer := seqBytes[1]
|
||||
switch introducer {
|
||||
case '[':
|
||||
if seqLen >= 3 && r >= 0x40 && r <= 0x7E {
|
||||
terminated = true
|
||||
}
|
||||
case ']':
|
||||
if r == '\x07' {
|
||||
terminated = true
|
||||
} else if seqLen > 1 && seqBytes[seqLen-2] == '\x1b' && r == '\\' { // Check for ST: \x1b\
|
||||
terminated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if terminated {
|
||||
inAnsiSequence = false
|
||||
contentBuf.Write(ansiSeqBuf.Bytes())
|
||||
ansiWrittenToContent = true
|
||||
ansiSeqBuf.Reset()
|
||||
}
|
||||
} else { // Normal character
|
||||
runeDisplayWidth := localRunewidthCond.RuneWidth(r)
|
||||
if targetContentForIteration == 0 { // No budget for content at all
|
||||
break
|
||||
}
|
||||
if currentContentDisplayWidth+runeDisplayWidth > targetContentForIteration {
|
||||
break
|
||||
}
|
||||
contentBuf.WriteRune(r)
|
||||
currentContentDisplayWidth += runeDisplayWidth
|
||||
}
|
||||
}
|
||||
|
||||
result := contentBuf.String()
|
||||
|
||||
// Suffix is added if:
|
||||
// 1. A suffix string is provided.
|
||||
// 2. Truncation actually happened (sDisplayWidth > maxWidth originally)
|
||||
// OR if the content part is empty but a suffix is meant to be shown
|
||||
// (e.g. targetContentForIteration was 0).
|
||||
if len(suffixStr) > 0 {
|
||||
// Add suffix if we are in the truncation path (sDisplayWidth > maxWidth)
|
||||
// OR if targetContentForIteration was 0 (meaning only suffix should be shown)
|
||||
// but we must ensure we don't exceed original maxWidth.
|
||||
// The logic above for targetContentForIteration already ensures space.
|
||||
|
||||
needsReset := false
|
||||
// Condition for reset: if styling was active in 's' and might affect suffix
|
||||
if (ansiWrittenToContent || (inAnsiSequence && strings.Contains(s, "\x1b["))) && (currentContentDisplayWidth > 0 || ansiWrittenToContent) {
|
||||
if !strings.HasSuffix(result, "\x1b[0m") {
|
||||
needsReset = true
|
||||
}
|
||||
} else if currentContentDisplayWidth > 0 && strings.Contains(result, "\x1b[") && !strings.HasSuffix(result, "\x1b[0m") && strings.Contains(s, "\x1b[") {
|
||||
// If result has content and ANSI, and original had ANSI, and result not already reset
|
||||
needsReset = true
|
||||
}
|
||||
|
||||
if needsReset {
|
||||
result += "\x1b[0m"
|
||||
}
|
||||
result += suffixStr
|
||||
}
|
||||
return result
|
||||
}
|
||||
126
vendor/github.com/olekukonko/tablewriter/renderer/blueprint.go
generated
vendored
126
vendor/github.com/olekukonko/tablewriter/renderer/blueprint.go
generated
vendored
|
|
@ -1,10 +1,12 @@
|
|||
package renderer
|
||||
|
||||
import (
|
||||
"github.com/olekukonko/ll"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/ll"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
)
|
||||
|
||||
|
|
@ -42,7 +44,7 @@ func NewBlueprint(configs ...tw.Rendition) *Blueprint {
|
|||
// Merge user settings with default settings
|
||||
cfg.Settings = mergeSettings(cfg.Settings, userCfg.Settings)
|
||||
}
|
||||
return &Blueprint{config: cfg}
|
||||
return &Blueprint{config: cfg, logger: ll.New("blueprint")}
|
||||
}
|
||||
|
||||
// Close performs cleanup (no-op in this implementation).
|
||||
|
|
@ -106,7 +108,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
|||
}
|
||||
if prefix != tw.Empty || suffix != tw.Empty {
|
||||
line.WriteString(prefix + suffix + tw.NewLine)
|
||||
totalLineWidth = tw.DisplayWidth(prefix) + tw.DisplayWidth(suffix)
|
||||
totalLineWidth = twwidth.Width(prefix) + twwidth.Width(suffix)
|
||||
f.w.Write([]byte(line.String()))
|
||||
}
|
||||
f.logger.Debugf("Line: Handled empty row/widths case (total width %d)", totalLineWidth)
|
||||
|
|
@ -119,13 +121,13 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
|||
targetTotalWidth += ctx.Row.Widths.Get(colIdx)
|
||||
}
|
||||
if f.config.Borders.Left.Enabled() {
|
||||
targetTotalWidth += tw.DisplayWidth(f.config.Symbols.Column())
|
||||
targetTotalWidth += twwidth.Width(f.config.Symbols.Column())
|
||||
}
|
||||
if f.config.Borders.Right.Enabled() {
|
||||
targetTotalWidth += tw.DisplayWidth(f.config.Symbols.Column())
|
||||
targetTotalWidth += twwidth.Width(f.config.Symbols.Column())
|
||||
}
|
||||
if f.config.Settings.Separators.BetweenColumns.Enabled() && len(sortedKeys) > 1 {
|
||||
targetTotalWidth += tw.DisplayWidth(f.config.Symbols.Column()) * (len(sortedKeys) - 1)
|
||||
targetTotalWidth += twwidth.Width(f.config.Symbols.Column()) * (len(sortedKeys) - 1)
|
||||
}
|
||||
|
||||
// Add left border if enabled
|
||||
|
|
@ -133,7 +135,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
|||
if f.config.Borders.Left.Enabled() {
|
||||
leftBorder := jr.RenderLeft()
|
||||
line.WriteString(leftBorder)
|
||||
leftBorderWidth = tw.DisplayWidth(leftBorder)
|
||||
leftBorderWidth = twwidth.Width(leftBorder)
|
||||
totalLineWidth += leftBorderWidth
|
||||
f.logger.Debugf("Line: Left border='%s' (f.width %d)", leftBorder, leftBorderWidth)
|
||||
}
|
||||
|
|
@ -156,11 +158,11 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
|||
// Adjust colWidth to account for wider borders
|
||||
adjustedColWidth := colWidth
|
||||
if f.config.Borders.Left.Enabled() && keyIndex == 0 {
|
||||
adjustedColWidth -= leftBorderWidth - tw.DisplayWidth(f.config.Symbols.Column())
|
||||
adjustedColWidth -= leftBorderWidth - twwidth.Width(f.config.Symbols.Column())
|
||||
}
|
||||
if f.config.Borders.Right.Enabled() && keyIndex == len(visibleColIndices)-1 {
|
||||
rightBorderWidth := tw.DisplayWidth(jr.RenderRight(currentColIdx))
|
||||
adjustedColWidth -= rightBorderWidth - tw.DisplayWidth(f.config.Symbols.Column())
|
||||
rightBorderWidth := twwidth.Width(jr.RenderRight(currentColIdx))
|
||||
adjustedColWidth -= rightBorderWidth - twwidth.Width(f.config.Symbols.Column())
|
||||
}
|
||||
if adjustedColWidth < 0 {
|
||||
adjustedColWidth = 0
|
||||
|
|
@ -172,7 +174,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
|||
totalLineWidth += adjustedColWidth
|
||||
f.logger.Debugf("Line: Rendered spaces='%s' (f.width %d) for col %d", spaces, adjustedColWidth, currentColIdx)
|
||||
} else {
|
||||
segmentWidth := tw.DisplayWidth(segment)
|
||||
segmentWidth := twwidth.Width(segment)
|
||||
if segmentWidth == 0 {
|
||||
segmentWidth = 1 // Avoid division by zero
|
||||
f.logger.Warnf("Line: Segment='%s' has zero width, using 1", segment)
|
||||
|
|
@ -183,11 +185,11 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
|||
repeat = 1
|
||||
}
|
||||
repeatedSegment := strings.Repeat(segment, repeat)
|
||||
actualWidth := tw.DisplayWidth(repeatedSegment)
|
||||
actualWidth := twwidth.Width(repeatedSegment)
|
||||
if actualWidth > adjustedColWidth {
|
||||
// Truncate if too long
|
||||
repeatedSegment = tw.TruncateString(repeatedSegment, adjustedColWidth)
|
||||
actualWidth = tw.DisplayWidth(repeatedSegment)
|
||||
repeatedSegment = twwidth.Truncate(repeatedSegment, adjustedColWidth)
|
||||
actualWidth = twwidth.Width(repeatedSegment)
|
||||
f.logger.Debugf("Line: Truncated segment='%s' to width %d", repeatedSegment, actualWidth)
|
||||
} else if actualWidth < adjustedColWidth {
|
||||
// Pad with segment character to match adjustedColWidth
|
||||
|
|
@ -195,7 +197,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
|||
for i := 0; i < remainingWidth/segmentWidth; i++ {
|
||||
repeatedSegment += segment
|
||||
}
|
||||
actualWidth = tw.DisplayWidth(repeatedSegment)
|
||||
actualWidth = twwidth.Width(repeatedSegment)
|
||||
if actualWidth < adjustedColWidth {
|
||||
repeatedSegment = tw.PadRight(repeatedSegment, tw.Space, adjustedColWidth)
|
||||
actualWidth = adjustedColWidth
|
||||
|
|
@ -214,13 +216,13 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
|||
nextColIdx := visibleColIndices[keyIndex+1]
|
||||
junction := jr.RenderJunction(currentColIdx, nextColIdx)
|
||||
// Use center symbol (❀) or column separator (|) to match data rows
|
||||
if tw.DisplayWidth(junction) != tw.DisplayWidth(f.config.Symbols.Column()) {
|
||||
if twwidth.Width(junction) != twwidth.Width(f.config.Symbols.Column()) {
|
||||
junction = f.config.Symbols.Center()
|
||||
if tw.DisplayWidth(junction) != tw.DisplayWidth(f.config.Symbols.Column()) {
|
||||
if twwidth.Width(junction) != twwidth.Width(f.config.Symbols.Column()) {
|
||||
junction = f.config.Symbols.Column()
|
||||
}
|
||||
}
|
||||
junctionWidth := tw.DisplayWidth(junction)
|
||||
junctionWidth := twwidth.Width(junction)
|
||||
line.WriteString(junction)
|
||||
totalLineWidth += junctionWidth
|
||||
f.logger.Debugf("Line: Junction between %d and %d: '%s' (f.width %d)", currentColIdx, nextColIdx, junction, junctionWidth)
|
||||
|
|
@ -232,7 +234,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
|||
if f.config.Borders.Right.Enabled() && len(visibleColIndices) > 0 {
|
||||
lastIdx := visibleColIndices[len(visibleColIndices)-1]
|
||||
rightBorder := jr.RenderRight(lastIdx)
|
||||
rightBorderWidth = tw.DisplayWidth(rightBorder)
|
||||
rightBorderWidth = twwidth.Width(rightBorder)
|
||||
line.WriteString(rightBorder)
|
||||
totalLineWidth += rightBorderWidth
|
||||
f.logger.Debugf("Line: Right border='%s' (f.width %d)", rightBorder, rightBorderWidth)
|
||||
|
|
@ -276,13 +278,13 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
|
|||
content, width, align, padding.Left, padding.Right)
|
||||
|
||||
// Calculate display width of content
|
||||
runeWidth := tw.DisplayWidth(content)
|
||||
runeWidth := twwidth.Width(content)
|
||||
|
||||
// Set default padding characters
|
||||
leftPadChar := padding.Left
|
||||
rightPadChar := padding.Right
|
||||
|
||||
//if f.config.Settings.Cushion.Enabled() || f.config.Settings.Cushion.Default() {
|
||||
// if f.config.Settings.Cushion.Enabled() || f.config.Settings.Cushion.Default() {
|
||||
// if leftPadChar == tw.Empty {
|
||||
// leftPadChar = tw.Space
|
||||
// }
|
||||
|
|
@ -292,28 +294,22 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
|
|||
//}
|
||||
|
||||
// Calculate padding widths
|
||||
padLeftWidth := tw.DisplayWidth(leftPadChar)
|
||||
padRightWidth := tw.DisplayWidth(rightPadChar)
|
||||
padLeftWidth := twwidth.Width(leftPadChar)
|
||||
padRightWidth := twwidth.Width(rightPadChar)
|
||||
|
||||
// Calculate available width for content
|
||||
availableContentWidth := width - padLeftWidth - padRightWidth
|
||||
if availableContentWidth < 0 {
|
||||
availableContentWidth = 0
|
||||
}
|
||||
availableContentWidth := max(width-padLeftWidth-padRightWidth, 0)
|
||||
f.logger.Debugf("Available content width: %d", availableContentWidth)
|
||||
|
||||
// Truncate content if it exceeds available width
|
||||
if runeWidth > availableContentWidth {
|
||||
content = tw.TruncateString(content, availableContentWidth)
|
||||
runeWidth = tw.DisplayWidth(content)
|
||||
content = twwidth.Truncate(content, availableContentWidth)
|
||||
runeWidth = twwidth.Width(content)
|
||||
f.logger.Debugf("Truncated content to fit %d: '%s' (new width %d)", availableContentWidth, content, runeWidth)
|
||||
}
|
||||
|
||||
// Calculate total padding needed
|
||||
totalPaddingWidth := width - runeWidth
|
||||
if totalPaddingWidth < 0 {
|
||||
totalPaddingWidth = 0
|
||||
}
|
||||
totalPaddingWidth := max(width-runeWidth, 0)
|
||||
f.logger.Debugf("Total padding width: %d", totalPaddingWidth)
|
||||
|
||||
var result strings.Builder
|
||||
|
|
@ -363,10 +359,10 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
|
|||
}
|
||||
|
||||
output := result.String()
|
||||
finalWidth := tw.DisplayWidth(output)
|
||||
finalWidth := twwidth.Width(output)
|
||||
// Adjust output to match target width
|
||||
if finalWidth > width {
|
||||
output = tw.TruncateString(output, width)
|
||||
output = twwidth.Truncate(output, width)
|
||||
f.logger.Debugf("formatCell: Truncated output to width %d", width)
|
||||
} else if finalWidth < width {
|
||||
output = tw.PadRight(output, tw.Space, width)
|
||||
|
|
@ -374,9 +370,9 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
|
|||
}
|
||||
|
||||
// Log warning if final width doesn't match target
|
||||
if f.logger.Enabled() && tw.DisplayWidth(output) != width {
|
||||
if f.logger.Enabled() && twwidth.Width(output) != width {
|
||||
f.logger.Debugf("formatCell Warning: Final width %d does not match target %d for result '%s'",
|
||||
tw.DisplayWidth(output), width, output)
|
||||
twwidth.Width(output), width, output)
|
||||
}
|
||||
|
||||
f.logger.Debugf("Formatted cell final result: '%s' (target width %d)", output, width)
|
||||
|
|
@ -407,14 +403,14 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
|||
totalLineWidth := 0 // Track total display width
|
||||
if prefix != tw.Empty {
|
||||
output.WriteString(prefix)
|
||||
totalLineWidth += tw.DisplayWidth(prefix)
|
||||
f.logger.Debugf("renderLine: Prefix='%s' (f.width %d)", prefix, tw.DisplayWidth(prefix))
|
||||
totalLineWidth += twwidth.Width(prefix)
|
||||
f.logger.Debugf("renderLine: Prefix='%s' (f.width %d)", prefix, twwidth.Width(prefix))
|
||||
}
|
||||
|
||||
colIndex := 0
|
||||
separatorDisplayWidth := 0
|
||||
if f.config.Settings.Separators.BetweenColumns.Enabled() {
|
||||
separatorDisplayWidth = tw.DisplayWidth(columnSeparator)
|
||||
separatorDisplayWidth = twwidth.Width(columnSeparator)
|
||||
}
|
||||
|
||||
// Process each column
|
||||
|
|
@ -434,7 +430,7 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
|||
prevWidth := ctx.Row.Widths.Get(colIndex - 1)
|
||||
prevCellCtx, prevOk := ctx.Row.Current[colIndex-1]
|
||||
prevIsHMergeEnd := prevOk && prevCellCtx.Merge.Horizontal.Present && prevCellCtx.Merge.Horizontal.End
|
||||
if (prevWidth > 0 || prevIsHMergeEnd) && (!ok || !(cellCtx.Merge.Horizontal.Present && !cellCtx.Merge.Horizontal.Start)) {
|
||||
if (prevWidth > 0 || prevIsHMergeEnd) && (!ok || (!cellCtx.Merge.Horizontal.Present || cellCtx.Merge.Horizontal.Start)) {
|
||||
shouldAddSeparator = true
|
||||
}
|
||||
}
|
||||
|
|
@ -453,10 +449,7 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
|||
if ctx.Row.Position == tw.Row {
|
||||
dynamicTotalWidth := 0
|
||||
for k := 0; k < span && colIndex+k < numCols; k++ {
|
||||
normWidth := ctx.NormalizedWidths.Get(colIndex + k)
|
||||
if normWidth < 0 {
|
||||
normWidth = 0
|
||||
}
|
||||
normWidth := max(ctx.NormalizedWidths.Get(colIndex+k), 0)
|
||||
dynamicTotalWidth += normWidth
|
||||
if k > 0 && separatorDisplayWidth > 0 && ctx.NormalizedWidths.Get(colIndex+k) > 0 {
|
||||
dynamicTotalWidth += separatorDisplayWidth
|
||||
|
|
@ -500,21 +493,24 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
|||
// Set cell padding and alignment
|
||||
padding := cellCtx.Padding
|
||||
align := cellCtx.Align
|
||||
if align == tw.AlignNone {
|
||||
if ctx.Row.Position == tw.Header {
|
||||
switch align {
|
||||
case tw.AlignNone:
|
||||
switch ctx.Row.Position {
|
||||
case tw.Header:
|
||||
align = tw.AlignCenter
|
||||
} else if ctx.Row.Position == tw.Footer {
|
||||
case tw.Footer:
|
||||
align = tw.AlignRight
|
||||
} else {
|
||||
default:
|
||||
align = tw.AlignLeft
|
||||
}
|
||||
f.logger.Debugf("renderLine: col %d (data: '%s') using renderer default align '%s' for position %s.", colIndex, cellCtx.Data, align, ctx.Row.Position)
|
||||
} else if align == tw.Skip {
|
||||
if ctx.Row.Position == tw.Header {
|
||||
case tw.Skip:
|
||||
switch ctx.Row.Position {
|
||||
case tw.Header:
|
||||
align = tw.AlignCenter
|
||||
} else if ctx.Row.Position == tw.Footer {
|
||||
case tw.Footer:
|
||||
align = tw.AlignRight
|
||||
} else {
|
||||
default:
|
||||
align = tw.AlignLeft
|
||||
}
|
||||
f.logger.Debugf("renderLine: col %d (data: '%s') cellCtx.Align was Skip/empty, falling back to basic default '%s'.", colIndex, cellCtx.Data, align)
|
||||
|
|
@ -522,9 +518,22 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
|||
|
||||
isTotalPattern := false
|
||||
|
||||
// Case-insensitive check for "total"
|
||||
if isHMergeStart && colIndex > 0 {
|
||||
if prevCellCtx, ok := ctx.Row.Current[colIndex-1]; ok {
|
||||
if strings.Contains(strings.ToLower(prevCellCtx.Data), "total") {
|
||||
isTotalPattern = true
|
||||
f.logger.Debugf("renderLine: total pattern in row in %d", colIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the alignment from the configuration
|
||||
align = cellCtx.Align
|
||||
|
||||
// Override alignment for footer merged cells
|
||||
if (ctx.Row.Position == tw.Footer && isHMergeStart) || isTotalPattern {
|
||||
if align != tw.AlignRight {
|
||||
if align == tw.AlignNone {
|
||||
f.logger.Debugf("renderLine: Applying AlignRight HMerge/TOTAL override for Footer col %d. Original/default align was: %s", colIndex, align)
|
||||
align = tw.AlignRight
|
||||
}
|
||||
|
|
@ -542,7 +551,7 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
|||
formattedCell := f.formatCell(cellData, visualWidth, padding, align)
|
||||
if len(formattedCell) > 0 {
|
||||
output.WriteString(formattedCell)
|
||||
cellWidth := tw.DisplayWidth(formattedCell)
|
||||
cellWidth := twwidth.Width(formattedCell)
|
||||
totalLineWidth += cellWidth
|
||||
f.logger.Debugf("renderLine: Rendered col %d, formattedCell='%s' (f.width %d), totalLineWidth=%d", colIndex, formattedCell, cellWidth, totalLineWidth)
|
||||
}
|
||||
|
|
@ -561,17 +570,18 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
|||
// Add suffix and adjust total width
|
||||
if output.Len() > len(prefix) || f.config.Borders.Right.Enabled() {
|
||||
output.WriteString(suffix)
|
||||
totalLineWidth += tw.DisplayWidth(suffix)
|
||||
f.logger.Debugf("renderLine: Suffix='%s' (f.width %d)", suffix, tw.DisplayWidth(suffix))
|
||||
totalLineWidth += twwidth.Width(suffix)
|
||||
f.logger.Debugf("renderLine: Suffix='%s' (f.width %d)", suffix, twwidth.Width(suffix))
|
||||
}
|
||||
output.WriteString(tw.NewLine)
|
||||
f.w.Write([]byte(output.String()))
|
||||
f.logger.Debugf("renderLine: Final rendered line: '%s' (total width %d)", strings.TrimSuffix(output.String(), tw.NewLine), totalLineWidth)
|
||||
}
|
||||
|
||||
// Rendition updates the Blueprint's configuration.
|
||||
func (f *Blueprint) Rendition(config tw.Rendition) {
|
||||
f.config = mergeRendition(f.config, config)
|
||||
f.logger.Debugf("Blueprint.Rendition updated. New internal config: %+v", f.config)
|
||||
f.logger.Debugf("Blueprint.Rendition updated. New config: %+v", f.config)
|
||||
}
|
||||
|
||||
// Ensure Blueprint implements tw.Renditioning
|
||||
|
|
|
|||
64
vendor/github.com/olekukonko/tablewriter/renderer/colorized.go
generated
vendored
64
vendor/github.com/olekukonko/tablewriter/renderer/colorized.go
generated
vendored
|
|
@ -1,11 +1,13 @@
|
|||
package renderer
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/olekukonko/ll"
|
||||
"github.com/olekukonko/ll/lh"
|
||||
"io"
|
||||
"strings"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
)
|
||||
|
|
@ -254,7 +256,7 @@ func (c *Colorized) Line(ctx tw.Formatting) {
|
|||
line.WriteString(strings.Repeat(tw.Space, colWidth))
|
||||
} else {
|
||||
// Calculate how many times to repeat the segment
|
||||
segmentWidth := tw.DisplayWidth(segment)
|
||||
segmentWidth := twwidth.Width(segment)
|
||||
if segmentWidth <= 0 {
|
||||
segmentWidth = 1
|
||||
}
|
||||
|
|
@ -266,7 +268,7 @@ func (c *Colorized) Line(ctx tw.Formatting) {
|
|||
line.WriteString(drawnSegment)
|
||||
|
||||
// Adjust for width discrepancies
|
||||
actualDrawnWidth := tw.DisplayWidth(drawnSegment)
|
||||
actualDrawnWidth := twwidth.Width(drawnSegment)
|
||||
if actualDrawnWidth < colWidth {
|
||||
missingWidth := colWidth - actualDrawnWidth
|
||||
spaces := strings.Repeat(tw.Space, missingWidth)
|
||||
|
|
@ -373,39 +375,33 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
|
|||
}
|
||||
|
||||
// Calculate visual width of content
|
||||
contentVisualWidth := tw.DisplayWidth(content)
|
||||
contentVisualWidth := twwidth.Width(content)
|
||||
|
||||
// Set default padding characters
|
||||
padLeftCharStr := padding.Left
|
||||
if padLeftCharStr == tw.Empty {
|
||||
padLeftCharStr = tw.Space
|
||||
}
|
||||
// if padLeftCharStr == tw.Empty {
|
||||
// padLeftCharStr = tw.Space
|
||||
//}
|
||||
padRightCharStr := padding.Right
|
||||
if padRightCharStr == tw.Empty {
|
||||
padRightCharStr = tw.Space
|
||||
}
|
||||
// if padRightCharStr == tw.Empty {
|
||||
// padRightCharStr = tw.Space
|
||||
//}
|
||||
|
||||
// Calculate padding widths
|
||||
definedPadLeftWidth := tw.DisplayWidth(padLeftCharStr)
|
||||
definedPadRightWidth := tw.DisplayWidth(padRightCharStr)
|
||||
definedPadLeftWidth := twwidth.Width(padLeftCharStr)
|
||||
definedPadRightWidth := twwidth.Width(padRightCharStr)
|
||||
// Calculate available width for content and alignment
|
||||
availableForContentAndAlign := width - definedPadLeftWidth - definedPadRightWidth
|
||||
if availableForContentAndAlign < 0 {
|
||||
availableForContentAndAlign = 0
|
||||
}
|
||||
availableForContentAndAlign := max(width-definedPadLeftWidth-definedPadRightWidth, 0)
|
||||
|
||||
// Truncate content if it exceeds available width
|
||||
if contentVisualWidth > availableForContentAndAlign {
|
||||
content = tw.TruncateString(content, availableForContentAndAlign)
|
||||
contentVisualWidth = tw.DisplayWidth(content)
|
||||
content = twwidth.Truncate(content, availableForContentAndAlign)
|
||||
contentVisualWidth = twwidth.Width(content)
|
||||
c.logger.Debugf("Truncated content to fit %d: '%s' (new width %d)", availableForContentAndAlign, content, contentVisualWidth)
|
||||
}
|
||||
|
||||
// Calculate remaining space for alignment
|
||||
remainingSpaceForAlignment := availableForContentAndAlign - contentVisualWidth
|
||||
if remainingSpaceForAlignment < 0 {
|
||||
remainingSpaceForAlignment = 0
|
||||
}
|
||||
remainingSpaceForAlignment := max(availableForContentAndAlign-contentVisualWidth, 0)
|
||||
|
||||
// Apply alignment padding
|
||||
leftAlignmentPadSpaces := tw.Empty
|
||||
|
|
@ -472,12 +468,12 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
|
|||
output := sb.String()
|
||||
|
||||
// Adjust output width if necessary
|
||||
currentVisualWidth := tw.DisplayWidth(output)
|
||||
currentVisualWidth := twwidth.Width(output)
|
||||
if currentVisualWidth != width {
|
||||
c.logger.Debugf("formatCell MISMATCH: content='%s', target_w=%d. Calculated parts width = %d. String: '%s'",
|
||||
content, width, currentVisualWidth, output)
|
||||
if currentVisualWidth > width {
|
||||
output = tw.TruncateString(output, width)
|
||||
output = twwidth.Truncate(output, width)
|
||||
} else {
|
||||
paddingSpacesStr := strings.Repeat(tw.Space, width-currentVisualWidth)
|
||||
if len(tint.BG) > 0 {
|
||||
|
|
@ -486,10 +482,10 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
|
|||
output += paddingSpacesStr
|
||||
}
|
||||
}
|
||||
c.logger.Debugf("formatCell Post-Correction: Target %d, New Visual width %d. Output: '%s'", width, tw.DisplayWidth(output), output)
|
||||
c.logger.Debugf("formatCell Post-Correction: Target %d, New Visual width %d. Output: '%s'", width, twwidth.Width(output), output)
|
||||
}
|
||||
|
||||
c.logger.Debugf("Formatted cell final result: '%s' (target width %d, display width %d)", output, width, tw.DisplayWidth(output))
|
||||
c.logger.Debugf("Formatted cell final result: '%s' (target width %d, display width %d)", output, width, twwidth.Width(output))
|
||||
return output
|
||||
}
|
||||
|
||||
|
|
@ -529,7 +525,7 @@ func (c *Colorized) renderLine(ctx tw.Formatting, line []string, tint Tint) {
|
|||
separatorString := tw.Empty
|
||||
if c.config.Settings.Separators.BetweenColumns.Enabled() {
|
||||
separatorString = c.config.Separator.Apply(c.config.Symbols.Column())
|
||||
separatorDisplayWidth = tw.DisplayWidth(c.config.Symbols.Column())
|
||||
separatorDisplayWidth = twwidth.Width(c.config.Symbols.Column())
|
||||
}
|
||||
|
||||
// Process each column
|
||||
|
|
@ -538,7 +534,7 @@ func (c *Colorized) renderLine(ctx tw.Formatting, line []string, tint Tint) {
|
|||
shouldAddSeparator := false
|
||||
if i > 0 && c.config.Settings.Separators.BetweenColumns.Enabled() {
|
||||
cellCtx, ok := ctx.Row.Current[i]
|
||||
if !ok || !(cellCtx.Merge.Horizontal.Present && !cellCtx.Merge.Horizontal.Start) {
|
||||
if !ok || (!cellCtx.Merge.Horizontal.Present || cellCtx.Merge.Horizontal.Start) {
|
||||
shouldAddSeparator = true
|
||||
}
|
||||
}
|
||||
|
|
@ -573,10 +569,7 @@ func (c *Colorized) renderLine(ctx tw.Formatting, line []string, tint Tint) {
|
|||
dynamicTotalWidth := 0
|
||||
for k := 0; k < span && i+k < numCols; k++ {
|
||||
colToSum := i + k
|
||||
normWidth := ctx.NormalizedWidths.Get(colToSum)
|
||||
if normWidth < 0 {
|
||||
normWidth = 0
|
||||
}
|
||||
normWidth := max(ctx.NormalizedWidths.Get(colToSum), 0)
|
||||
dynamicTotalWidth += normWidth
|
||||
if k > 0 && separatorDisplayWidth > 0 {
|
||||
dynamicTotalWidth += separatorDisplayWidth
|
||||
|
|
@ -632,7 +625,7 @@ func (c *Colorized) renderLine(ctx tw.Formatting, line []string, tint Tint) {
|
|||
}
|
||||
// Override alignment for footer merges or TOTAL pattern
|
||||
if (ctx.Row.Position == tw.Footer && isHMergeStart) || isTotalPattern {
|
||||
if align != tw.AlignRight {
|
||||
if align == tw.AlignNone {
|
||||
c.logger.Debugf("renderLine: Applying AlignRight override for Footer HMerge/TOTAL pattern at col %d. Original/default align was: %s", i, align)
|
||||
align = tw.AlignRight
|
||||
}
|
||||
|
|
@ -693,8 +686,7 @@ func (c *Colorized) renderLine(ctx tw.Formatting, line []string, tint Tint) {
|
|||
// Rendition updates the parts of ColorizedConfig that correspond to tw.Rendition
|
||||
// by merging the provided newRendition. Color-specific Tints are not modified.
|
||||
func (c *Colorized) Rendition(newRendition tw.Rendition) { // Method name matches interface
|
||||
c.logger.Debug("Colorized.Rendition called. Current B/Sym/Set: B:%+v, Sym:%T, S:%+v. Override: %+v",
|
||||
c.config.Borders, c.config.Symbols, c.config.Settings, newRendition)
|
||||
c.logger.Debug("Colorized.Rendition called. Current B/Sym/Set: B:%+v, Sym:%T, S:%+v. Override: %+v", c.config.Borders, c.config.Symbols, c.config.Settings, newRendition)
|
||||
|
||||
currentRenditionPart := tw.Rendition{
|
||||
Borders: c.config.Borders,
|
||||
|
|
|
|||
4
vendor/github.com/olekukonko/tablewriter/renderer/fn.go
generated
vendored
4
vendor/github.com/olekukonko/tablewriter/renderer/fn.go
generated
vendored
|
|
@ -2,6 +2,7 @@ package renderer
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
)
|
||||
|
|
@ -82,7 +83,6 @@ func defaultColorized() ColorizedConfig {
|
|||
|
||||
// defaultOceanRendererConfig returns a base tw.Rendition for the Ocean renderer.
|
||||
func defaultOceanRendererConfig() tw.Rendition {
|
||||
|
||||
return tw.Rendition{
|
||||
Borders: tw.Border{
|
||||
Left: tw.On, Right: tw.On, Top: tw.On, Bottom: tw.On,
|
||||
|
|
@ -190,7 +190,7 @@ func mergeSettings(defaults, overrides tw.Settings) tw.Settings {
|
|||
defaults.CompactMode = overrides.CompactMode
|
||||
}
|
||||
|
||||
//if overrides.Cushion != tw.Unknown {
|
||||
// if overrides.Cushion != tw.Unknown {
|
||||
// defaults.Cushion = overrides.Cushion
|
||||
//}
|
||||
|
||||
|
|
|
|||
6
vendor/github.com/olekukonko/tablewriter/renderer/html.go
generated
vendored
6
vendor/github.com/olekukonko/tablewriter/renderer/html.go
generated
vendored
|
|
@ -3,11 +3,12 @@ package renderer
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/olekukonko/ll"
|
||||
"html"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/ll"
|
||||
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
)
|
||||
|
||||
|
|
@ -63,6 +64,7 @@ func NewHTML(configs ...HTMLConfig) *HTML {
|
|||
tableStarted: false,
|
||||
tbodyStarted: false,
|
||||
tfootStarted: false,
|
||||
logger: ll.New("html"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +83,7 @@ func (h *HTML) Config() tw.Rendition {
|
|||
}
|
||||
|
||||
// debugLog appends a formatted message to the debug trace if debugging is enabled.
|
||||
//func (h *HTML) debugLog(format string, a ...interface{}) {
|
||||
// func (h *HTML) debugLog(format string, a ...interface{}) {
|
||||
// if h.debug {
|
||||
// msg := fmt.Sprintf(format, a...)
|
||||
// h.trace = append(h.trace, fmt.Sprintf("[HTML] %s", msg))
|
||||
|
|
|
|||
53
vendor/github.com/olekukonko/tablewriter/renderer/markdown.go
generated
vendored
53
vendor/github.com/olekukonko/tablewriter/renderer/markdown.go
generated
vendored
|
|
@ -1,10 +1,12 @@
|
|||
package renderer
|
||||
|
||||
import (
|
||||
"github.com/olekukonko/ll"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/ll"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
)
|
||||
|
||||
|
|
@ -35,7 +37,7 @@ func NewMarkdown(configs ...tw.Rendition) *Markdown {
|
|||
if len(configs) > 0 {
|
||||
cfg = mergeMarkdownConfig(cfg, configs[0])
|
||||
}
|
||||
return &Markdown{config: cfg}
|
||||
return &Markdown{config: cfg, logger: ll.New("markdown")}
|
||||
}
|
||||
|
||||
// mergeMarkdownConfig combines user-provided config with Markdown defaults, enforcing Markdown-specific settings.
|
||||
|
|
@ -94,7 +96,6 @@ func (m *Markdown) Row(row []string, ctx tw.Formatting) {
|
|||
m.resolveAlignment(ctx)
|
||||
m.logger.Debugf("Rendering row with data=%v, widths=%v, previous=%v, current=%v, next=%v", row, ctx.Row.Widths, ctx.Row.Previous, ctx.Row.Current, ctx.Row.Next)
|
||||
m.renderMarkdownLine(row, ctx, false)
|
||||
|
||||
}
|
||||
|
||||
// Footer renders the Markdown table footer.
|
||||
|
|
@ -154,10 +155,10 @@ func (m *Markdown) resolveAlignment(ctx tw.Formatting) tw.Alignment {
|
|||
|
||||
// formatCell formats a Markdown cell's content with padding and alignment, ensuring at least 3 characters wide.
|
||||
func (m *Markdown) formatCell(content string, width int, align tw.Align, padding tw.Padding) string {
|
||||
//if m.config.Settings.TrimWhitespace.Enabled() {
|
||||
// if m.config.Settings.TrimWhitespace.Enabled() {
|
||||
// content = strings.TrimSpace(content)
|
||||
//}
|
||||
contentVisualWidth := tw.DisplayWidth(content)
|
||||
contentVisualWidth := twwidth.Width(content)
|
||||
|
||||
// Use specified padding characters or default to spaces
|
||||
padLeftChar := padding.Left
|
||||
|
|
@ -170,16 +171,13 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding
|
|||
}
|
||||
|
||||
// Calculate padding widths
|
||||
padLeftCharWidth := tw.DisplayWidth(padLeftChar)
|
||||
padRightCharWidth := tw.DisplayWidth(padRightChar)
|
||||
padLeftCharWidth := twwidth.Width(padLeftChar)
|
||||
padRightCharWidth := twwidth.Width(padRightChar)
|
||||
minWidth := tw.Max(3, contentVisualWidth+padLeftCharWidth+padRightCharWidth)
|
||||
targetWidth := tw.Max(width, minWidth)
|
||||
|
||||
// Calculate padding
|
||||
totalPaddingNeeded := targetWidth - contentVisualWidth
|
||||
if totalPaddingNeeded < 0 {
|
||||
totalPaddingNeeded = 0
|
||||
}
|
||||
totalPaddingNeeded := max(targetWidth-contentVisualWidth, 0)
|
||||
|
||||
var leftPadStr, rightPadStr string
|
||||
switch align {
|
||||
|
|
@ -212,26 +210,27 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding
|
|||
result := leftPadStr + content + rightPadStr
|
||||
|
||||
// Adjust width if needed
|
||||
finalWidth := tw.DisplayWidth(result)
|
||||
finalWidth := twwidth.Width(result)
|
||||
if finalWidth != targetWidth {
|
||||
m.logger.Debugf("Markdown formatCell MISMATCH: content='%s', target_w=%d, paddingL='%s', paddingR='%s', align=%s -> result='%s', result_w=%d",
|
||||
content, targetWidth, padding.Left, padding.Right, align, result, finalWidth)
|
||||
adjNeeded := targetWidth - finalWidth
|
||||
if adjNeeded > 0 {
|
||||
adjStr := strings.Repeat(tw.Space, adjNeeded)
|
||||
if align == tw.AlignRight {
|
||||
switch align {
|
||||
case tw.AlignRight:
|
||||
result = adjStr + result
|
||||
} else if align == tw.AlignCenter {
|
||||
case tw.AlignCenter:
|
||||
leftAdj := adjNeeded / 2
|
||||
rightAdj := adjNeeded - leftAdj
|
||||
result = strings.Repeat(tw.Space, leftAdj) + result + strings.Repeat(tw.Space, rightAdj)
|
||||
} else {
|
||||
default:
|
||||
result += adjStr
|
||||
}
|
||||
} else {
|
||||
result = tw.TruncateString(result, targetWidth)
|
||||
result = twwidth.Truncate(result, targetWidth)
|
||||
}
|
||||
m.logger.Debugf("Markdown formatCell Corrected: target_w=%d, result='%s', new_w=%d", targetWidth, result, tw.DisplayWidth(result))
|
||||
m.logger.Debugf("Markdown formatCell Corrected: target_w=%d, result='%s', new_w=%d", targetWidth, result, twwidth.Width(result))
|
||||
}
|
||||
|
||||
m.logger.Debugf("Markdown formatCell: content='%s', width=%d, align=%s, paddingL='%s', paddingR='%s' -> '%s' (target %d)",
|
||||
|
|
@ -262,11 +261,11 @@ func (m *Markdown) formatSeparator(width int, align tw.Align) string {
|
|||
}
|
||||
|
||||
result := sb.String()
|
||||
currentLen := tw.DisplayWidth(result)
|
||||
currentLen := twwidth.Width(result)
|
||||
if currentLen < targetWidth {
|
||||
result += strings.Repeat("-", targetWidth-currentLen)
|
||||
} else if currentLen > targetWidth {
|
||||
result = tw.TruncateString(result, targetWidth)
|
||||
result = twwidth.Truncate(result, targetWidth)
|
||||
}
|
||||
|
||||
m.logger.Debugf("Markdown formatSeparator: width=%d, align=%s -> '%s'", width, align, result)
|
||||
|
|
@ -314,7 +313,7 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader
|
|||
output.WriteString(prefix)
|
||||
|
||||
colIndex := 0
|
||||
separatorWidth := tw.DisplayWidth(separator)
|
||||
separatorWidth := twwidth.Width(separator)
|
||||
|
||||
for colIndex < numCols {
|
||||
cellCtx, ok := ctx.Row.Current[colIndex]
|
||||
|
|
@ -345,10 +344,7 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader
|
|||
span = cellCtx.Merge.Horizontal.Span
|
||||
totalWidth := 0
|
||||
for k := 0; k < span && colIndex+k < numCols; k++ {
|
||||
colWidth := ctx.NormalizedWidths.Get(colIndex + k)
|
||||
if colWidth < 0 {
|
||||
colWidth = 0
|
||||
}
|
||||
colWidth := max(ctx.NormalizedWidths.Get(colIndex+k), 0)
|
||||
totalWidth += colWidth
|
||||
if k > 0 && separatorWidth > 0 {
|
||||
totalWidth += separatorWidth
|
||||
|
|
@ -387,17 +383,18 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader
|
|||
}
|
||||
// For rows, use the header's alignment if specified
|
||||
rowAlign := align
|
||||
if headerCellCtx, headerOK := ctx.Row.Previous[colIndex]; headerOK && isHeaderSep == false {
|
||||
if headerCellCtx, headerOK := ctx.Row.Previous[colIndex]; headerOK && !isHeaderSep {
|
||||
if headerCellCtx.Align != tw.AlignNone && headerCellCtx.Align != tw.Empty {
|
||||
rowAlign = headerCellCtx.Align
|
||||
}
|
||||
}
|
||||
if rowAlign == tw.AlignNone || rowAlign == tw.Empty {
|
||||
if ctx.Row.Position == tw.Header {
|
||||
switch ctx.Row.Position {
|
||||
case tw.Header:
|
||||
rowAlign = tw.AlignCenter
|
||||
} else if ctx.Row.Position == tw.Footer {
|
||||
case tw.Footer:
|
||||
rowAlign = tw.AlignRight
|
||||
} else {
|
||||
default:
|
||||
rowAlign = tw.AlignLeft
|
||||
}
|
||||
m.logger.Debugf("renderMarkdownLine: Col %d using default align '%s'", colIndex, rowAlign)
|
||||
|
|
|
|||
81
vendor/github.com/olekukonko/tablewriter/renderer/ocean.go
generated
vendored
81
vendor/github.com/olekukonko/tablewriter/renderer/ocean.go
generated
vendored
|
|
@ -2,15 +2,17 @@ package renderer
|
|||
|
||||
import (
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
|
||||
"github.com/olekukonko/ll"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
)
|
||||
|
||||
// OceanConfig defines configuration specific to the Ocean renderer.
|
||||
type OceanConfig struct {
|
||||
}
|
||||
type OceanConfig struct{}
|
||||
|
||||
// Ocean is a streaming table renderer that writes ASCII tables.
|
||||
type Ocean struct {
|
||||
|
|
@ -106,17 +108,9 @@ func (o *Ocean) Header(headers [][]string, ctx tw.Formatting) {
|
|||
if !o.widthsFinalized {
|
||||
o.tryFinalizeWidths(ctx)
|
||||
}
|
||||
// The batch renderer (table.go/renderHeader) will call Line() for the top border
|
||||
// and for the header separator if its main config t.config says so.
|
||||
// So, Ocean.Header should *not* draw these itself when in batch mode.
|
||||
// For true streaming, table.go's streamRenderHeader would make these Line() calls.
|
||||
|
||||
// Decision: Ocean.Header *only* renders header content.
|
||||
// Lines (top border, header separator) are managed by the caller (batch or stream logic in table.go).
|
||||
|
||||
if !o.widthsFinalized {
|
||||
o.logger.Error("Ocean.Header: Cannot render content, widths are not finalized.")
|
||||
// o.headerContentRendered = true; // No, content wasn't rendered.
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -133,10 +127,7 @@ func (o *Ocean) Header(headers [][]string, ctx tw.Formatting) {
|
|||
o.headerContentRendered = true
|
||||
} else {
|
||||
o.logger.Debug("Ocean.Header: No actual header content lines to render.")
|
||||
// If header is empty, table.go's renderHeader might still call Line() for the separator.
|
||||
// o.headerContentRendered remains false if no content.
|
||||
}
|
||||
// DO NOT draw the header separator line here. Let table.go's renderHeader or streamRenderHeader call o.Line().
|
||||
}
|
||||
|
||||
func (o *Ocean) Row(row []string, ctx tw.Formatting) {
|
||||
|
|
@ -145,15 +136,6 @@ func (o *Ocean) Row(row []string, ctx tw.Formatting) {
|
|||
if !o.widthsFinalized {
|
||||
o.tryFinalizeWidths(ctx)
|
||||
}
|
||||
// Top border / header separator logic:
|
||||
// If this is the very first output, table.go's batch renderHeader (or streamRenderHeader)
|
||||
// should have already called Line() for top border and header separator.
|
||||
// If Header() was called but rendered no content, table.go's renderHeader would still call Line() for the separator.
|
||||
// If Header() was never called by table.go (e.g. streaming rows directly after Start()),
|
||||
// then table.go's streamAppendRow needs to handle initial lines.
|
||||
|
||||
// Decision: Ocean.Row *only* renders row content.
|
||||
|
||||
if !o.widthsFinalized {
|
||||
o.logger.Error("Ocean.Row: Cannot render content, widths are not finalized.")
|
||||
return
|
||||
|
|
@ -171,11 +153,6 @@ func (o *Ocean) Footer(footers [][]string, ctx tw.Formatting) {
|
|||
o.tryFinalizeWidths(ctx)
|
||||
o.logger.Warn("Ocean.Footer: Widths finalized at Footer stage (unusual).")
|
||||
}
|
||||
// Separator line before footer:
|
||||
// This should be handled by table.go's renderFooter or streamRenderFooter calling o.Line().
|
||||
|
||||
// Decision: Ocean.Footer *only* renders footer content.
|
||||
|
||||
if !o.widthsFinalized {
|
||||
o.logger.Error("Ocean.Footer: Cannot render content, widths are not finalized.")
|
||||
return
|
||||
|
|
@ -194,24 +171,19 @@ func (o *Ocean) Footer(footers [][]string, ctx tw.Formatting) {
|
|||
} else {
|
||||
o.logger.Debug("Ocean.Footer: No actual footer content lines to render.")
|
||||
}
|
||||
// DO NOT draw the bottom border here. Let table.go's main Close or batch renderFooter call o.Line().
|
||||
}
|
||||
|
||||
func (o *Ocean) Line(ctx tw.Formatting) {
|
||||
// This method is now called EXTERNALLY by table.go's batch or stream logic
|
||||
// to draw all horizontal lines (top border, header sep, footer sep, bottom border).
|
||||
if !o.widthsFinalized {
|
||||
// If Line is called before widths are known (e.g. table.go's batch renderHeader trying to draw top border)
|
||||
// we must try to finalize widths from this context.
|
||||
o.tryFinalizeWidths(ctx)
|
||||
if !o.widthsFinalized {
|
||||
o.logger.Error("Ocean.Line: Called but widths could not be finalized. Skipping line rendering.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure Line uses the consistent fixedWidths for drawing
|
||||
ctx.Row.Widths = o.fixedWidths
|
||||
|
||||
o.logger.Debugf("Ocean.Line DRAWING: Level=%v, Loc=%s, Pos=%s, IsSubRow=%t, WidthsLen=%d", ctx.Level, ctx.Row.Location, ctx.Row.Position, ctx.IsSubRow, ctx.Row.Widths.Len())
|
||||
|
||||
jr := NewJunction(JunctionContext{
|
||||
|
|
@ -262,7 +234,7 @@ func (o *Ocean) Line(ctx tw.Formatting) {
|
|||
if segmentChar == tw.Empty {
|
||||
segmentChar = o.config.Symbols.Row()
|
||||
}
|
||||
segmentDisplayWidth := tw.DisplayWidth(segmentChar)
|
||||
segmentDisplayWidth := twwidth.Width(segmentChar)
|
||||
if segmentDisplayWidth <= 0 {
|
||||
segmentDisplayWidth = 1
|
||||
}
|
||||
|
|
@ -296,12 +268,6 @@ func (o *Ocean) Line(ctx tw.Formatting) {
|
|||
|
||||
func (o *Ocean) Close() error {
|
||||
o.logger.Debug("Ocean.Close() called.")
|
||||
// The actual bottom border drawing is expected to be handled by table.go's
|
||||
// batch render logic (renderFooter) or stream logic (streamRenderBottomBorder)
|
||||
// by making an explicit call to o.Line() with the correct context.
|
||||
// Ocean.Close() itself does not draw the bottom border to avoid duplication.
|
||||
|
||||
// Only reset state.
|
||||
o.resetState()
|
||||
return nil
|
||||
}
|
||||
|
|
@ -362,19 +328,16 @@ func (o *Ocean) renderContentLine(ctx tw.Formatting, lineData []string) {
|
|||
idxInMergeSpan := colIdx + k
|
||||
// Check if idxInMergeSpan is a defined column in fixedWidths
|
||||
foundInFixedWidths := false
|
||||
for _, sortedCIdx_inner := range sortedColIndices {
|
||||
if sortedCIdx_inner == idxInMergeSpan {
|
||||
currentMergeTotalRenderWidth += o.fixedWidths.Get(idxInMergeSpan)
|
||||
foundInFixedWidths = true
|
||||
break
|
||||
}
|
||||
if slices.Contains(sortedColIndices, idxInMergeSpan) {
|
||||
currentMergeTotalRenderWidth += o.fixedWidths.Get(idxInMergeSpan)
|
||||
foundInFixedWidths = true
|
||||
}
|
||||
if !foundInFixedWidths && idxInMergeSpan <= sortedColIndices[len(sortedColIndices)-1] {
|
||||
o.logger.Debugf("Col %d in HMerge span not found in fixedWidths, assuming 0-width contribution.", idxInMergeSpan)
|
||||
}
|
||||
|
||||
if k < hSpan-1 && o.config.Settings.Separators.BetweenColumns.Enabled() {
|
||||
currentMergeTotalRenderWidth += tw.DisplayWidth(o.config.Symbols.Column())
|
||||
currentMergeTotalRenderWidth += twwidth.Width(o.config.Symbols.Column())
|
||||
}
|
||||
}
|
||||
actualCellWidthToRender = currentMergeTotalRenderWidth
|
||||
|
|
@ -428,7 +391,7 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
|
|||
return tw.Empty
|
||||
}
|
||||
|
||||
contentDisplayWidth := tw.DisplayWidth(content)
|
||||
contentDisplayWidth := twwidth.Width(content)
|
||||
|
||||
padLeftChar := padding.Left
|
||||
if padLeftChar == tw.Empty {
|
||||
|
|
@ -439,23 +402,17 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
|
|||
padRightChar = tw.Space
|
||||
}
|
||||
|
||||
padLeftDisplayWidth := tw.DisplayWidth(padLeftChar)
|
||||
padRightDisplayWidth := tw.DisplayWidth(padRightChar)
|
||||
padLeftDisplayWidth := twwidth.Width(padLeftChar)
|
||||
padRightDisplayWidth := twwidth.Width(padRightChar)
|
||||
|
||||
spaceForContentAndAlignment := cellVisualWidth - padLeftDisplayWidth - padRightDisplayWidth
|
||||
if spaceForContentAndAlignment < 0 {
|
||||
spaceForContentAndAlignment = 0
|
||||
}
|
||||
spaceForContentAndAlignment := max(cellVisualWidth-padLeftDisplayWidth-padRightDisplayWidth, 0)
|
||||
|
||||
if contentDisplayWidth > spaceForContentAndAlignment {
|
||||
content = tw.TruncateString(content, spaceForContentAndAlignment)
|
||||
contentDisplayWidth = tw.DisplayWidth(content)
|
||||
content = twwidth.Truncate(content, spaceForContentAndAlignment)
|
||||
contentDisplayWidth = twwidth.Width(content)
|
||||
}
|
||||
|
||||
remainingSpace := spaceForContentAndAlignment - contentDisplayWidth
|
||||
if remainingSpace < 0 {
|
||||
remainingSpace = 0
|
||||
}
|
||||
remainingSpace := max(spaceForContentAndAlignment-contentDisplayWidth, 0)
|
||||
|
||||
var PL, PR string
|
||||
switch align {
|
||||
|
|
@ -477,7 +434,7 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
|
|||
sb.WriteString(PR)
|
||||
sb.WriteString(padRightChar)
|
||||
|
||||
currentFormattedWidth := tw.DisplayWidth(sb.String())
|
||||
currentFormattedWidth := twwidth.Width(sb.String())
|
||||
if currentFormattedWidth < cellVisualWidth {
|
||||
if align == tw.AlignRight {
|
||||
prefixSpaces := strings.Repeat(tw.Space, cellVisualWidth-currentFormattedWidth)
|
||||
|
|
@ -490,7 +447,7 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
|
|||
} else if currentFormattedWidth > cellVisualWidth {
|
||||
tempStr := sb.String()
|
||||
sb.Reset()
|
||||
sb.WriteString(tw.TruncateString(tempStr, cellVisualWidth))
|
||||
sb.WriteString(twwidth.Truncate(tempStr, cellVisualWidth))
|
||||
o.logger.Warnf("formatCellContent: Final string '%s' (width %d) exceeded target %d. Force truncated.", tempStr, currentFormattedWidth, cellVisualWidth)
|
||||
}
|
||||
return sb.String()
|
||||
|
|
|
|||
16
vendor/github.com/olekukonko/tablewriter/renderer/svg.go
generated
vendored
16
vendor/github.com/olekukonko/tablewriter/renderer/svg.go
generated
vendored
|
|
@ -2,11 +2,12 @@ package renderer
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/olekukonko/ll"
|
||||
"html"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/ll"
|
||||
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
)
|
||||
|
||||
|
|
@ -138,6 +139,7 @@ func NewSVG(configs ...SVGConfig) *SVG {
|
|||
allVisualLineData: make([][][]string, 3),
|
||||
allVisualLineCtx: make([][]tw.Formatting, 3),
|
||||
vMergeTrack: make(map[int]int),
|
||||
logger: ll.New("svg"),
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
r.allVisualLineData[i] = make([][]string, 0)
|
||||
|
|
@ -511,10 +513,7 @@ func (s *SVG) renderVisualLine(visualLineData []string, ctx tw.Formatting, posit
|
|||
}
|
||||
s.dataRowCounter++
|
||||
} else {
|
||||
parentDataRowStripeIndex := s.dataRowCounter - 1
|
||||
if parentDataRowStripeIndex < 0 {
|
||||
parentDataRowStripeIndex = 0
|
||||
}
|
||||
parentDataRowStripeIndex := max(s.dataRowCounter-1, 0)
|
||||
if s.config.RowAltBG != tw.Empty && parentDataRowStripeIndex%2 != 0 {
|
||||
bgColor = s.config.RowAltBG
|
||||
} else {
|
||||
|
|
@ -622,9 +621,10 @@ func (s *SVG) renderVisualLine(visualLineData []string, ctx tw.Formatting, posit
|
|||
}
|
||||
}
|
||||
textX := currentX + s.config.Padding
|
||||
if cellTextAnchor == "middle" {
|
||||
switch cellTextAnchor {
|
||||
case "middle":
|
||||
textX = currentX + s.config.Padding + (rectWidth-2*s.config.Padding)/2.0
|
||||
} else if cellTextAnchor == "end" {
|
||||
case "end":
|
||||
textX = currentX + rectWidth - s.config.Padding
|
||||
}
|
||||
textY := s.currentY + rectHeight/2.0
|
||||
|
|
@ -685,7 +685,7 @@ func (s *SVG) Start(w io.Writer) error {
|
|||
func (s *SVG) debug(format string, a ...interface{}) {
|
||||
if s.config.Debug {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
s.trace = append(s.trace, fmt.Sprintf("[SVG] %s", msg))
|
||||
s.trace = append(s.trace, "[SVG] "+msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
74
vendor/github.com/olekukonko/tablewriter/stream.go
generated
vendored
74
vendor/github.com/olekukonko/tablewriter/stream.go
generated
vendored
|
|
@ -1,10 +1,11 @@
|
|||
package tablewriter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/olekukonko/errors"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
"math"
|
||||
|
||||
"github.com/olekukonko/errors"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
)
|
||||
|
||||
// Close finalizes the table stream.
|
||||
|
|
@ -89,11 +90,11 @@ func (t *Table) Start() error {
|
|||
if !t.renderer.Config().Streaming {
|
||||
// Check if the configured renderer actually supports streaming.
|
||||
t.logger.Error("Configured renderer does not support streaming.")
|
||||
return fmt.Errorf("renderer does not support streaming")
|
||||
return errors.Newf("renderer does not support streaming")
|
||||
}
|
||||
|
||||
//t.renderer.Start(t.writer)
|
||||
//t.renderer.Logger(t.logger)
|
||||
// t.renderer.Start(t.writer)
|
||||
// t.renderer.Logger(t.logger)
|
||||
|
||||
if t.hasPrinted {
|
||||
// Prevent calling Start() multiple times on the same stream instance.
|
||||
|
|
@ -119,7 +120,7 @@ func (t *Table) Start() error {
|
|||
t.streamWidths = t.config.Widths.PerColumn.Clone()
|
||||
// Determine numCols from the highest index in PerColumn map
|
||||
maxColIdx := -1
|
||||
t.streamWidths.Each(func(col int, width int) {
|
||||
t.streamWidths.Each(func(col, width int) {
|
||||
if col > maxColIdx {
|
||||
maxColIdx = col
|
||||
}
|
||||
|
|
@ -207,7 +208,7 @@ func (t *Table) streamAppendRow(row interface{}) error {
|
|||
rawCellsSlice, err := t.convertCellsToStrings(row, t.config.Row)
|
||||
if err != nil {
|
||||
t.logger.Errorf("streamAppendRow: Failed to convert row to strings: %v", err)
|
||||
return fmt.Errorf("failed to convert row to strings: %w", err)
|
||||
return errors.Newf("failed to convert row to strings").Wrap(err)
|
||||
}
|
||||
|
||||
if len(rawCellsSlice) == 0 {
|
||||
|
|
@ -220,21 +221,34 @@ func (t *Table) streamAppendRow(row interface{}) error {
|
|||
}
|
||||
|
||||
if err := t.ensureStreamWidthsCalculated(rawCellsSlice, t.config.Row); err != nil {
|
||||
return err
|
||||
return errors.New("failed to establish stream column count/widths").Wrap(err)
|
||||
}
|
||||
|
||||
if t.streamNumCols > 0 && len(rawCellsSlice) != t.streamNumCols {
|
||||
t.logger.Warnf("streamAppendRow: Input row column count (%d) != stream column count (%d). Padding/Truncating.", len(rawCellsSlice), t.streamNumCols)
|
||||
if len(rawCellsSlice) < t.streamNumCols {
|
||||
paddedCells := make([]string, t.streamNumCols)
|
||||
copy(paddedCells, rawCellsSlice)
|
||||
for i := len(rawCellsSlice); i < t.streamNumCols; i++ {
|
||||
paddedCells[i] = tw.Empty
|
||||
// Now, check for column mismatch if a column count has been established.
|
||||
if t.streamNumCols > 0 {
|
||||
if len(rawCellsSlice) != t.streamNumCols {
|
||||
if t.config.Stream.StrictColumns {
|
||||
err := errors.Newf("input row column count (%d) does not match established stream column count (%d) and StrictColumns is enabled", len(rawCellsSlice), t.streamNumCols)
|
||||
t.logger.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
// If not strict, retain the old lenient behavior (warn and pad/truncate)
|
||||
t.logger.Warnf("streamAppendRow: Input row column count (%d) != stream column count (%d). Padding/Truncating (StrictColumns is false).", len(rawCellsSlice), t.streamNumCols)
|
||||
if len(rawCellsSlice) < t.streamNumCols {
|
||||
paddedCells := make([]string, t.streamNumCols)
|
||||
copy(paddedCells, rawCellsSlice)
|
||||
for i := len(rawCellsSlice); i < t.streamNumCols; i++ {
|
||||
paddedCells[i] = tw.Empty
|
||||
}
|
||||
rawCellsSlice = paddedCells
|
||||
} else {
|
||||
rawCellsSlice = rawCellsSlice[:t.streamNumCols]
|
||||
}
|
||||
rawCellsSlice = paddedCells
|
||||
} else {
|
||||
rawCellsSlice = rawCellsSlice[:t.streamNumCols]
|
||||
}
|
||||
} else if len(rawCellsSlice) > 0 && t.config.Stream.StrictColumns {
|
||||
err := errors.Newf("failed to establish stream column count from first data row (%d cells) and StrictColumns is enabled", len(rawCellsSlice))
|
||||
t.logger.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if t.streamNumCols == 0 {
|
||||
|
|
@ -318,7 +332,7 @@ func (t *Table) streamAppendRow(row interface{}) error {
|
|||
t.logger.Debug("streamAppendRow: Separator line rendered. Updated lastRenderedPosition to 'separator'.")
|
||||
} else {
|
||||
details := ""
|
||||
if !(shouldDrawHeaderRowSeparator || shouldDrawRowRowSeparator) {
|
||||
if !shouldDrawHeaderRowSeparator && !shouldDrawRowRowSeparator {
|
||||
details = "neither header/row nor row/row separator was flagged true"
|
||||
} else if t.lastRenderedPosition == tw.Position("separator") {
|
||||
details = "lastRenderedPosition is already 'separator'"
|
||||
|
|
@ -462,7 +476,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
|||
determinedNumCols := 0
|
||||
if t.config.Widths.PerColumn != nil && t.config.Widths.PerColumn.Len() > 0 {
|
||||
maxColIdx := -1
|
||||
t.config.Widths.PerColumn.Each(func(col int, width int) {
|
||||
t.config.Widths.PerColumn.Each(func(col, width int) {
|
||||
if col > maxColIdx {
|
||||
maxColIdx = col
|
||||
}
|
||||
|
|
@ -511,7 +525,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
|||
|
||||
ellipsisWidthBuffer := 0
|
||||
if autoWrapForWidthCalc == tw.WrapTruncate {
|
||||
ellipsisWidthBuffer = tw.DisplayWidth(tw.CharEllipsis)
|
||||
ellipsisWidthBuffer = twwidth.Width(tw.CharEllipsis)
|
||||
}
|
||||
varianceBuffer := 2 // Your suggested variance
|
||||
minTotalColWidth := tw.MinimumColumnWidth
|
||||
|
|
@ -525,14 +539,14 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
|||
if i < len(sampling) {
|
||||
sampleContent = t.Trimmer(sampling[i])
|
||||
}
|
||||
sampleContentDisplayWidth := tw.DisplayWidth(sampleContent)
|
||||
sampleContentDisplayWidth := twwidth.Width(sampleContent)
|
||||
|
||||
colPad := paddingForWidthCalc.Global
|
||||
if i < len(paddingForWidthCalc.PerColumn) && paddingForWidthCalc.PerColumn[i].Paddable() {
|
||||
colPad = paddingForWidthCalc.PerColumn[i]
|
||||
}
|
||||
currentPadLWidth := tw.DisplayWidth(colPad.Left)
|
||||
currentPadRWidth := tw.DisplayWidth(colPad.Right)
|
||||
currentPadLWidth := twwidth.Width(colPad.Left)
|
||||
currentPadRWidth := twwidth.Width(colPad.Right)
|
||||
currentTotalPaddingWidth := currentPadLWidth + currentPadRWidth
|
||||
|
||||
// Start with the target content width logic
|
||||
|
|
@ -587,7 +601,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
|||
if t.config.Widths.Global > 0 && t.streamNumCols > 0 {
|
||||
t.logger.Debug("streamCalculateWidths: Applying global stream width constraint %d", t.config.Widths.Global)
|
||||
currentTotalColumnWidthsSum := 0
|
||||
t.streamWidths.Each(func(_ int, w int) {
|
||||
t.streamWidths.Each(func(_, w int) {
|
||||
currentTotalColumnWidthsSum += w
|
||||
})
|
||||
|
||||
|
|
@ -595,7 +609,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
|||
if t.renderer != nil {
|
||||
rendererConfig := t.renderer.Config()
|
||||
if rendererConfig.Settings.Separators.BetweenColumns.Enabled() {
|
||||
separatorWidth = tw.DisplayWidth(rendererConfig.Symbols.Column())
|
||||
separatorWidth = twwidth.Width(rendererConfig.Symbols.Column())
|
||||
}
|
||||
} else {
|
||||
separatorWidth = 1 // Default if renderer not available yet
|
||||
|
|
@ -652,7 +666,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
|||
// Distribute remainingSpace (positive or negative) among non-zero width columns
|
||||
if remainingSpace != 0 && t.streamNumCols > 0 {
|
||||
colsToAdjust := []int{}
|
||||
t.streamWidths.Each(func(col int, w int) {
|
||||
t.streamWidths.Each(func(col, w int) {
|
||||
if w > 0 { // Only consider columns that currently have width
|
||||
colsToAdjust = append(colsToAdjust, col)
|
||||
}
|
||||
|
|
@ -676,7 +690,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
|||
}
|
||||
|
||||
// Final sanitization
|
||||
t.streamWidths.Each(func(col int, width int) {
|
||||
t.streamWidths.Each(func(col, width int) {
|
||||
if width < 0 {
|
||||
t.streamWidths.Set(col, 0)
|
||||
}
|
||||
|
|
@ -845,7 +859,7 @@ func (t *Table) streamRenderFooter(processedFooterLines [][]string) error {
|
|||
// If this is the last line of the last content block (footer), and no bottom border will be drawn,
|
||||
// its Location should be End.
|
||||
isLastLineOfTableContent := (i == totalFooterLines-1) &&
|
||||
!(cfg.Borders.Bottom.Enabled() && cfg.Settings.Lines.ShowBottom.Enabled())
|
||||
(!cfg.Borders.Bottom.Enabled() || !cfg.Settings.Lines.ShowBottom.Enabled())
|
||||
if isLastLineOfTableContent {
|
||||
resp.location = tw.LocationEnd
|
||||
t.logger.Debug("streamRenderFooter: Setting LocationEnd for last footer line as no bottom border will follow.")
|
||||
|
|
|
|||
248
vendor/github.com/olekukonko/tablewriter/tablewriter.go
generated
vendored
248
vendor/github.com/olekukonko/tablewriter/tablewriter.go
generated
vendored
|
|
@ -2,23 +2,27 @@ package tablewriter
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/olekukonko/errors"
|
||||
"github.com/olekukonko/ll"
|
||||
"github.com/olekukonko/ll/lh"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwarp"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
"github.com/olekukonko/tablewriter/renderer"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Table represents a table instance with content and rendering capabilities.
|
||||
type Table struct {
|
||||
writer io.Writer // Destination for table output
|
||||
counters []tw.Counter // Counters for indices
|
||||
rows [][][]string // Row data, supporting multi-line cells
|
||||
headers [][]string // Header content
|
||||
footers [][]string // Footer content
|
||||
|
|
@ -177,65 +181,87 @@ func (t *Table) Caption(caption tw.Caption) *Table { // This is the one we modif
|
|||
// This method always contributes to a single logical row in the table.
|
||||
// To add multiple distinct rows, call Append multiple times (once for each row's data)
|
||||
// or use the Bulk() method if providing a slice where each element is a row.
|
||||
func (t *Table) Append(rows ...interface{}) error { // rows is already []interface{}
|
||||
func (t *Table) Append(rows ...interface{}) error {
|
||||
t.ensureInitialized()
|
||||
|
||||
if t.config.Stream.Enable && t.hasPrinted {
|
||||
// Streaming logic remains unchanged, as AutoHeader is a batch-mode concept.
|
||||
t.logger.Debugf("Append() called in streaming mode with %d items for a single row", len(rows))
|
||||
var rowItemForStream interface{}
|
||||
if len(rows) == 1 {
|
||||
rowItemForStream = rows[0]
|
||||
} else {
|
||||
rowItemForStream = rows // Pass the slice of items if multiple args
|
||||
rowItemForStream = rows
|
||||
}
|
||||
if err := t.streamAppendRow(rowItemForStream); err != nil {
|
||||
t.logger.Errorf("Error rendering streaming row: %v", err)
|
||||
return fmt.Errorf("failed to stream append row: %w", err)
|
||||
return errors.Newf("failed to stream append row").Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//Batch Mode Logic
|
||||
// Batch Mode Logic
|
||||
t.logger.Debugf("Append (Batch) received %d arguments: %v", len(rows), rows)
|
||||
|
||||
var cellsSource interface{}
|
||||
if len(rows) == 1 {
|
||||
cellsSource = rows[0]
|
||||
t.logger.Debug("Append (Batch): Single argument provided. Treating it as the source for row cells.")
|
||||
} else {
|
||||
cellsSource = rows // 'rows' is []interface{} containing all arguments
|
||||
t.logger.Debug("Append (Batch): Multiple arguments provided. Treating them directly as cells for one row.")
|
||||
cellsSource = rows
|
||||
}
|
||||
// Check if we should attempt to auto-generate headers from this append operation.
|
||||
// Conditions: AutoHeader is on, no headers are set yet, and this is the first data row.
|
||||
isFirstRow := len(t.rows) == 0
|
||||
if t.config.Behavior.Structs.AutoHeader.Enabled() && len(t.headers) == 0 && isFirstRow {
|
||||
t.logger.Debug("Append: Triggering AutoHeader for the first row.")
|
||||
headers := t.extractHeadersFromStruct(cellsSource)
|
||||
if len(headers) > 0 {
|
||||
// Set the extracted headers. The Header() method handles the rest.
|
||||
t.Header(headers)
|
||||
}
|
||||
}
|
||||
|
||||
if err := t.appendSingle(cellsSource); err != nil {
|
||||
// The rest of the function proceeds as before, converting the data to string lines.
|
||||
lines, err := t.toStringLines(cellsSource, t.config.Row)
|
||||
if err != nil {
|
||||
t.logger.Errorf("Append (Batch) failed for cellsSource %v: %v", cellsSource, err)
|
||||
return err
|
||||
}
|
||||
t.rows = append(t.rows, lines)
|
||||
|
||||
t.logger.Debugf("Append (Batch) completed for one row, total rows in table: %d", len(t.rows))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bulk adds multiple rows from a slice to the table (legacy method).
|
||||
// Parameter rows must be a slice compatible with stringer or []string.
|
||||
// Returns an error if the input is invalid or appending fails.
|
||||
// Bulk adds multiple rows from a slice to the table.
|
||||
// If Behavior.AutoHeader is enabled, no headers set, and rows is a slice of structs,
|
||||
// automatically extracts/sets headers from the first struct.
|
||||
func (t *Table) Bulk(rows interface{}) error {
|
||||
t.logger.Debug("Starting Bulk operation")
|
||||
rv := reflect.ValueOf(rows)
|
||||
if rv.Kind() != reflect.Slice {
|
||||
err := errors.Newf("Bulk expects a slice, got %T", rows)
|
||||
t.logger.Debugf("Bulk error: %v", err)
|
||||
return err
|
||||
return errors.Newf("Bulk expects a slice, got %T", rows)
|
||||
}
|
||||
if rv.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AutoHeader logic remains here, as it's a "Bulk" operation concept.
|
||||
if t.config.Behavior.Structs.AutoHeader.Enabled() && len(t.headers) == 0 {
|
||||
first := rv.Index(0).Interface()
|
||||
// We can now correctly get headers from pointers or embedded structs
|
||||
headers := t.extractHeadersFromStruct(first)
|
||||
if len(headers) > 0 {
|
||||
t.Header(headers)
|
||||
}
|
||||
}
|
||||
|
||||
// The rest of the logic is now just a loop over Append.
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
row := rv.Index(i).Interface()
|
||||
t.logger.Debugf("Processing bulk row %d: %v", i, row)
|
||||
if err := t.appendSingle(row); err != nil {
|
||||
t.logger.Debugf("Bulk append failed at index %d: %v", i, err)
|
||||
if err := t.Append(row); err != nil { // Use Append
|
||||
return err
|
||||
}
|
||||
}
|
||||
t.logger.Debugf("Bulk completed, processed %d rows", rv.Len())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -386,7 +412,6 @@ func (t *Table) Footer(elements ...any) {
|
|||
// Parameter opts is a function that modifies the Table struct.
|
||||
// Returns the Table instance for method chaining.
|
||||
func (t *Table) Options(opts ...Option) *Table {
|
||||
|
||||
// add logger
|
||||
if t.logger == nil {
|
||||
t.logger = ll.New("table").Handler(lh.NewTextHandler(t.trace))
|
||||
|
|
@ -399,7 +424,7 @@ func (t *Table) Options(opts ...Option) *Table {
|
|||
|
||||
// force debugging mode if set
|
||||
// This should be move away form WithDebug
|
||||
if t.config.Debug == true {
|
||||
if t.config.Debug {
|
||||
t.logger.Enable()
|
||||
t.logger.Resume()
|
||||
} else {
|
||||
|
|
@ -407,10 +432,14 @@ func (t *Table) Options(opts ...Option) *Table {
|
|||
t.logger.Suspend()
|
||||
}
|
||||
|
||||
// help resolve from deprecation
|
||||
//if t.config.Stream.Enable {
|
||||
// t.config.Widths = t.config.Stream.Widths
|
||||
//}
|
||||
// Get additional system information for debugging
|
||||
goVersion := runtime.Version()
|
||||
goOS := runtime.GOOS
|
||||
goArch := runtime.GOARCH
|
||||
numCPU := runtime.NumCPU()
|
||||
|
||||
t.logger.Infof("Environment: LC_CTYPE=%s, LANG=%s, TERM=%s", os.Getenv("LC_CTYPE"), os.Getenv("LANG"), os.Getenv("TERM"))
|
||||
t.logger.Infof("Go Runtime: Version=%s, OS=%s, Arch=%s, CPUs=%d", goVersion, goOS, goArch, numCPU)
|
||||
|
||||
// send logger to renderer
|
||||
// this will overwrite the default logger
|
||||
|
|
@ -482,6 +511,28 @@ func (t *Table) Render() error {
|
|||
return t.render()
|
||||
}
|
||||
|
||||
// Lines returns the total number of lines rendered.
|
||||
// This method is only effective if the WithLineCounter() option was used during
|
||||
// table initialization and must be called *after* Render().
|
||||
// It actively searches for the default tw.LineCounter among all active counters.
|
||||
// It returns -1 if the line counter was not enabled.
|
||||
func (t *Table) Lines() int {
|
||||
for _, counter := range t.counters {
|
||||
if lc, ok := counter.(*tw.LineCounter); ok {
|
||||
return lc.Total()
|
||||
}
|
||||
}
|
||||
// use -1 to indicate no line counter is attached
|
||||
return -1
|
||||
}
|
||||
|
||||
// Counters returns the slice of all active counter instances.
|
||||
// This is useful when multiple counters are enabled.
|
||||
// It must be called *after* Render().
|
||||
func (t *Table) Counters() []tw.Counter {
|
||||
return t.counters
|
||||
}
|
||||
|
||||
// Trimmer trims whitespace from a string based on the Table’s configuration.
|
||||
// It conditionally applies strings.TrimSpace to the input string if the TrimSpace behavior
|
||||
// is enabled in t.config.Behavior, otherwise returning the string unchanged. This method
|
||||
|
|
@ -635,7 +686,7 @@ func (t *Table) finalizeHierarchicalMergeBlock(ctx *renderContext, mctx *mergeCo
|
|||
startState.Hierarchical.Present = true
|
||||
startState.Hierarchical.Start = true
|
||||
startState.Hierarchical.Span = finalSpan
|
||||
startState.Hierarchical.End = (finalSpan == 1)
|
||||
startState.Hierarchical.End = finalSpan == 1
|
||||
mctx.rowMerges[startRow][col] = startState
|
||||
}
|
||||
|
||||
|
|
@ -756,7 +807,7 @@ func (t *Table) printTopBottomCaption(w io.Writer, actualTableWidth int) {
|
|||
captionWrapWidth = t.caption.Width
|
||||
t.logger.Debugf("[printCaption] Using user-defined caption.Width %d for wrapping.", captionWrapWidth)
|
||||
} else if actualTableWidth <= 4 {
|
||||
captionWrapWidth = tw.DisplayWidth(t.caption.Text)
|
||||
captionWrapWidth = twwidth.Width(t.caption.Text)
|
||||
t.logger.Debugf("[printCaption] Empty table, no user caption.Width: Using natural caption width %d.", captionWrapWidth)
|
||||
} else {
|
||||
captionWrapWidth = actualTableWidth
|
||||
|
|
@ -879,8 +930,8 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
|
|||
colPad = config.Padding.PerColumn[i]
|
||||
}
|
||||
|
||||
padLeftWidth := tw.DisplayWidth(colPad.Left)
|
||||
padRightWidth := tw.DisplayWidth(colPad.Right)
|
||||
padLeftWidth := twwidth.Width(colPad.Left)
|
||||
padRightWidth := twwidth.Width(colPad.Right)
|
||||
|
||||
effectiveContentMaxWidth := t.calculateContentMaxWidth(i, config, padLeftWidth, padRightWidth, isStreaming)
|
||||
|
||||
|
|
@ -902,12 +953,12 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
|
|||
}
|
||||
finalLinesForCell = append(finalLinesForCell, wrapped...)
|
||||
case tw.WrapTruncate:
|
||||
if tw.DisplayWidth(line) > effectiveContentMaxWidth {
|
||||
ellipsisWidth := tw.DisplayWidth(tw.CharEllipsis)
|
||||
if twwidth.Width(line) > effectiveContentMaxWidth {
|
||||
ellipsisWidth := twwidth.Width(tw.CharEllipsis)
|
||||
if effectiveContentMaxWidth >= ellipsisWidth {
|
||||
finalLinesForCell = append(finalLinesForCell, tw.TruncateString(line, effectiveContentMaxWidth-ellipsisWidth, tw.CharEllipsis))
|
||||
finalLinesForCell = append(finalLinesForCell, twwidth.Truncate(line, effectiveContentMaxWidth-ellipsisWidth, tw.CharEllipsis))
|
||||
} else {
|
||||
finalLinesForCell = append(finalLinesForCell, tw.TruncateString(line, effectiveContentMaxWidth, ""))
|
||||
finalLinesForCell = append(finalLinesForCell, twwidth.Truncate(line, effectiveContentMaxWidth, ""))
|
||||
}
|
||||
} else {
|
||||
finalLinesForCell = append(finalLinesForCell, line)
|
||||
|
|
@ -915,12 +966,9 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
|
|||
case tw.WrapBreak:
|
||||
wrapped := make([]string, 0)
|
||||
currentLine := line
|
||||
breakCharWidth := tw.DisplayWidth(tw.CharBreak)
|
||||
for tw.DisplayWidth(currentLine) > effectiveContentMaxWidth {
|
||||
targetWidth := effectiveContentMaxWidth - breakCharWidth
|
||||
if targetWidth < 0 {
|
||||
targetWidth = 0
|
||||
}
|
||||
breakCharWidth := twwidth.Width(tw.CharBreak)
|
||||
for twwidth.Width(currentLine) > effectiveContentMaxWidth {
|
||||
targetWidth := max(effectiveContentMaxWidth-breakCharWidth, 0)
|
||||
breakPoint := tw.BreakPoint(currentLine, targetWidth)
|
||||
runes := []rune(currentLine)
|
||||
if breakPoint <= 0 || breakPoint > len(runes) {
|
||||
|
|
@ -929,7 +977,7 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
|
|||
tempWidth := 0
|
||||
for charIdx, r := range runes {
|
||||
runeStr := string(r)
|
||||
rw := tw.DisplayWidth(runeStr)
|
||||
rw := twwidth.Width(runeStr)
|
||||
if tempWidth+rw > targetWidth && charIdx > 0 {
|
||||
break
|
||||
}
|
||||
|
|
@ -956,10 +1004,10 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
|
|||
currentLine = string(runes[breakPoint:])
|
||||
}
|
||||
}
|
||||
if tw.DisplayWidth(currentLine) > 0 {
|
||||
if twwidth.Width(currentLine) > 0 {
|
||||
wrapped = append(wrapped, currentLine)
|
||||
}
|
||||
if len(wrapped) == 0 && tw.DisplayWidth(line) > 0 && len(finalLinesForCell) == 0 {
|
||||
if len(wrapped) == 0 && twwidth.Width(line) > 0 && len(finalLinesForCell) == 0 {
|
||||
finalLinesForCell = append(finalLinesForCell, line)
|
||||
} else {
|
||||
finalLinesForCell = append(finalLinesForCell, wrapped...)
|
||||
|
|
@ -1334,23 +1382,40 @@ func (t *Table) prepareWithMerges(content [][]string, config tw.CellConfig, posi
|
|||
// No parameters are required.
|
||||
// Returns an error if rendering fails in any section.
|
||||
func (t *Table) render() error {
|
||||
|
||||
t.ensureInitialized()
|
||||
|
||||
// Save the original writer and schedule its restoration upon function exit.
|
||||
// This guarantees the table's writer is restored even if errors occur.
|
||||
originalWriter := t.writer
|
||||
defer func() {
|
||||
t.writer = originalWriter
|
||||
}()
|
||||
|
||||
// If a counter is active, wrap the writer in a MultiWriter.
|
||||
if len(t.counters) > 0 {
|
||||
// The slice must be of type io.Writer.
|
||||
// Start it with the original destination writer.
|
||||
allWriters := []io.Writer{originalWriter}
|
||||
|
||||
// Append each counter to the slice of writers.
|
||||
for _, c := range t.counters {
|
||||
allWriters = append(allWriters, c)
|
||||
}
|
||||
|
||||
// Create a MultiWriter that broadcasts to the original writer AND all counters.
|
||||
t.writer = io.MultiWriter(allWriters...)
|
||||
}
|
||||
|
||||
if t.config.Stream.Enable {
|
||||
t.logger.Warn("Render() called in streaming mode. Use Start/Append/Close methods instead.")
|
||||
return errors.New("render called in streaming mode; use Start/Append/Close")
|
||||
}
|
||||
|
||||
// Calculate and cache numCols for THIS batch render pass
|
||||
t.batchRenderNumCols = t.maxColumns() // Calculate ONCE
|
||||
t.isBatchRenderNumColsSet = true // Mark the cache as active for this render pass
|
||||
t.logger.Debugf("Render(): Set batchRenderNumCols to %d and isBatchRenderNumColsSet to true.", t.batchRenderNumCols)
|
||||
|
||||
// Calculate and cache the column count for this specific batch render pass.
|
||||
t.batchRenderNumCols = t.maxColumns()
|
||||
t.isBatchRenderNumColsSet = true
|
||||
defer func() {
|
||||
t.isBatchRenderNumColsSet = false
|
||||
// t.batchRenderNumCols = 0; // Optional: reset to 0, or leave as is.
|
||||
// Since isBatchRenderNumColsSet is false, its value won't be used by getNumColsToUse.
|
||||
t.logger.Debugf("Render(): Cleared isBatchRenderNumColsSet to false (batchRenderNumCols was %d).", t.batchRenderNumCols)
|
||||
}()
|
||||
|
||||
|
|
@ -1359,9 +1424,10 @@ func (t *Table) render() error {
|
|||
(t.caption.Spot >= tw.SpotTopLeft && t.caption.Spot <= tw.SpotBottomRight)
|
||||
|
||||
var tableStringBuffer *strings.Builder
|
||||
targetWriter := t.writer
|
||||
originalWriter := t.writer // Save original writer for restoration if needed
|
||||
targetWriter := t.writer // Can be the original writer or the MultiWriter.
|
||||
|
||||
// If a caption is present, the main table content must be rendered to an
|
||||
// in-memory buffer first to calculate its final width.
|
||||
if isTopOrBottomCaption {
|
||||
tableStringBuffer = &strings.Builder{}
|
||||
targetWriter = tableStringBuffer
|
||||
|
|
@ -1370,19 +1436,17 @@ func (t *Table) render() error {
|
|||
t.logger.Debugf("No caption detected. Rendering table core directly to writer.")
|
||||
}
|
||||
|
||||
//Render Table Core
|
||||
// Point the table's writer to the target (either the final destination or the buffer).
|
||||
t.writer = targetWriter
|
||||
ctx, mctx, err := t.prepareContexts()
|
||||
if err != nil {
|
||||
t.writer = originalWriter
|
||||
t.logger.Errorf("prepareContexts failed: %v", err)
|
||||
return fmt.Errorf("failed to prepare table contexts: %w", err)
|
||||
return errors.Newf("failed to prepare table contexts").Wrap(err)
|
||||
}
|
||||
|
||||
if err := ctx.renderer.Start(t.writer); err != nil {
|
||||
t.writer = originalWriter
|
||||
t.logger.Errorf("Renderer Start() error: %v", err)
|
||||
return fmt.Errorf("renderer start failed: %w", err)
|
||||
return errors.Newf("renderer start failed").Wrap(err)
|
||||
}
|
||||
|
||||
renderError := false
|
||||
|
|
@ -1397,7 +1461,7 @@ func (t *Table) render() error {
|
|||
if renderErr := renderFn(ctx, mctx); renderErr != nil {
|
||||
t.logger.Errorf("Renderer section error (%s): %v", sectionName, renderErr)
|
||||
if !renderError {
|
||||
firstRenderErr = fmt.Errorf("failed to render %s section: %w", sectionName, renderErr)
|
||||
firstRenderErr = errors.Newf("failed to render %s section", sectionName).Wrap(renderErr)
|
||||
}
|
||||
renderError = true
|
||||
break
|
||||
|
|
@ -1407,23 +1471,26 @@ func (t *Table) render() error {
|
|||
if closeErr := ctx.renderer.Close(); closeErr != nil {
|
||||
t.logger.Errorf("Renderer Close() error: %v", closeErr)
|
||||
if !renderError {
|
||||
firstRenderErr = fmt.Errorf("renderer close failed: %w", closeErr)
|
||||
firstRenderErr = errors.Newf("renderer close failed").Wrap(closeErr)
|
||||
}
|
||||
renderError = true
|
||||
}
|
||||
|
||||
t.writer = originalWriter // Restore original writer
|
||||
// Restore the writer to the original for the caption-handling logic.
|
||||
// This is necessary because the caption must be written to the final
|
||||
// destination, not the temporary buffer used for the table body.
|
||||
t.writer = originalWriter
|
||||
|
||||
if renderError {
|
||||
return firstRenderErr // Return error from core rendering if any
|
||||
return firstRenderErr
|
||||
}
|
||||
|
||||
//Caption Handling & Final Output ---
|
||||
// Caption Handling & Final Output
|
||||
if isTopOrBottomCaption {
|
||||
renderedTableContent := tableStringBuffer.String()
|
||||
t.logger.Debugf("[Render] Table core buffer length: %d", len(renderedTableContent))
|
||||
|
||||
// Check if the buffer is empty AND borders are enabled
|
||||
// Handle edge case where table is empty but should have borders.
|
||||
shouldHaveBorders := t.renderer != nil && (t.renderer.Config().Borders.Top.Enabled() || t.renderer.Config().Borders.Bottom.Enabled())
|
||||
if len(renderedTableContent) == 0 && shouldHaveBorders {
|
||||
var sb strings.Builder
|
||||
|
|
@ -1441,7 +1508,7 @@ func (t *Table) render() error {
|
|||
actualTableWidth := 0
|
||||
trimmedBuffer := strings.TrimRight(renderedTableContent, "\r\n \t")
|
||||
for _, line := range strings.Split(trimmedBuffer, "\n") {
|
||||
w := tw.DisplayWidth(line)
|
||||
w := twwidth.Width(line)
|
||||
if w > actualTableWidth {
|
||||
actualTableWidth = w
|
||||
}
|
||||
|
|
@ -1475,7 +1542,7 @@ func (t *Table) render() error {
|
|||
|
||||
t.hasPrinted = true
|
||||
t.logger.Info("Render() completed.")
|
||||
return nil // Success
|
||||
return nil
|
||||
}
|
||||
|
||||
// renderFooter renders the table's footer section with borders and padding.
|
||||
|
|
@ -1649,7 +1716,7 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error {
|
|||
if hasTopPadding {
|
||||
hctx.rowIdx = 0
|
||||
hctx.lineIdx = -1
|
||||
if !(hasContentAbove && cfg.Settings.Lines.ShowFooterLine.Enabled()) {
|
||||
if !hasContentAbove || !cfg.Settings.Lines.ShowFooterLine.Enabled() {
|
||||
hctx.location = tw.LocationFirst
|
||||
} else {
|
||||
hctx.location = tw.LocationMiddle
|
||||
|
|
@ -1671,7 +1738,7 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error {
|
|||
hctx.line = padLine(line, ctx.numCols)
|
||||
isFirstContentLine := i == 0
|
||||
isLastContentLine := i == len(ctx.footerLines)-1
|
||||
if isFirstContentLine && !hasTopPadding && !(hasContentAbove && cfg.Settings.Lines.ShowFooterLine.Enabled()) {
|
||||
if isFirstContentLine && !hasTopPadding && (!hasContentAbove || !cfg.Settings.Lines.ShowFooterLine.Enabled()) {
|
||||
hctx.location = tw.LocationFirst
|
||||
} else if isLastContentLine && !hasBottomPaddingConfig {
|
||||
hctx.location = tw.LocationEnd
|
||||
|
|
@ -1688,7 +1755,7 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error {
|
|||
if hasBottomPaddingConfig {
|
||||
paddingLineContentForContext = make([]string, ctx.numCols)
|
||||
formattedPaddingCells := make([]string, ctx.numCols)
|
||||
var representativePadChar string = " "
|
||||
representativePadChar := " "
|
||||
ctx.logger.Debugf("Constructing Footer Bottom Padding line content strings")
|
||||
for j := 0; j < ctx.numCols; j++ {
|
||||
colWd := ctx.widths[tw.Footer].Get(j)
|
||||
|
|
@ -1713,10 +1780,7 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error {
|
|||
if j == 0 || representativePadChar == " " {
|
||||
representativePadChar = padChar
|
||||
}
|
||||
padWidth := tw.DisplayWidth(padChar)
|
||||
if padWidth < 1 {
|
||||
padWidth = 1
|
||||
}
|
||||
padWidth := max(twwidth.Width(padChar), 1)
|
||||
repeatCount := 0
|
||||
if colWd > 0 && padWidth > 0 {
|
||||
repeatCount = colWd / padWidth
|
||||
|
|
@ -1728,12 +1792,12 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error {
|
|||
repeatCount = 0
|
||||
}
|
||||
rawPaddingContent := strings.Repeat(padChar, repeatCount)
|
||||
currentWd := tw.DisplayWidth(rawPaddingContent)
|
||||
currentWd := twwidth.Width(rawPaddingContent)
|
||||
if currentWd < colWd {
|
||||
rawPaddingContent += strings.Repeat(" ", colWd-currentWd)
|
||||
}
|
||||
if currentWd > colWd && colWd > 0 {
|
||||
rawPaddingContent = tw.TruncateString(rawPaddingContent, colWd)
|
||||
rawPaddingContent = twwidth.Truncate(rawPaddingContent, colWd)
|
||||
}
|
||||
if colWd == 0 {
|
||||
rawPaddingContent = ""
|
||||
|
|
@ -2111,19 +2175,21 @@ func (t *Table) renderRow(ctx *renderContext, mctx *mergeContext) error {
|
|||
hctx.lineIdx = j
|
||||
hctx.line = padLine(visualLineData, ctx.numCols)
|
||||
|
||||
if j > 0 {
|
||||
visualLineHasActualContent := false
|
||||
for kCellIdx, cellContentInVisualLine := range hctx.line {
|
||||
if t.Trimmer(cellContentInVisualLine) != "" {
|
||||
visualLineHasActualContent = true
|
||||
ctx.logger.Debug("Visual line [%d][%d] has content in cell %d: '%s'. Not skipping.", i, j, kCellIdx, cellContentInVisualLine)
|
||||
break
|
||||
if t.config.Behavior.TrimLine.Enabled() {
|
||||
if j > 0 {
|
||||
visualLineHasActualContent := false
|
||||
for kCellIdx, cellContentInVisualLine := range hctx.line {
|
||||
if t.Trimmer(cellContentInVisualLine) != "" {
|
||||
visualLineHasActualContent = true
|
||||
ctx.logger.Debug("Visual line [%d][%d] has content in cell %d: '%s'. Not skipping.", i, j, kCellIdx, cellContentInVisualLine)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !visualLineHasActualContent {
|
||||
ctx.logger.Debug("Skipping visual line [%d][%d] as it's entirely blank after trimming. Line: %q", i, j, hctx.line)
|
||||
continue
|
||||
if !visualLineHasActualContent {
|
||||
ctx.logger.Debug("Skipping visual line [%d][%d] as it's entirely blank after trimming. Line: %q", i, j, hctx.line)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
1
vendor/github.com/olekukonko/tablewriter/tw/cell.go
generated
vendored
1
vendor/github.com/olekukonko/tablewriter/tw/cell.go
generated
vendored
|
|
@ -12,7 +12,6 @@ type CellFormatting struct {
|
|||
// Deprecated: kept for compatibility
|
||||
// will be removed soon
|
||||
Alignment Align // Text alignment within the cell (e.g., Left, Right, Center)
|
||||
|
||||
}
|
||||
|
||||
// CellPadding defines padding settings for table cells.
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue