diff --git a/cmd/sshproxyctl/sshproxyctl.go b/cmd/sshproxyctl/sshproxyctl.go index f91bdf60..95343ad8 100644 --- a/cmd/sshproxyctl/sshproxyctl.go +++ b/cmd/sshproxyctl/sshproxyctl.go @@ -82,9 +82,7 @@ func displayTable(headers []string, rows [][]string, footers []string) { tablewriter.WithConfig(tablewriter.Config{ MaxWidth: 200, Row: tw.CellConfig{ - Formatting: tw.CellFormatting{ - Alignment: tw.AlignRight, - }, + Alignment: tw.CellAlignment{Global: tw.AlignRight}, }, })) table.Header(headers) diff --git a/go.mod b/go.mod index 03355d20..cec0b8b1 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,15 @@ go 1.23.0 require ( github.com/creack/pty v1.1.24 - github.com/ebitengine/purego v0.8.3 + github.com/ebitengine/purego v0.8.4 github.com/iskylite/nodeset v1.0.1 github.com/moby/term v0.5.2 - github.com/olekukonko/tablewriter v1.0.6 + github.com/olekukonko/tablewriter v1.0.8 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 - go.etcd.io/etcd/api/v3 v3.6.0 - go.etcd.io/etcd/client/v3 v3.6.0 + go.etcd.io/etcd/api/v3 v3.6.2 + go.etcd.io/etcd/client/v3 v3.6.2 go.uber.org/zap v1.27.0 - google.golang.org/grpc v1.72.1 + google.golang.org/grpc v1.73.0 gopkg.in/yaml.v2 v2.4.0 ) @@ -23,21 +23,21 @@ require ( github.com/fatih/color v1.18.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // 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/olekukonko/errors v1.1.0 // indirect - github.com/olekukonko/ll v0.0.8 // indirect + github.com/olekukonko/ll v0.0.9 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/segmentio/fasthash v1.0.3 // indirect github.com/willf/bitset v1.1.11 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.0 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.2 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.40.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 // indirect google.golang.org/protobuf v1.36.6 // indirect ) diff --git a/go.sum b/go.sum index 67d37ab9..0b8040b9 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc= -github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 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/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -25,8 +25,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/iskylite/nodeset v1.0.1 h1:2yif2U3tyioRQyWSYSRVC47DUL0Raib4Ej+tEZIFlLs= github.com/iskylite/nodeset v1.0.1/go.mod h1:PCZzSaZBqVBbiHxCJb4PVHl7m6C5+5DrD1EVMl5+Clw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -45,10 +45,10 @@ github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 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.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc= -github.com/olekukonko/ll v0.0.8/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= -github.com/olekukonko/tablewriter v1.0.6 h1:/T45mIHc5hcEvibgzBzvMy7ruT+RjgoQRvkHbnl6OWA= -github.com/olekukonko/tablewriter v1.0.6/go.mod h1:SJ0MV1aHb/89fLcsBMXMp30Xg3g5eGoOUu0RptEk4AU= +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.8 h1:f6wJzHg4QUtJdvrVPKco4QTrAylgaU0+b9br/lJxEiQ= +github.com/olekukonko/tablewriter v1.0.8/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -67,24 +67,24 @@ github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/etcd/api/v3 v3.6.0 h1:vdbkcUBGLf1vfopoGE/uS3Nv0KPyIpUV/HM6w9yx2kM= -go.etcd.io/etcd/api/v3 v3.6.0/go.mod h1:Wt5yZqEmxgTNJGHob7mTVBJDZNXiHPtXTcPab37iFOw= -go.etcd.io/etcd/client/pkg/v3 v3.6.0 h1:nchnPqpuxvv3UuGGHaz0DQKYi5EIW5wOYsgUNRc365k= -go.etcd.io/etcd/client/pkg/v3 v3.6.0/go.mod h1:Jv5SFWMnGvIBn8o3OaBq/PnT0jjsX8iNokAUessNjoA= -go.etcd.io/etcd/client/v3 v3.6.0 h1:/yjKzD+HW5v/3DVj9tpwFxzNbu8hjcKID183ug9duWk= -go.etcd.io/etcd/client/v3 v3.6.0/go.mod h1:Jzk/Knqe06pkOZPHXsQ0+vNDvMQrgIqJ0W8DwPdMJMg= +go.etcd.io/etcd/api/v3 v3.6.2 h1:25aCkIMjUmiiOtnBIp6PhNj4KdcURuBak0hU2P1fgRc= +go.etcd.io/etcd/api/v3 v3.6.2/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= +go.etcd.io/etcd/client/pkg/v3 v3.6.2 h1:zw+HRghi/G8fKpgKdOcEKpnBTE4OO39T6MegA0RopVU= +go.etcd.io/etcd/client/pkg/v3 v3.6.2/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= +go.etcd.io/etcd/client/v3 v3.6.2 h1:RgmcLJxkpHqpFvgKNwAQHX3K+wsSARMXKgjmUSpoSKQ= +go.etcd.io/etcd/client/v3 v3.6.2/go.mod h1:PL7e5QMKzjybn0FosgiWvCUDzvdChpo5UgGR4Sk4Gzc= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -100,8 +100,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -110,12 +110,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -124,12 +124,12 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= -google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 h1:iOye66xuaAK0WnkPuhQPUFy8eJcmwUXqGGP3om6IxX8= +google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79/go.mod h1:HKJDgKsFUnv5VAGeQjz8kxcgDP0HoE0iZNp0OdZNlhE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 h1:1ZwqphdOdWYXsUHgMpU/101nCtf/kSp9hOrcvFsnl10= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_libinit.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_libinit.go index d229d842..e5a66f39 100644 --- a/vendor/github.com/ebitengine/purego/internal/fakecgo/go_libinit.go +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/go_libinit.go @@ -19,6 +19,7 @@ var ( ) //go:nosplit +//go:norace func x_cgo_notify_runtime_init_done() { pthread_mutex_lock(&runtime_init_mu) runtime_init_done = 1 @@ -28,6 +29,8 @@ func x_cgo_notify_runtime_init_done() { // Store the g into a thread-specific value associated with the pthread key pthread_g. // And pthread_key_destructor will dropm when the thread is exiting. +// +//go:norace func x_cgo_bindm(g unsafe.Pointer) { // We assume this will always succeed, otherwise, there might be extra M leaking, // when a C thread exits after a cgo call. diff --git a/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols.go b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols.go index d17942e0..d5170240 100644 --- a/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols.go +++ b/vendor/github.com/ebitengine/purego/internal/fakecgo/symbols.go @@ -19,6 +19,7 @@ func setg_trampoline(setg uintptr, G uintptr) func call5(fn, a1, a2, a3, a4, a5 uintptr) uintptr //go:nosplit +//go:norace func malloc(size uintptr) unsafe.Pointer { ret := call5(mallocABI0, uintptr(size), 0, 0, 0, 0) // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer @@ -26,96 +27,115 @@ func malloc(size uintptr) unsafe.Pointer { } //go:nosplit +//go:norace func free(ptr unsafe.Pointer) { call5(freeABI0, uintptr(ptr), 0, 0, 0, 0) } //go:nosplit +//go:norace func setenv(name *byte, value *byte, overwrite int32) int32 { return int32(call5(setenvABI0, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), uintptr(overwrite), 0, 0)) } //go:nosplit +//go:norace func unsetenv(name *byte) int32 { return int32(call5(unsetenvABI0, uintptr(unsafe.Pointer(name)), 0, 0, 0, 0)) } //go:nosplit +//go:norace func sigfillset(set *sigset_t) int32 { return int32(call5(sigfillsetABI0, uintptr(unsafe.Pointer(set)), 0, 0, 0, 0)) } //go:nosplit +//go:norace func nanosleep(ts *syscall.Timespec, rem *syscall.Timespec) int32 { return int32(call5(nanosleepABI0, uintptr(unsafe.Pointer(ts)), uintptr(unsafe.Pointer(rem)), 0, 0, 0)) } //go:nosplit +//go:norace func abort() { call5(abortABI0, 0, 0, 0, 0, 0) } //go:nosplit +//go:norace func pthread_attr_init(attr *pthread_attr_t) int32 { return int32(call5(pthread_attr_initABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0)) } //go:nosplit +//go:norace func pthread_create(thread *pthread_t, attr *pthread_attr_t, start unsafe.Pointer, arg unsafe.Pointer) int32 { return int32(call5(pthread_createABI0, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(start), uintptr(arg), 0)) } //go:nosplit +//go:norace func pthread_detach(thread pthread_t) int32 { return int32(call5(pthread_detachABI0, uintptr(thread), 0, 0, 0, 0)) } //go:nosplit +//go:norace func pthread_sigmask(how sighow, ign *sigset_t, oset *sigset_t) int32 { return int32(call5(pthread_sigmaskABI0, uintptr(how), uintptr(unsafe.Pointer(ign)), uintptr(unsafe.Pointer(oset)), 0, 0)) } //go:nosplit +//go:norace func pthread_self() pthread_t { return pthread_t(call5(pthread_selfABI0, 0, 0, 0, 0, 0)) } //go:nosplit +//go:norace func pthread_get_stacksize_np(thread pthread_t) size_t { return size_t(call5(pthread_get_stacksize_npABI0, uintptr(thread), 0, 0, 0, 0)) } //go:nosplit +//go:norace func pthread_attr_getstacksize(attr *pthread_attr_t, stacksize *size_t) int32 { return int32(call5(pthread_attr_getstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(unsafe.Pointer(stacksize)), 0, 0, 0)) } //go:nosplit +//go:norace func pthread_attr_setstacksize(attr *pthread_attr_t, size size_t) int32 { return int32(call5(pthread_attr_setstacksizeABI0, uintptr(unsafe.Pointer(attr)), uintptr(size), 0, 0, 0)) } //go:nosplit +//go:norace func pthread_attr_destroy(attr *pthread_attr_t) int32 { return int32(call5(pthread_attr_destroyABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0)) } //go:nosplit +//go:norace func pthread_mutex_lock(mutex *pthread_mutex_t) int32 { return int32(call5(pthread_mutex_lockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0)) } //go:nosplit +//go:norace func pthread_mutex_unlock(mutex *pthread_mutex_t) int32 { return int32(call5(pthread_mutex_unlockABI0, uintptr(unsafe.Pointer(mutex)), 0, 0, 0, 0)) } //go:nosplit +//go:norace func pthread_cond_broadcast(cond *pthread_cond_t) int32 { return int32(call5(pthread_cond_broadcastABI0, uintptr(unsafe.Pointer(cond)), 0, 0, 0, 0)) } //go:nosplit +//go:norace func pthread_setspecific(key pthread_key_t, value unsafe.Pointer) int32 { return int32(call5(pthread_setspecificABI0, uintptr(key), uintptr(value), 0, 0, 0)) } diff --git a/vendor/github.com/olekukonko/ll/lh/colorized.go b/vendor/github.com/olekukonko/ll/lh/colorized.go index 3dbfed30..8f5aa53d 100644 --- a/vendor/github.com/olekukonko/ll/lh/colorized.go +++ b/vendor/github.com/olekukonko/ll/lh/colorized.go @@ -7,6 +7,7 @@ import ( "os" "sort" "strings" + "time" ) // Palette defines ANSI color codes for various log components. @@ -76,8 +77,10 @@ var lightPalette = Palette{ // writing the result to the provided writer. // Thread-safe if the underlying writer is thread-safe. type ColorizedHandler struct { - w io.Writer // Destination for colored log output - palette Palette // Color scheme for formatting + w io.Writer // Destination for colored log output + palette Palette // Color scheme for formatting + showTime bool // Whether to display timestamps + timeFormat string // Format for timestamps (defaults to time.RFC3339) } // ColorOption defines a configuration function for ColorizedHandler. @@ -104,7 +107,12 @@ func WithColorPallet(pallet Palette) ColorOption { // logger := ll.New("app").Enable().Handler(handler) // logger.Info("Test") // Output: [app] : Test func NewColorizedHandler(w io.Writer, opts ...ColorOption) *ColorizedHandler { - c := &ColorizedHandler{w: w} // Initialize with writer + // Initialize with writer + c := &ColorizedHandler{w: w, + showTime: false, + timeFormat: time.RFC3339, + } + // Apply configuration options for _, opt := range opts { opt(c) @@ -136,6 +144,19 @@ func (h *ColorizedHandler) Handle(e *lx.Entry) error { } } +// Timestamped enables or disables timestamp display and optionally sets a custom time format. +// If format is empty, defaults to RFC3339. +// Example: +// +// handler := NewColorizedHandler(os.Stdout).Timestamped(true, time.StampMilli) +// // Output: Jan 02 15:04:05.000 [app] INFO: Test +func (h *ColorizedHandler) Timestamped(enable bool, format ...string) { + h.showTime = enable + if len(format) > 0 && format[0] != "" { + h.timeFormat = format[0] + } +} + // handleRegularOutput handles normal log entries. // It formats the entry with colored namespace, level, message, fields, and stack trace (if present), // writing the result to the handler's writer. @@ -146,6 +167,12 @@ func (h *ColorizedHandler) Handle(e *lx.Entry) error { func (h *ColorizedHandler) handleRegularOutput(e *lx.Entry) error { var builder strings.Builder // Buffer for building formatted output + // Add timestamp if enabled + if h.showTime { + builder.WriteString(e.Timestamp.Format(h.timeFormat)) + builder.WriteString(lx.Space) + } + // Format namespace with colors h.formatNamespace(&builder, e) @@ -345,6 +372,13 @@ func (h *ColorizedHandler) formatStack(b *strings.Builder, stack []byte) { // h.handleDumpOutput(&lx.Entry{Class: lx.ClassDump, Message: "pos 00 hex: 61 62 'ab'"}) // Writes colored dump func (h *ColorizedHandler) handleDumpOutput(e *lx.Entry) error { var builder strings.Builder + + // Add timestamp if enabled + if h.showTime { + builder.WriteString(e.Timestamp.Format(h.timeFormat)) + builder.WriteString(lx.Newline) + } + // Write colored BEGIN separator builder.WriteString(h.palette.Title) builder.WriteString("---- BEGIN DUMP ----") diff --git a/vendor/github.com/olekukonko/ll/lh/memory.go b/vendor/github.com/olekukonko/ll/lh/memory.go index 78ac918a..e3bc9398 100644 --- a/vendor/github.com/olekukonko/ll/lh/memory.go +++ b/vendor/github.com/olekukonko/ll/lh/memory.go @@ -11,8 +11,10 @@ import ( // Useful for testing or buffering logs for later inspection. // It maintains a thread-safe slice of log entries, protected by a read-write mutex. type MemoryHandler struct { - mu sync.RWMutex // Protects concurrent access to entries - entries []*lx.Entry // Slice of stored log entries + mu sync.RWMutex // Protects concurrent access to entries + entries []*lx.Entry // Slice of stored log entries + showTime bool // Whether to show timestamps when dumping + timeFormat string // Time format for dumping } // NewMemoryHandler creates a new MemoryHandler. @@ -28,6 +30,23 @@ func NewMemoryHandler() *MemoryHandler { } } +// Timestamped enables/disables timestamp display when dumping and optionally sets a time format. +// Consistent with TextHandler and ColorizedHandler signature. +// Example: +// +// handler.Timestamped(true) // Enable with default format +// handler.Timestamped(true, time.StampMilli) // Enable with custom format +// handler.Timestamped(false) // Disable +func (h *MemoryHandler) Timestamped(enable bool, format ...string) { + h.mu.Lock() + defer h.mu.Unlock() + + h.showTime = enable + if len(format) > 0 && format[0] != "" { + h.timeFormat = format[0] + } +} + // Handle stores the log entry in memory. // It appends the provided entry to the entries slice, ensuring thread-safety with a write lock. // Always returns nil, as it does not perform I/O operations. @@ -82,6 +101,7 @@ func (h *MemoryHandler) Dump(w io.Writer) error { // Create a temporary TextHandler to format entries tempHandler := NewTextHandler(w) + tempHandler.Timestamped(h.showTime, h.timeFormat) // Process each entry through the TextHandler for _, entry := range h.entries { diff --git a/vendor/github.com/olekukonko/ll/lh/text.go b/vendor/github.com/olekukonko/ll/lh/text.go index 3b0a85ce..7423e210 100644 --- a/vendor/github.com/olekukonko/ll/lh/text.go +++ b/vendor/github.com/olekukonko/ll/lh/text.go @@ -6,6 +6,7 @@ import ( "io" "sort" "strings" + "time" ) // TextHandler is a handler that outputs log entries as plain text. @@ -13,7 +14,9 @@ import ( // writing the result to the provided writer. // Thread-safe if the underlying writer is thread-safe. type TextHandler struct { - w io.Writer // Destination for formatted log output + w io.Writer // Destination for formatted log output + showTime bool // Whether to display timestamps + timeFormat string // Format for timestamps (defaults to time.RFC3339) } // NewTextHandler creates a new TextHandler writing to the specified writer. @@ -24,7 +27,24 @@ type TextHandler struct { // logger := ll.New("app").Enable().Handler(handler) // logger.Info("Test") // Output: [app] INFO: Test func NewTextHandler(w io.Writer) *TextHandler { - return &TextHandler{w: w} + return &TextHandler{ + w: w, + showTime: false, + timeFormat: time.RFC3339, + } +} + +// Timestamped enables or disables timestamp display and optionally sets a custom time format. +// If format is empty, defaults to RFC3339. +// Example: +// +// handler := NewTextHandler(os.Stdout).TextWithTime(true, time.StampMilli) +// // Output: Jan 02 15:04:05.000 [app] INFO: Test +func (h *TextHandler) Timestamped(enable bool, format ...string) { + h.showTime = enable + if len(format) > 0 && format[0] != "" { + h.timeFormat = format[0] + } } // Handle processes a log entry and writes it as plain text. @@ -60,6 +80,12 @@ func (h *TextHandler) Handle(e *lx.Entry) error { func (h *TextHandler) handleRegularOutput(e *lx.Entry) error { var builder strings.Builder // Buffer for building formatted output + // Add timestamp if enabled + if h.showTime { + builder.WriteString(e.Timestamp.Format(h.timeFormat)) + builder.WriteString(lx.Space) + } + // Format namespace based on style switch e.Style { case lx.NestedPath: @@ -140,6 +166,12 @@ func (h *TextHandler) handleDumpOutput(e *lx.Entry) error { // For text handler, we just add a newline before dump output var builder strings.Builder // Buffer for building formatted output + // Add timestamp if enabled + if h.showTime { + builder.WriteString(e.Timestamp.Format(h.timeFormat)) + builder.WriteString(lx.Newline) + } + // Add separator lines and dump content builder.WriteString("---- BEGIN DUMP ----\n") builder.WriteString(e.Message) diff --git a/vendor/github.com/olekukonko/ll/ll.go b/vendor/github.com/olekukonko/ll/ll.go index 3a660b95..44e51512 100644 --- a/vendor/github.com/olekukonko/ll/ll.go +++ b/vendor/github.com/olekukonko/ll/ll.go @@ -1116,6 +1116,24 @@ func (l *Logger) Style(style lx.StyleType) *Logger { return l } +// Timestamped enables or disables timestamp logging for the logger and optionally sets the timestamp format. +// It is thread-safe, using a write lock to ensure safe concurrent access. +// If the logger's handler supports the lx.Timestamper interface, the timestamp settings are applied. +// The method returns the logger instance to support method chaining. +// Parameters: +// +// enable: Boolean to enable or disable timestamp logging +// format: Optional string(s) to specify the timestamp format +func (l *Logger) Timestamped(enable bool, format ...string) *Logger { + l.mu.Lock() + defer l.mu.Unlock() + + if h, ok := l.handler.(lx.Timestamper); ok { + h.Timestamped(enable, format...) + } + return l +} + // Use adds a middleware function to process log entries before they are handled, returning // a Middleware handle for removal. Middleware returning a non-nil error stops the log. // It is thread-safe using a write lock. @@ -1389,6 +1407,24 @@ func WithHandler(handler lx.Handler) Option { } } +// WithTimestamped returns an Option that configures timestamp settings for the logger's existing handler. +// It enables or disables timestamp logging and optionally sets the timestamp format if the handler +// supports the lx.Timestamper interface. If no handler is set, the function has no effect. +// Parameters: +// +// enable: Boolean to enable or disable timestamp logging +// format: Optional string(s) to specify the timestamp format +func WithTimestamped(enable bool, format ...string) Option { + return func(l *Logger) { + if l.handler != nil { // Check if a handler is set + // Verify if the handler supports the lx.Timestamper interface + if h, ok := l.handler.(lx.Timestamper); ok { + h.Timestamped(enable, format...) // Apply timestamp settings to the handler + } + } + } +} + // WithLevel sets the minimum log level for the logger as a functional option for // configuring a new logger instance. // Example: diff --git a/vendor/github.com/olekukonko/ll/lx/lx.go b/vendor/github.com/olekukonko/ll/lx/lx.go index 2e404d59..64661886 100644 --- a/vendor/github.com/olekukonko/ll/lx/lx.go +++ b/vendor/github.com/olekukonko/ll/lx/lx.go @@ -124,6 +124,16 @@ type Handler interface { Handle(e *Entry) error // Processes a log entry, returning any error } +// Timestamper defines an interface for handlers that support timestamp configuration. +// It includes a method to enable or disable timestamp logging and optionally set the timestamp format. +type Timestamper interface { + // Timestamped enables or disables timestamp logging and allows specifying an optional format. + // Parameters: + // enable: Boolean to enable or disable timestamp logging + // format: Optional string(s) to specify the timestamp format + Timestamped(enable bool, format ...string) +} + // ClassType represents the type of a log entry. // It is an integer type used to categorize log entries (Text, JSON, Dump, Special, Raw), // influencing how handlers process and format them. diff --git a/vendor/github.com/olekukonko/tablewriter/MIGRATION.md b/vendor/github.com/olekukonko/tablewriter/MIGRATION.md new file mode 100644 index 00000000..650a195b --- /dev/null +++ b/vendor/github.com/olekukonko/tablewriter/MIGRATION.md @@ -0,0 +1,3280 @@ +# Migration Guide: tablewriter v0.0.5 to v1.0.x +> **NOTE:** This document is work in progress, use with `caution`. This document is a comprehensive guide. For specific issues or advanced scenarios, please refer to the source code or open an issue. + +The `tablewriter` library has undergone a significant redesign between versions **v0.0.5** and **v1.0.x**, transitioning from a primarily method-driven API to a more robust, modular, and configuration-driven framework. This guide provides a detailed roadmap for migrating your v0.0.5 codebase to v1.0.x. It includes mappings of old methods to new approaches, practical examples, and explanations of new features. + +We believe these changes significantly improve the library's flexibility, maintainability, and power, enabling new features and making complex table configurations more manageable. + +## Why Migrate to v1.0.x? + +The v1.0.x redesign enhances `tablewriter`’s flexibility, maintainability, and feature set: +- **Extensibility**: Decoupled rendering supports diverse output formats (e.g., HTML, Markdown, CSV). +- **Robust Configuration**: Centralized `Config` struct and fluent builders ensure atomic, predictable setups. +- **Streaming Capability**: Dedicated API for row-by-row rendering, ideal for large or real-time datasets. +- **Type Safety**: Specific types (e.g., `tw.State`, `tw.Align`) reduce errors and improve clarity. +- **Consistent API**: Unified interface for intuitive usage across simple and complex use cases. +- **New Features**: Hierarchical merging, granular padding, table captions, and fixed column widths. + +These improvements make v1.0.x more powerful, but they require updating code to leverage the new configuration-driven framework and take advantage of advanced functionalities. + +## Key New Features in v1.0.x + +- **Fluent Configuration Builders**: `NewConfigBuilder()` enables chained, readable setups (`config.go:NewConfigBuilder`). +- **Centralized Configuration**: `Config` struct governs table behavior and data processing (`config.go:Config`). +- **Decoupled Renderer**: `tw.Renderer` interface with `tw.Rendition` for visual styling, allowing custom renderers (`tw/renderer.go`). +- **True Streaming Support**: `Start()`, `Append()`, `Close()` for incremental rendering (`stream.go`). +- **Hierarchical Cell Merging**: `tw.MergeHierarchical` for complex data structures (`tw/tw.go:MergeMode` constant, logic in `zoo.go`). +- **Granular Padding Control**: Per-side (`Top`, `Bottom`, `Left`, `Right`) and per-column padding (`tw/cell.go:CellPadding`, `tw/types.go:Padding`). +- **Enhanced Type System**: `tw.State`, `tw.Align`, `tw.Spot`, and others for clarity and safety (`tw/state.go`, `tw/types.go`). +- **Comprehensive Error Handling**: Methods like `Render()` and `Append()` return errors (`tablewriter.go`, `stream.go`). +- **Fixed Column Width System**: `Config.Widths` for precise column sizing, especially in streaming (`config.go:Config`, `tw/cell.go:CellWidth`). +- **Table Captioning**: Flexible placement and styling with `tw.Caption` (`tw/types.go:Caption`). +- **Advanced Data Processing**: Support for `tw.Formatter`, per-column filters, and stringer caching (`tw/cell.go:CellFilter`, `tablewriter.go:WithStringer`). + +## Core Philosophy Changes in v1.0.x + +Understanding these shifts is essential for a successful migration: + +1. **Configuration-Driven Approach**: + - **Old**: Relied on `table.SetXxx()` methods for incremental, stateful modifications to table properties. + - **New**: Table behavior is defined by a `tablewriter.Config` struct (`config.go:Config`), while visual styling is managed by a `tw.Rendition` struct (`tw/renderer.go:Rendition`). These are typically set at table creation using `NewTable()` with `Option` functions or via a fluent `ConfigBuilder`, ensuring atomic and predictable configuration changes. + +2. **Decoupled Rendering Engine**: + - **Old**: Rendering logic was tightly integrated into the `Table` struct, limiting output flexibility. + - **New**: The `tw.Renderer` interface (`tw/renderer.go:Renderer`) defines rendering logic, with `renderer.NewBlueprint()` as the default text-based renderer. The renderer’s appearance (e.g., borders, symbols) is controlled by `tw.Rendition`, enabling support for alternative formats like HTML or Markdown. + +3. **Unified Section Configuration**: + - **Old**: Headers, rows, and footers had separate, inconsistent configuration methods. + - **New**: `tw.CellConfig` (`tw/cell.go:CellConfig`) standardizes configuration across headers, rows, and footers, encompassing formatting (`tw.CellFormatting`), padding (`tw.CellPadding`), column widths (`tw.CellWidth`), alignments (`tw.CellAlignment`), and filters (`tw.CellFilter`). + +4. **Fluent Configuration Builders**: + - **Old**: Configuration was done via individual setters, often requiring multiple method calls. + - **New**: `tablewriter.NewConfigBuilder()` (`config.go:NewConfigBuilder`) provides a chained, fluent API for constructing `Config` objects, with nested builders for `Header()`, `Row()`, `Footer()`, `Alignment()`, `Behavior()`, and `ForColumn()` to simplify complex setups. + +5. **Explicit Streaming Mode**: + - **Old**: No dedicated streaming support; tables were rendered in batch mode. + - **New**: Streaming for row-by-row rendering is enabled via `Config.Stream.Enable` or `WithStreaming(tw.StreamConfig{Enable: true})` and managed with `Table.Start()`, `Table.Append()` (or `Table.Header()`, `Table.Footer()`), and `Table.Close()` (`stream.go`). This is ideal for large datasets or continuous output. + +6. **Enhanced Error Handling**: + - **Old**: Methods like `Render()` did not return errors, making error detection difficult. + - **New**: Key methods (`Render()`, `Start()`, `Close()`, `Append()`, `Bulk()`) return errors to promote robust error handling and improve application reliability (`tablewriter.go`, `stream.go`). + +7. **Richer Type System & `tw` Package**: + - **Old**: Used integer constants (e.g., `ALIGN_CENTER`) and booleans, leading to potential errors. + - **New**: The `tw` sub-package introduces type-safe constructs like `tw.State` (`tw/state.go`), `tw.Align` (`tw/types.go`), `tw.Position` (`tw/types.go`), and `tw.CellConfig` (`tw/cell.go`), replacing magic constants and enhancing code clarity. + +## Configuration Methods in v1.0.x + +v1.0.x offers four flexible methods to configure tables, catering to different use cases and complexity levels. Each method can be used independently or combined, providing versatility for both simple and advanced setups. + +1. **Using `WithConfig` Option**: + - **Description**: Pass a fully populated `tablewriter.Config` struct during `NewTable` initialization using the `WithConfig` option. + - **Use Case**: Ideal for predefined, reusable configurations that can be serialized or shared across multiple tables. + - **Pros**: Explicit, portable, and suitable for complex setups; allows complete control over all configuration aspects. + - **Cons**: Verbose for simple changes, requiring manual struct population. + - **Example**: + +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + cfg := tablewriter.Config{ + Header: tw.CellConfig{ + Alignment: tw.CellAlignment{Global: tw.AlignCenter}, + Formatting: tw.CellFormatting{AutoFormat: tw.On}, + }, + Row: tw.CellConfig{ + Alignment: tw.CellAlignment{Global: tw.AlignLeft}, + }, + MaxWidth: 80, + Behavior: tw.Behavior{TrimSpace: tw.On}, + } + table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg)) + table.Header("Name", "Status") + table.Append("Node1", "Ready") + table.Render() +} +``` +**Output**: + ``` +┌───────┬────────┐ +│ NAME │ STATUS │ +├───────┼────────┤ +│ Node1 │ Ready │ +└───────┴────────┘ + ``` + +2. **Using `Table.Configure` method**: + - **Description**: After creating a `Table` instance, use the `Configure` method with a function that modifies the table's `Config` struct. + - **Use Case**: Suitable for quick, ad-hoc tweaks post-initialization, especially for simple or dynamic adjustments. + - **Pros**: Straightforward for minor changes; no need for additional structs or builders if you already have a `Table` instance. + - **Cons**: Less readable for complex configurations compared to a builder; modifications are applied to an existing instance. + - **Example**: +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + table := tablewriter.NewTable(os.Stdout) + table.Configure(func(cfg *tablewriter.Config) { + cfg.Header.Alignment.Global = tw.AlignCenter + cfg.Row.Alignment.Global = tw.AlignLeft + }) + + table.Header("Name", "Status") + table.Append("Node1", "Ready") + table.Render() +} +``` +**Output**: Same as above. + +3. **Standalone `Option` Functions**: + - **Description**: Use `WithXxx` functions (e.g., `WithHeaderAlignment`, `WithDebug`) during `NewTable` initialization or via `table.Options()` to apply targeted settings. + - **Use Case**: Best for simple, specific configuration changes without needing a full `Config` struct. + - **Pros**: Concise, readable, and intuitive for common settings; ideal for minimal setups. + - **Cons**: Limited for complex, multi-faceted configurations; requires multiple options for extensive changes. + - **Example**: +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + table := tablewriter.NewTable(os.Stdout, + tablewriter.WithHeaderAlignment(tw.AlignCenter), + tablewriter.WithRowAlignment(tw.AlignLeft), + tablewriter.WithDebug(true), + ) + table.Header("Name", "Status") + table.Append("Node1", "Ready") + table.Render() +} +``` + **Output**: Same as above. + +4. **Fluent `ConfigBuilder`**: + - **Description**: Use `tablewriter.NewConfigBuilder()` to construct a `Config` struct through a chained, fluent API, then apply it with `WithConfig(builder.Build())`. + - **Use Case**: Optimal for complex, dynamic, or programmatically generated configurations requiring fine-grained control. + - **Pros**: Highly readable, maintainable, and expressive; supports nested builders for sections and columns. + - **Cons**: Slightly verbose; requires understanding builder methods and `Build()` call. + - **Example**: +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + cnfBuilder := tablewriter.NewConfigBuilder() + cnfBuilder.Header().Alignment().WithGlobal(tw.AlignCenter) + // Example of configuring a specific column (less emphasis on this for now) + // cnfBuilder.ForColumn(0).WithAlignment(tw.AlignLeft).Build() // Call Build() to return to ConfigBuilder + + table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cnfBuilder.Build())) + table.Header("Name", "Status") + table.Append("Node1", "Ready") + table.Render() +} +``` +**Output**: Same as above. + +**Best Practices**: +- Use `WithConfig` or `ConfigBuilder` for complex setups or reusable configurations. +- Opt for `Option` functions for simple, targeted changes. +- Use `Table.Configure` for direct modifications after table creation, but avoid changes during rendering. +- Combine methods as needed (e.g., `ConfigBuilder` for initial setup, `Option` functions for overrides). + +## Default Parameters in v1.0.x + +The `defaultConfig()` function (`config.go:defaultConfig`) establishes baseline settings for new tables, ensuring predictable behavior unless overridden. Below is a detailed table of default parameters, organized by configuration section, to help you understand the starting point for table behavior and appearance. + +| Section | Parameter | Default Value | Description | +|---------------|-------------------------------|-----------------------------------|-----------------------------------------------------------------------------| +| **Header** | `Alignment.Global` | `tw.AlignCenter` | Centers header text globally unless overridden by `PerColumn`. | +| Header | `Alignment.PerColumn` | `[]tw.Align{}` | Empty; falls back to `Global` unless specified. | +| Header | `Formatting.AutoFormat` | `tw.On` | Applies title case (e.g., "col_one" → "COL ONE") to header content. | +| Header | `Formatting.AutoWrap` | `tw.WrapTruncate` | Truncates long header text with "…" based on width constraints. | +| Header | `Formatting.MergeMode` | `tw.MergeNone` | Disables cell merging in headers by default. | +| Header | `Padding.Global` | `tw.PaddingDefault` (`" "`) | Adds one space on left and right of header cells. | +| Header | `Padding.PerColumn` | `[]tw.Padding{}` | Empty; falls back to `Global` unless specified. | +| Header | `ColMaxWidths.Global` | `0` (unlimited) | No maximum content width for header cells unless set. | +| Header | `ColMaxWidths.PerColumn` | `tw.NewMapper[int, int]()` | Empty map; no per-column content width limits unless specified. | +| Header | `Filter.Global` | `nil` | No global content transformation for header cells. | +| Header | `Filter.PerColumn` | `[]func(string) string{}` | No per-column content transformations unless specified. | +| **Row** | `Alignment.Global` | `tw.AlignLeft` | Left-aligns row text globally unless overridden by `PerColumn`. | +| Row | `Alignment.PerColumn` | `[]tw.Align{}` | Empty; falls back to `Global`. | +| Row | `Formatting.AutoFormat` | `tw.Off` | Disables auto-formatting (e.g., title case) for row content. | +| Row | `Formatting.AutoWrap` | `tw.WrapNormal` | Wraps long row text naturally at word boundaries based on width constraints.| +| Row | `Formatting.MergeMode` | `tw.MergeNone` | Disables cell merging in rows by default. | +| Row | `Padding.Global` | `tw.PaddingDefault` (`" "`) | Adds one space on left and right of row cells. | +| Row | `Padding.PerColumn` | `[]tw.Padding{}` | Empty; falls back to `Global`. | +| Row | `ColMaxWidths.Global` | `0` (unlimited) | No maximum content width for row cells. | +| Row | `ColMaxWidths.PerColumn` | `tw.NewMapper[int, int]()` | Empty map; no per-column content width limits. | +| Row | `Filter.Global` | `nil` | No global content transformation for row cells. | +| Row | `Filter.PerColumn` | `[]func(string) string{}` | No per-column content transformations. | +| **Footer** | `Alignment.Global` | `tw.AlignRight` | Right-aligns footer text globally unless overridden by `PerColumn`. | +| Footer | `Alignment.PerColumn` | `[]tw.Align{}` | Empty; falls back to `Global`. | +| Footer | `Formatting.AutoFormat` | `tw.Off` | Disables auto-formatting for footer content. | +| Footer | `Formatting.AutoWrap` | `tw.WrapNormal` | Wraps long footer text naturally. | +| Footer | `Formatting.MergeMode` | `tw.MergeNone` | Disables cell merging in footers. | +| Footer | `Padding.Global` | `tw.PaddingDefault` (`" "`) | Adds one space on left and right of footer cells. | +| Footer | `Padding.PerColumn` | `[]tw.Padding{}` | Empty; falls back to `Global`. | +| Footer | `ColMaxWidths.Global` | `0` (unlimited) | No maximum content width for footer cells. | +| Footer | `ColMaxWidths.PerColumn` | `tw.NewMapper[int, int]()` | Empty map; no per-column content width limits. | +| Footer | `Filter.Global` | `nil` | No global content transformation for footer cells. | +| Footer | `Filter.PerColumn` | `[]func(string) string{}` | No per-column content transformations. | +| **Global** | `MaxWidth` | `0` (unlimited) | No overall table width limit. | +| Global | `Behavior.AutoHide` | `tw.Off` | Displays empty columns (ignored in streaming). | +| Global | `Behavior.TrimSpace` | `tw.On` | Trims leading/trailing spaces from cell content. | +| Global | `Behavior.Header` | `tw.Control{Hide: tw.Off}` | Shows header if content is provided. | +| Global | `Behavior.Footer` | `tw.Control{Hide: tw.Off}` | Shows footer if content is provided. | +| Global | `Behavior.Compact` | `tw.Compact{Merge: tw.Off}` | No compact width optimization for merged cells. | +| Global | `Debug` | `false` | Disables debug logging. | +| Global | `Stream.Enable` | `false` | Disables streaming mode by default. | +| Global | `Widths.Global` | `0` (unlimited) | No fixed column width unless specified. | +| Global | `Widths.PerColumn` | `tw.NewMapper[int, int]()` | Empty map; no per-column fixed widths unless specified. | + +**Notes**: +- Defaults can be overridden using any configuration method. +- `tw.PaddingDefault` is `{Left: " ", Right: " "}` (`tw/preset.go`). +- Alignment within `tw.CellFormatting` is deprecated; `tw.CellAlignment` is preferred. `tw.AlignDefault` falls back to `Global` or `tw.AlignLeft` (`tw/types.go`). +- Streaming mode uses `Widths` for fixed column sizing (`stream.go`). + +## Renderer Types and Customization + +v1.0.x introduces a flexible rendering system via the `tw.Renderer` interface (`tw/renderer.go:Renderer`), allowing for both default text-based rendering and custom output formats. This decouples rendering logic from table data processing, enabling support for diverse formats like HTML, CSV, or JSON. + +### Default Renderer: `renderer.NewBlueprint` +- **Description**: `renderer.NewBlueprint()` creates a text-based renderer. Its visual styles are configured using `tw.Rendition`. +- **Use Case**: Standard terminal or text output with configurable borders, symbols, and separators. +- **Configuration**: Styled via `tw.Rendition`, which controls: + - `Borders`: Outer table borders (`tw.Border`) with `tw.State` for each side (`tw/renderer.go`). + - `Settings`: Internal lines (`tw.Lines`) and separators (`tw.Separators`) (`tw/renderer.go`). + - `Symbols`: Characters for drawing table lines and junctions (`tw.Symbols`) (`tw/symbols.go`). + - **Example**: +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" // Import the renderer package + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + table := tablewriter.NewTable(os.Stdout, + tablewriter.WithRenderer(renderer.NewBlueprint()), // Default Blueprint renderer + tablewriter.WithRendition(tw.Rendition{ // Apply custom rendition + Symbols: tw.NewSymbols(tw.StyleRounded), + Borders: tw.Border{Top: tw.On, Bottom: tw.On, Left: tw.On, Right: tw.On}, + Settings: tw.Settings{ + Separators: tw.Separators{BetweenRows: tw.On}, + Lines: tw.Lines{ShowHeaderLine: tw.On}, + }, + }), + ) + table.Header("Name", "Status") + table.Append("Node1", "Ready") + table.Render() +} +``` +**Output**: + ``` + ╭───────┬────────╮ + │ Name │ Status │ + ├───────┼────────┤ + │ Node1 │ Ready │ + ╰───────┴────────╯ + ``` + +### Custom Renderer Implementation +- **Description**: Implement the `tw.Renderer` interface to create custom output formats (e.g., HTML, CSV). +- **Use Case**: Non-text outputs, specialized formatting, or integration with other systems. +- **Interface Methods**: + - `Start(w io.Writer) error`: Initializes rendering. + - `Header(headers [][]string, ctx tw.Formatting)`: Renders header rows. + - `Row(row []string, ctx tw.Formatting)`: Renders a data row. + - `Footer(footers [][]string, ctx tw.Formatting)`: Renders footer rows. + - `Line(ctx tw.Formatting)`: Renders separator lines. + - `Close() error`: Finalizes rendering. + - `Config() tw.Rendition`: Returns renderer's current rendition configuration. + - `Logger(logger *ll.Logger)`: Sets logger for debugging. + - **Example (HTML Renderer)**: + +```go +package main + +import ( + "fmt" + "github.com/olekukonko/ll" // For logger type + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + "io" + "os" +) + +// BasicHTMLRenderer implements tw.Renderer +type BasicHTMLRenderer struct { + writer io.Writer + config tw.Rendition // Store the rendition + logger *ll.Logger + err error +} + +func (r *BasicHTMLRenderer) Start(w io.Writer) error { + r.writer = w + _, r.err = r.writer.Write([]byte("\n")) + return r.err +} + +// Header expects [][]string for potentially multi-line headers +func (r *BasicHTMLRenderer) Header(headers [][]string, ctx tw.Formatting) { + if r.err != nil { return } + _, r.err = r.writer.Write([]byte(" \n")) + if r.err != nil { return } + // Iterate over cells from the context for the current line + for _, cellCtx := range ctx.Row.Current { + content := fmt.Sprintf(" \n", cellCtx.Data) + _, r.err = r.writer.Write([]byte(content)) + if r.err != nil { return } + } + _, r.err = r.writer.Write([]byte(" \n")) +} + +// Row expects []string for a single line row, but uses ctx for actual data +func (r *BasicHTMLRenderer) Row(row []string, ctx tw.Formatting) { // row param is less relevant here, ctx.Row.Current is key + if r.err != nil { return } + _, r.err = r.writer.Write([]byte(" \n")) + if r.err != nil { return } + for _, cellCtx := range ctx.Row.Current { + content := fmt.Sprintf(" \n", cellCtx.Data) + _, r.err = r.writer.Write([]byte(content)) + if r.err != nil { return } + } + _, r.err = r.writer.Write([]byte(" \n")) +} + +func (r *BasicHTMLRenderer) Footer(footers [][]string, ctx tw.Formatting) { + if r.err != nil { return } + // Similar to Header/Row, using ctx.Row.Current for the footer line data + // The footers [][]string param might be used if the renderer needs multi-line footer logic + r.Row(nil, ctx) // Reusing Row logic, passing nil for the direct row []string as ctx contains the data +} + +func (r *BasicHTMLRenderer) Line(ctx tw.Formatting) { /* No lines in basic HTML */ } + +func (r *BasicHTMLRenderer) Close() error { + if r.err != nil { + return r.err + } + _, r.err = r.writer.Write([]byte("
%s
%s
\n")) + return r.err +} + +func (r *BasicHTMLRenderer) Config() tw.Rendition { return r.config } +func (r *BasicHTMLRenderer) Logger(logger *ll.Logger) { r.logger = logger } + +func main() { + table := tablewriter.NewTable(os.Stdout, tablewriter.WithRenderer(&BasicHTMLRenderer{ + config: tw.Rendition{Symbols: tw.NewSymbols(tw.StyleASCII)}, // Provide a default Rendition + })) + table.Header("Name", "Status") + table.Append("Node1", "Ready") + table.Render() +} +``` +**Output**: +```html + + + + + + + + + +
NAMESTATUS
Node1Ready
+``` + + +#### 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. + +## Function Mapping Table (v0.0.5 → v1.0.x) + +The following table maps v0.0.5 methods to their v1.0.x equivalents, ensuring a quick reference for migration. All deprecated methods are retained for compatibility but should be replaced with new APIs. + +| v0.0.5 Method | v1.0.x Equivalent(s) | Notes | +|-----------------------------------|--------------------------------------------------------------------------------------|----------------------------------------------------------------------| +| `NewWriter(w)` | `NewTable(w, opts...)` | `NewWriter` deprecated; wraps `NewTable` (`tablewriter.go`). | +| `SetHeader([]string)` | `Header(...any)` | Variadic or slice; supports any type (`tablewriter.go`). | +| `Append([]string)` | `Append(...any)` | Variadic, slice, or struct (`tablewriter.go`). | +| `AppendBulk([][]string)` | `Bulk([]any)` | Slice of rows; supports any type (`tablewriter.go`). | +| `SetFooter([]string)` | `Footer(...any)` | Variadic or slice; supports any type (`tablewriter.go`). | +| `Render()` | `Render()` (returns `error`) | Batch mode; streaming uses `Start/Close` (`tablewriter.go`). | +| `SetBorder(bool)` | `WithRendition(tw.Rendition{Borders: ...})` or `WithBorders(tw.Border)` (deprecated) | Use `tw.Border` (`deprecated.go`, `tw/renderer.go`). | +| `SetRowLine(bool)` | `WithRendition(tw.Rendition{Settings: {Separators: {BetweenRows: tw.On}}})` | `tw.Separators` (`tw/renderer.go`). | +| `SetHeaderLine(bool)` | `WithRendition(tw.Rendition{Settings: {Lines: {ShowHeaderLine: tw.On}}})` | `tw.Lines` (`tw/renderer.go`). | +| `SetColumnSeparator(string)` | `WithRendition(tw.Rendition{Symbols: ...})` or `WithSymbols(tw.Symbols)` (deprecated)| `tw.NewSymbols` or custom `tw.Symbols` (`tw/symbols.go`). | +| `SetCenterSeparator(string)` | `WithRendition(tw.Rendition{Symbols: ...})` or `WithSymbols(tw.Symbols)` (deprecated)| `tw.Symbols` (`tw/symbols.go`). | +| `SetRowSeparator(string)` | `WithRendition(tw.Rendition{Symbols: ...})` or `WithSymbols(tw.Symbols)` (deprecated)| `tw.Symbols` (`tw/symbols.go`). | +| `SetAlignment(int)` | `WithRowAlignment(tw.Align)` or `Config.Row.Alignment.Global` | `tw.Align` type (`config.go`). | +| `SetHeaderAlignment(int)` | `WithHeaderAlignment(tw.Align)` or `Config.Header.Alignment.Global` | `tw.Align` type (`config.go`). | +| `SetAutoFormatHeaders(bool)` | `WithHeaderAutoFormat(tw.State)` or `Config.Header.Formatting.AutoFormat` | `tw.State` (`config.go`). | +| `SetAutoWrapText(bool)` | `WithRowAutoWrap(int)` or `Config.Row.Formatting.AutoWrap` (uses `tw.Wrap...` const) | `tw.WrapNormal`, `tw.WrapTruncate`, etc. (`config.go`). | +| `SetAutoMergeCells(bool)` | `WithRowMergeMode(int)` or `Config.Row.Formatting.MergeMode` (uses `tw.Merge...` const) | Supports `Vertical`, `Hierarchical` (`config.go`). | +| `SetColMinWidth(col, w)` | `WithColumnWidths(tw.NewMapper[int,int]().Set(col, w))` or `Config.Widths.PerColumn` | `Config.Widths` for fixed widths (`config.go`). | +| `SetTablePadding(string)` | Use `Config.
.Padding.Global` with `tw.Padding` | No direct equivalent; manage via cell padding (`tw/cell.go`). | +| `SetDebug(bool)` | `WithDebug(bool)` or `Config.Debug` | Logs via `table.Debug()` (`config.go`). | +| `Clear()` | `Reset()` | Clears data and state (`tablewriter.go`). | +| `SetNoWhiteSpace(bool)` | `WithTrimSpace(tw.Off)` (if `true`) or `WithPadding(tw.PaddingNone)` | Manage via `Config.Behavior.TrimSpace` or padding (`config.go`). | +| `SetColumnColor(Colors)` | Embed ANSI codes in cell data, use `tw.Formatter`, or `Config.
.Filter` | Colors via data or filters (`tw/cell.go`). | +| `SetHeaderColor(Colors)` | Embed ANSI codes in cell data, use `tw.Formatter`, or `Config.Header.Filter` | Colors via data or filters (`tw/cell.go`). | +| `SetFooterColor(Colors)` | Embed ANSI codes in cell data, use `tw.Formatter`, or `Config.Footer.Filter` | Colors via data or filters (`tw/cell.go`). | + +**Notes**: +- Deprecated methods are in `deprecated.go` but should be replaced. +- `tw` package types (e.g., `tw.Align`, `tw.State`) are required for new APIs. +- Examples for each mapping are provided in the migration steps below. + +## Detailed Migration Steps: Initialization, Data Input, Rendering + +This section provides step-by-step guidance for migrating core table operations, with code examples and explanations to ensure clarity. Each step maps v0.0.5 methods to v1.0.x equivalents, highlighting changes, best practices, and potential pitfalls. + +### 1. Table Initialization +Initialization has shifted from `NewWriter` to `NewTable`, which supports flexible configuration via `Option` functions, `Config` structs, or `ConfigBuilder`. + +**Old (v0.0.5):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +func main() { + table := tablewriter.NewWriter(os.Stdout) + // ... use table + _ = table // Avoid "declared but not used" +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "fmt" // Added for FormattableEntry example + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" // Import renderer + "github.com/olekukonko/tablewriter/tw" + "os" + "strings" // Added for Formatter example +) + +func main() { + // Minimal Setup (Default Configuration) + tableMinimal := tablewriter.NewTable(os.Stdout) + _ = tableMinimal // Avoid "declared but not used" + + // Using Option Functions for Targeted Configuration + tableWithOptions := tablewriter.NewTable(os.Stdout, + tablewriter.WithHeaderAlignment(tw.AlignCenter), // Center header text + tablewriter.WithRowAlignment(tw.AlignLeft), // Left-align row text + tablewriter.WithDebug(true), // Enable debug logging + ) + _ = tableWithOptions // Avoid "declared but not used" + + // Using a Full Config Struct for Comprehensive Control + cfg := tablewriter.Config{ + Header: tw.CellConfig{ + Alignment: tw.CellAlignment{ + Global: tw.AlignCenter, + PerColumn: []tw.Align{tw.AlignLeft, tw.AlignRight}, // Column-specific alignment + }, + Formatting: tw.CellFormatting{AutoFormat: tw.On}, + }, + Row: tw.CellConfig{ + Alignment: tw.CellAlignment{Global: tw.AlignLeft}, + Formatting: tw.CellFormatting{AutoWrap: tw.WrapNormal}, + }, + Footer: tw.CellConfig{ + Alignment: tw.CellAlignment{Global: tw.AlignRight}, + }, + MaxWidth: 80, // Constrain total table width + Behavior: tw.Behavior{ + AutoHide: tw.Off, // Show empty columns + TrimSpace: tw.On, // Trim cell spaces + }, + Widths: tw.CellWidth{ + Global: 20, // Default fixed column width + PerColumn: tw.NewMapper[int, int]().Set(0, 15), // Column 0 fixed at 15 + }, + } + tableWithConfig := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg)) + _ = tableWithConfig // Avoid "declared but not used" + + // Using ConfigBuilder for Fluent, Complex Configuration + builder := tablewriter.NewConfigBuilder(). + WithMaxWidth(80). + WithAutoHide(tw.Off). + WithTrimSpace(tw.On). + WithDebug(true). // Enable debug logging + Header(). + Alignment(). + WithGlobal(tw.AlignCenter). + Build(). // Returns *ConfigBuilder + Header(). + Formatting(). + WithAutoFormat(tw.On). + WithAutoWrap(tw.WrapTruncate). // Test truncation + WithMergeMode(tw.MergeNone). // Explicit merge mode + Build(). // Returns *HeaderConfigBuilder + Padding(). + WithGlobal(tw.Padding{Left: "[", Right: "]", Overwrite: true}). + Build(). // Returns *HeaderConfigBuilder + Build(). // Returns *ConfigBuilder + Row(). + Formatting(). + WithAutoFormat(tw.On). // Uppercase rows + Build(). // Returns *RowConfigBuilder + Build(). // Returns *ConfigBuilder + Row(). + Alignment(). + WithGlobal(tw.AlignLeft). + Build(). // Returns *ConfigBuilder + Build() // Finalize Config + + tableWithFluent := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(builder)) + _ = tableWithFluent // Avoid "declared but not used" +} +``` + +**Key Changes**: +- **Deprecation**: `NewWriter` is deprecated but retained for compatibility, internally calling `NewTable` (`tablewriter.go:NewWriter`). Transition to `NewTable` for new code. +- **Flexibility**: `NewTable` accepts an `io.Writer` and variadic `Option` functions (`tablewriter.go:NewTable`), enabling configuration via: + - `WithConfig(Config)`: Applies a complete `Config` struct (`config.go:WithConfig`). + - `WithRenderer(tw.Renderer)`: Sets a custom renderer, defaulting to `renderer.NewBlueprint()` (`tablewriter.go:WithRenderer`). + - `WithRendition(tw.Rendition)`: Configures visual styles for the renderer (`tablewriter.go:WithRendition`). + - `WithStreaming(tw.StreamConfig)`: Enables streaming mode (`tablewriter.go:WithStreaming`). + - Other `WithXxx` functions for specific settings (e.g., `WithHeaderAlignment`, `WithDebug`). +- **ConfigBuilder**: Provides a fluent API for complex setups; `Build()` finalizes the `Config` (`config.go:ConfigBuilder`). +- **Method Chaining in Builder**: Remember to call `Build()` on nested builders to return to the parent builder (e.g., `builder.Header().Alignment().WithGlobal(...).Build().Formatting()...`). + +**Migration Tips**: +- Replace `NewWriter` with `NewTable` and specify configurations explicitly. +- Use `ConfigBuilder` for complex setups or when readability is paramount. +- Apply `Option` functions for quick, targeted changes. +- Ensure `tw` package is imported for types like `tw.Align` and `tw.CellConfig`. +- Verify renderer settings if custom styling is needed, as `renderer.NewBlueprint()` is the default. + +**Potential Pitfalls**: +- **Unintended Defaults**: Without explicit configuration, `defaultConfig()` applies (see Default Parameters), which may differ from v0.0.5 behavior (e.g., `Header.Formatting.AutoFormat = tw.On`). +- **Renderer Absence**: If no renderer is set, `NewTable` defaults to `renderer.NewBlueprint()`; explicitly set for custom formats. +- **ConfigBuilder Errors**: Always call `Build()` at the end of a builder chain and on nested builders; omitting it can lead to incomplete configurations or runtime errors. +- **Concurrent Modification**: Avoid modifying `Table.config` (if using the `Configure` method or direct access) in concurrent scenarios or during rendering to prevent race conditions. + +### 2. Data Input +Data input methods in v1.0.x are more flexible, accepting `any` type for headers, rows, and footers, with robust conversion logic to handle strings, structs, and custom types. + +#### 2.1. Setting Headers +Headers define the table’s column labels and are typically the first data added. + +**Old (v0.0.5):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +func main() { + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Name", "Sign", "Rating"}) + // ... +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "fmt" + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" // For tw.Formatter + "os" + "strings" +) + +// Struct with Formatter +type HeaderData struct { // Renamed to avoid conflict + Label string +} + +func (h HeaderData) Format() string { return strings.ToUpper(h.Label) } // Implements tw.Formatter + +func main() { + table := tablewriter.NewTable(os.Stdout) + + // Variadic Arguments (Preferred for Simplicity) + table.Header("Name", "Sign", "Rating") + + // Slice of Strings + // table.Header([]string{"Name", "Sign", "Rating"}) // Example, comment out if using variadic + + // Slice of Any Type (Flexible) + // table.Header([]any{"Name", "Sign", 123}) // Numbers converted to strings + + // Using Formatter + // table.Header(HeaderData{"name"}, HeaderData{"sign"}, HeaderData{"rating"}) // Outputs "NAME", "SIGN", "RATING" + + table.Append("Example", "Row", "Data") // Add some data to render + table.Render() +} +``` + +**Key Changes**: +- **Method**: `SetHeader([]string)` replaced by `Header(...any)` (`tablewriter.go:Header`). +- **Flexibility**: Accepts variadic arguments or a single slice; supports any type, not just strings. +- **Conversion**: Elements are processed by `processVariadic` (`zoo.go:processVariadic`) and converted to strings via `convertCellsToStrings` (`zoo.go:convertCellsToStrings`), supporting: + - Basic types (e.g., `string`, `int`, `float64`). + - `fmt.Stringer` implementations. + - `tw.Formatter` implementations (custom string conversion). + - Structs (exported fields as cells or single cell if `Stringer`/`Formatter`). +- **Streaming**: In streaming mode, `Header()` renders immediately via `streamRenderHeader` (`stream.go:streamRenderHeader`). +- **Formatting**: Headers are formatted per `Config.Header` settings (e.g., `AutoFormat`, `Alignment`) during rendering (`zoo.go:prepareContent`). + +**Migration Tips**: +- Replace `SetHeader` with `Header`, using variadic arguments for simplicity. +- Use slices for dynamic header lists or when passing from a variable. +- Implement `tw.Formatter` for custom header formatting (e.g., uppercase). +- Ensure header count matches expected columns to avoid width mismatches. +- In streaming mode, call `Header()` before rows, as it fixes column widths (`stream.go`). + +**Potential Pitfalls**: +- **Type Mismatch**: Non-string types are converted to strings; ensure desired formatting (e.g., use `tw.Formatter` for custom types). +- **Streaming Widths**: Headers influence column widths in streaming; set `Config.Widths` explicitly if specific widths are needed (`stream.go:streamCalculateWidths`). +- **Empty Headers**: Missing headers may cause rendering issues; provide placeholders (e.g., `""`) if needed. +- **AutoFormat**: Default `Header.Formatting.AutoFormat = tw.On` applies title case; disable with `WithHeaderAutoFormat(tw.Off)` if unwanted (`config.go`). + +#### 2.2. Appending Rows +Rows represent the table’s data entries, and v1.0.x enhances flexibility with support for varied input types. + +**Old (v0.0.5):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +func main() { + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"ColA", "ColB", "ColC"}) // Add header for context + table.Append([]string{"A", "The Good", "500"}) + table.Append([]string{"B", "The Very Bad", "288"}) + data := [][]string{ + {"C", "The Ugly", "120"}, + {"D", "The Gopher", "800"}, + } + table.AppendBulk(data) + table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "fmt" + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" // For tw.Formatter + "log" + "os" +) + +// Struct for examples +type Entry struct { + ID string + Label string + Score int +} + +// Struct with Formatter +type FormattableEntry struct { + ID string + Label string + Score int +} + +func (e FormattableEntry) Format() string { return fmt.Sprintf("%s (%d)", e.Label, e.Score) } // Implements tw.Formatter + +func main() { + table := tablewriter.NewTable(os.Stdout) + table.Header("ID", "Description", "Value") // Header for context + + // Variadic Arguments (Single Row) + table.Append("A", "The Good", 500) // Numbers converted to strings + + // Slice of Any Type (Single Row) + table.Append([]any{"B", "The Very Bad", "288"}) + + // Struct with Fields + table.Append(Entry{ID: "C", Label: "The Ugly", Score: 120}) // Fields as cells + + // Struct with Formatter (will produce a single cell for this row based on Format()) + // To make it fit 3 columns, the formatter would need to produce a string that looks like 3 cells, or the table config would need to adapt. + // For this example, let's assume it's meant to be one wide cell, or adjust the header. + // For now, let's simplify and append it to a table with one header. + tableOneCol := tablewriter.NewTable(os.Stdout) + tableOneCol.Header("Formatted Entry") + tableOneCol.Append(FormattableEntry{ID: "D", Label: "The Gopher", Score: 800}) // Single cell "The Gopher (800)" + tableOneCol.Render() + fmt.Println("---") + + + // Re-initialize main table for Bulk example + table = tablewriter.NewTable(os.Stdout) + table.Header("ID", "Description", "Value") + + // Multiple Rows with Bulk + data := []any{ + []any{"E", "The Fast", 300}, + Entry{ID: "F", Label: "The Swift", Score: 400}, // Struct instance + // FormattableEntry{ID: "G", Label: "The Bold", Score: 500}, // Would also be one cell + } + if err := table.Bulk(data); err != nil { + log.Fatalf("Bulk append failed: %v", err) + } + table.Render() +} +``` + +**Key Changes**: +- **Method**: `Append([]string)` replaced by `Append(...any)`; `AppendBulk([][]string)` replaced by `Bulk([]any)` (`tablewriter.go`). +- **Input Flexibility**: `Append` accepts: + - Multiple arguments as cells of a single row. + - A single slice (e.g., `[]string`, `[]any`) as cells of a single row. + - A single struct, processed by `convertItemToCells` (`zoo.go`): + - Uses `tw.Formatter` or `fmt.Stringer` for single-cell output. + - Extracts exported fields as multiple cells otherwise. +- **Bulk Input**: `Bulk` accepts a slice where each element is a row (e.g., `[][]any`, `[]Entry`), processed by `appendSingle` (`zoo.go:appendSingle`). +- **Streaming**: Rows render immediately in streaming mode via `streamAppendRow` (`stream.go:streamAppendRow`). +- **Error Handling**: `Append` and `Bulk` return errors for invalid conversions or streaming issues (`tablewriter.go`). + +**Migration Tips**: +- Replace `Append([]string)` with `Append(...any)` for variadic input. +- Use `Bulk` for multiple rows, ensuring each element is a valid row representation. +- Implement `tw.Formatter` for custom struct formatting to control cell output. +- Match row cell count to headers to avoid alignment issues. +- In streaming mode, append rows after `Start()` and before `Close()` (`stream.go`). + +**Potential Pitfalls**: +- **Cell Count Mismatch**: Uneven row lengths may cause rendering errors; pad with empty strings (e.g., `""`) if needed. +- **Struct Conversion**: Unexported fields are ignored; ensure fields are public or use `Formatter`/`Stringer` (`zoo.go`). +- **Streaming Order**: Rows must be appended after headers in streaming to maintain width consistency (`stream.go`). +- **Error Ignored**: Always check `Bulk` errors, as invalid data can cause failures. + +#### 2.3. Setting Footers +Footers provide summary or closing data for the table, often aligned differently from rows. + +**Old (v0.0.5):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +func main() { + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"ColA", "ColB", "ColC", "ColD"}) // Add header for context + table.SetFooter([]string{"", "", "Total", "1408"}) + table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "fmt" + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" // For tw.Formatter + "os" +) + +// Using Formatter +type FooterSummary struct { // Renamed to avoid conflict + Label string + Value float64 +} + +func (s FooterSummary) Format() string { return fmt.Sprintf("%s: %.2f", s.Label, s.Value) } // Implements tw.Formatter + +func main() { + table := tablewriter.NewTable(os.Stdout) + table.Header("ColA", "ColB", "ColC", "ColD") // Header for context + + // Variadic Arguments + table.Footer("", "", "Total", 1408) + + // Slice of Any Type + // table.Footer([]any{"", "", "Total", 1408.50}) + + table.Render() // Render this table + + fmt.Println("--- Another Table with Formatter Footer ---") + table2 := tablewriter.NewTable(os.Stdout) + table2.Header("Summary Info") // Single column header + // Using Formatter for a single cell footer + table2.Footer(FooterSummary{Label: "Grand Total", Value: 1408.50}) // Single cell: "Grand Total: 1408.50" + table2.Render() +} +``` + +**Key Changes**: +- **Method**: `SetFooter([]string)` replaced by `Footer(...any)` (`tablewriter.go:Footer`). +- **Input Flexibility**: Like `Header`, accepts variadic arguments, slices, or structs with `Formatter`/`Stringer` support (`zoo.go:processVariadic`). +- **Streaming**: Footers are buffered via `streamStoreFooter` and rendered during `Close()` (`stream.go:streamStoreFooter`, `stream.go:streamRenderFooter`). +- **Formatting**: Controlled by `Config.Footer` settings (e.g., `Alignment`, `AutoWrap`) (`zoo.go:prepareTableSection`). + +**Migration Tips**: +- Replace `SetFooter` with `Footer`, preferring variadic input for simplicity. +- Use `tw.Formatter` for custom footer formatting (e.g., formatted numbers). +- Ensure footer cell count matches headers/rows to maintain alignment. +- In streaming mode, call `Footer()` before `Close()` to include it in rendering. + +**Potential Pitfalls**: +- **Alignment Differences**: Default `Footer.Alignment.Global = tw.AlignRight` differs from rows (`tw.AlignLeft`); adjust if needed (`config.go`). +- **Streaming Timing**: Footers not rendered until `Close()`; ensure `Close()` is called (`stream.go`). +- **Empty Footers**: Missing footers may affect table appearance; use placeholders if needed. + +### 3. Rendering the Table +Rendering has been overhauled to support both batch and streaming modes, with mandatory error handling for robustness. + +**Old (v0.0.5):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +func main() { + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"Data"}) + table.Append([]string{"Example"}) + table.Render() +} +``` + +**New (v1.0.x) - Batch Mode:** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "log" + "os" +) + +func main() { + table := tablewriter.NewTable(os.Stdout) + table.Header("Data") + table.Append("Example") + if err := table.Render(); err != nil { + log.Fatalf("Table rendering failed: %v", err) + } +} +``` + +**New (v1.0.x) - Streaming Mode:** +```go +package main + +import ( + "fmt" + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + "log" + "os" +) + +func main() { + tableStream := tablewriter.NewTable(os.Stdout, + tablewriter.WithConfig(tablewriter.Config{ + Stream: tw.StreamConfig{Enable: true}, + Widths: tw.CellWidth{ + Global: 12, // Fixed column width for streaming + PerColumn: tw.NewMapper[int, int]().Set(0, 15), // Column 0 at 15 + }, + }), + ) + // Alternative: Using WithStreaming Option + // tableStream := tablewriter.NewTable(os.Stdout, + // tablewriter.WithStreaming(tw.StreamConfig{Enable: true}), + // tablewriter.WithColumnMax(12), // Sets Config.Widths.Global + // ) + + if err := tableStream.Start(); err != nil { + log.Fatalf("Failed to start table stream: %v", err) + } + tableStream.Header("Column 1", "Column 2") + for i := 0; i < 3; i++ { + if err := tableStream.Append(fmt.Sprintf("Data %d-1", i), fmt.Sprintf("Data %d-2", i)); err != nil { + log.Printf("Failed to append row %d: %v", i, err) // Log and continue or handle differently + } + } + tableStream.Footer("End", "Summary") + if err := tableStream.Close(); err != nil { + log.Fatalf("Failed to close table stream: %v", err) + } +} +``` + +**Key Changes**: +- **Batch Mode**: + - `Render()` processes all data (headers, rows, footers) in one go (`tablewriter.go:render`). + - Calculates column widths dynamically based on content, `Config.Widths`, `ColMaxWidths`, and `MaxWidth` (`zoo.go:calculateAndNormalizeWidths`). + - Invokes renderer methods: `Start()`, `Header()`, `Row()`, `Footer()`, `Line()`, `Close()` (`tw/renderer.go`). + - Returns errors for invalid configurations or I/O issues. +- **Streaming Mode**: + - Enabled via `Config.Stream.Enable` or `WithStreaming(tw.StreamConfig{Enable: true})` (`tablewriter.go:WithStreaming`). + - `Start()` initializes the stream, fixing column widths based on `Config.Widths` or first data (header/row) (`stream.go:streamCalculateWidths`). + - `Header()`, `Append()` render immediately (`stream.go:streamRenderHeader`, `stream.go:streamAppendRow`). + - `Footer()` buffers data, rendered by `Close()` (`stream.go:streamStoreFooter`, `stream.go:streamRenderFooter`). + - `Close()` finalizes rendering with footer and borders (`stream.go:Close`). + - All methods return errors for robust handling. +- **Error Handling**: Mandatory error checks replace silent failures, improving reliability. + +**Migration Tips**: +- Replace `Render()` calls with error-checked versions in batch mode. +- For streaming, adopt `Start()`, `Append()`, `Close()` workflow, ensuring `Start()` precedes data input. +- Set `Config.Widths` for consistent column widths in streaming mode (`config.go`). +- Use `WithRendition` to customize visual output, as renderer settings are decoupled (`tablewriter.go`). +- Test rendering with small datasets to verify configuration before scaling. + +**Potential Pitfalls**: +- **Missing Error Checks**: Failing to check errors can miss rendering failures; always use `if err != nil`. +- **Streaming Widths**: Widths are fixed after `Start()`; inconsistent data may cause truncation (`stream.go`). +- **Renderer Misconfiguration**: Ensure `tw.Rendition` matches desired output style (`tw/renderer.go`). +- **Incomplete Streaming**: Forgetting `Close()` in streaming mode omits footer and final borders (`stream.go`). +- **Batch vs. Streaming**: Using `Render()` in streaming mode causes errors; use `Start()`/`Close()` instead (`tablewriter.go`). + + +## Styling and Appearance Configuration + +Styling in v1.0.x is split between `tablewriter.Config` for data processing (e.g., alignment, padding, wrapping) and `tw.Rendition` for visual rendering (e.g., borders, symbols, lines). This section details how to migrate v0.0.5 styling methods to v1.0.x, providing examples, best practices, and migration tips to maintain or enhance table appearance. + +### 4.1. Table Styles +Table styles define the visual structure through border and separator characters. In v0.0.5, styles were set via individual separator methods, whereas v1.0.x uses `tw.Rendition.Symbols` for a cohesive approach, offering predefined styles and custom symbol sets. + +**Available Table Styles**: +The `tw.Symbols` interface (`tw/symbols.go`) supports a variety of predefined styles, each tailored to specific use cases, as well as custom configurations for unique requirements. + +| Style Name | Use Case | Symbols Example | Recommended Context | +|----------------|-----------------------------------|-------------------------------------|-----------------------------------------| +| `StyleASCII` | Simple, terminal-friendly | `+`, `-`, `|` | Basic CLI output, minimal dependencies; ensures compatibility across all terminals, including older or non-Unicode systems. | +| `StyleLight` | Clean, lightweight borders | `┌`, `└`, `─`, `│` | Modern terminals, clean and professional aesthetics; suitable for most CLI applications requiring a modern look. | +| `StyleHeavy` | Thick, prominent borders | `┏`, `┗`, `━`, `┃` | High-visibility reports, dashboards, or logs; emphasizes table structure for critical data presentation. | +| `StyleDouble` | Double-line borders | `╔`, `╚`, `═`, `║` | Formal documents, structured data exports; visually distinct for presentations or printed outputs. | +| `StyleRounded` | Rounded corners, aesthetic | `╭`, `╰`, `─`, `│` | User-friendly CLI applications, reports; enhances visual appeal with smooth, rounded edges. | +| `StyleMarkdown`| Markdown-compatible | `|`, `-`, `:` | Documentation, GitHub READMEs, or Markdown-based platforms; ensures proper rendering in Markdown viewers. | +*Note: `StyleBold` was mentioned but not defined in the symbols code, `StyleHeavy` is similar.* + +**Old (v0.0.5):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +func main() { + table := tablewriter.NewWriter(os.Stdout) + // table.SetCenterSeparator("*") // Example, actual v0.0.5 method might vary + // table.SetColumnSeparator("!") + // table.SetRowSeparator("=") + // table.SetBorder(true) + table.SetHeader([]string{"Header1", "Header2"}) + table.Append([]string{"Cell1", "Cell2"}) + table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + // Using a Predefined Style (Rounded Corners for Aesthetic Output) + tableStyled := tablewriter.NewTable(os.Stdout, + tablewriter.WithRenderer(renderer.NewBlueprint()), // Ensure renderer is set + tablewriter.WithRendition(tw.Rendition{ + Symbols: tw.NewSymbols(tw.StyleRounded), // Predefined rounded style + Borders: tw.Border{Top: tw.On, Bottom: tw.On, Left: tw.On, Right: tw.On}, + Settings: tw.Settings{ + Separators: tw.Separators{BetweenRows: tw.On}, // Lines between rows + }, + }), + ) + tableStyled.Header("Name", "Status") + tableStyled.Append("Node1", "Ready") + tableStyled.Render() + + // Using Fully Custom Symbols (Mimicking v0.0.5 Custom Separators) + mySymbols := tw.NewSymbolCustom("my-style"). // Fluent builder for custom symbols + WithCenter("*"). // Junction of lines + WithColumn("!"). // Vertical separator + WithRow("="). // Horizontal separator + WithTopLeft("/"). + WithTopMid("-"). + WithTopRight("\\"). + WithMidLeft("["). + WithMidRight("]"). // Corrected from duplicate WithMidRight("+") + // WithMidMid was not a method in SymbolCustom + WithBottomLeft("\\"). + WithBottomMid("_"). + WithBottomRight("/"). + WithHeaderLeft("("). + WithHeaderMid("-"). + WithHeaderRight(")") // Header-specific + tableCustomSymbols := tablewriter.NewTable(os.Stdout, + tablewriter.WithRenderer(renderer.NewBlueprint()), + tablewriter.WithRendition(tw.Rendition{ + Symbols: mySymbols, + Borders: tw.Border{Top: tw.On, Bottom: tw.On, Left: tw.On, Right: tw.On}, + }), + ) + tableCustomSymbols.Header("Name", "Status") + tableCustomSymbols.Append("Node1", "Ready") + tableCustomSymbols.Render() +} +``` + +**Output (Styled, Rounded):** +``` +╭───────┬────────╮ +│ NAME │ STATUS │ +├───────┼────────┤ +│ Node1 │ Ready │ +╰───────┴────────╯ +``` + +**Output (Custom Symbols):** +``` +/=======-========\ +! NAME ! STATUS ! +[=======*========] +! Node1 ! Ready ! +\=======_========/ +``` + +**Key Changes**: +- **Deprecated Methods**: `SetCenterSeparator`, `SetColumnSeparator`, `SetRowSeparator`, and `SetBorder` are deprecated and moved to `deprecated.go`. These are replaced by `tw.Rendition.Symbols` and `tw.Rendition.Borders` for a unified styling approach (`tablewriter.go`). +- **Symbol System**: The `tw.Symbols` interface defines all drawing characters used for table lines, junctions, and corners (`tw/symbols.go:Symbols`). + - `tw.NewSymbols(tw.BorderStyle)` provides predefined styles (e.g., `tw.StyleASCII`, `tw.StyleMarkdown`, `tw.StyleRounded`) for common use cases. (Note: `tw.Style` should be `tw.BorderStyle`) + - `tw.NewSymbolCustom(name string)` enables fully custom symbol sets via a fluent builder, allowing precise control over each character (e.g., `WithCenter`, `WithTopLeft`). +- **Renderer Dependency**: Styling requires a renderer, set via `WithRenderer`; the default is `renderer.NewBlueprint()` if not specified (`tablewriter.go:WithRenderer`). +- **Border Control**: `tw.Rendition.Borders` uses `tw.State` (`tw.On`, `tw.Off`) to enable or disable borders on each side (Top, Bottom, Left, Right) (`tw/renderer.go:Border`). +- **Extensibility**: Custom styles can be combined with custom renderers for non-text outputs, enhancing flexibility (`tw/renderer.go`). + +**Migration Tips**: +- Replace individual separator setters (`SetCenterSeparator`, etc.) with `tw.NewSymbols` for predefined styles or `tw.NewSymbolCustom` for custom designs to maintain or enhance v0.0.5 styling. +- Use `WithRendition` to apply `tw.Rendition` settings, ensuring a renderer is explicitly set to avoid default behavior (`tablewriter.go`). +- Test table styles in your target environment (e.g., terminal, Markdown viewer, or log file) to ensure compatibility with fonts and display capabilities. +- For Markdown-based outputs (e.g., GitHub READMEs), use `tw.StyleMarkdown` to ensure proper rendering in Markdown parsers (`tw/symbols.go`). +- Combine `tw.Rendition.Symbols` with `tw.Rendition.Borders` and `tw.Rendition.Settings` to replicate or improve v0.0.5’s border and line configurations (`tw/renderer.go`). +- Document custom symbol sets in code comments to aid maintenance, as they can be complex (`tw/symbols.go`). + +**Potential Pitfalls**: +- **Missing Renderer**: If `WithRenderer` is omitted, `NewTable` defaults to `renderer.NewBlueprint` with minimal styling, which may not match v0.0.5’s `SetBorder(true)` behavior; always specify for custom styles (`tablewriter.go`). +- **Incomplete Custom Symbols**: When using `tw.NewSymbolCustom`, failing to set all required symbols (e.g., `TopLeft`, `Center`, `HeaderLeft`) can cause rendering errors or inconsistent visuals; ensure all necessary characters are defined (`tw/symbols.go`). +- **Terminal Compatibility Issues**: Advanced styles like `StyleDouble` or `StyleHeavy` may not render correctly in older terminals or non-Unicode environments; use `StyleASCII` for maximum compatibility across platforms (`tw/symbols.go`). +- **Border and Symbol Mismatch**: Inconsistent `tw.Rendition.Borders` and `tw.Symbols` settings (e.g., enabling borders but using minimal symbols) can lead to visual artifacts; test with small tables to verify alignment (`tw/renderer.go`). +- **Markdown Rendering**: Non-Markdown styles (e.g., `StyleRounded`) may not render correctly in Markdown viewers; use `StyleMarkdown` for documentation or web-based outputs (`tw/symbols.go`). + +### 4.2. Borders and Separator Lines +Borders and internal lines define the table’s structural appearance, controlling the visibility of outer edges and internal divisions. In v0.0.5, these were set via specific methods, while v1.0.x uses `tw.Rendition` fields for a more integrated approach. + +**Old (v0.0.5):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +func main() { + table := tablewriter.NewWriter(os.Stdout) + // table.SetBorder(false) // Disable all outer borders + // table.SetRowLine(true) // Enable lines between data rows + // table.SetHeaderLine(true) // Enable line below header + table.SetHeader([]string{"Header1", "Header2"}) + table.Append([]string{"Cell1", "Cell2"}) + table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + // Standard Bordered Table with Internal Lines + table := tablewriter.NewTable(os.Stdout, + tablewriter.WithRenderer(renderer.NewBlueprint()), + tablewriter.WithRendition(tw.Rendition{ + Borders: tw.Border{ // Outer table borders + Left: tw.On, + Right: tw.On, + Top: tw.On, + Bottom: tw.On, + }, + Settings: tw.Settings{ + Lines: tw.Lines{ // Major internal separator lines + ShowHeaderLine: tw.On, // Line after header + ShowFooterLine: tw.On, // Line before footer (if footer exists) + }, + Separators: tw.Separators{ // General row and column separators + BetweenRows: tw.On, // Horizontal lines between data rows + BetweenColumns: tw.On, // Vertical lines between columns + }, + }, + }), + ) + table.Header("Name", "Status") + table.Append("Node1", "Ready") + table.Render() + + // Borderless Table (kubectl-style, No Lines or Separators) + // Configure the table with a borderless, kubectl-style layout + config := tablewriter.NewConfigBuilder(). + Header(). + Padding(). + WithGlobal(tw.PaddingNone). // No header padding + Build(). + Alignment(). + WithGlobal(tw.AlignLeft). // Left-align header + Build(). + Row(). + Padding(). + WithGlobal(tw.PaddingNone). // No row padding + Build(). + Alignment(). + WithGlobal(tw.AlignLeft). // Left-align rows + Build(). + Footer(). + Padding(). + WithGlobal(tw.PaddingNone). // No footer padding + Build(). + Alignment(). + WithGlobal(tw.AlignLeft). // Left-align footer (if used) + Build(). + WithDebug(true). // Enable debug logging + Build() + + // Create borderless table + tableBorderless := tablewriter.NewTable(os.Stdout, + tablewriter.WithRenderer(renderer.NewBlueprint()), // Assumes valid renderer + tablewriter.WithRendition(tw.Rendition{ + Borders: tw.BorderNone, // No borders + Symbols: tw.NewSymbols(tw.StyleNone), // No border symbols + Settings: tw.Settings{ + Lines: tw.LinesNone, // No header/footer lines + Separators: tw.SeparatorsNone, // No row/column separators + }, + }), + tablewriter.WithConfig(config), + ) + + // Set headers and data + tableBorderless.Header("Name", "Status") + tableBorderless.Append("Node1", "Ready") + tableBorderless.Render() +} +``` + +**Output (Standard Bordered):** +``` +┌───────┬────────┐ +│ Name │ Status │ +├───────┼────────┤ +│ Node1 │ Ready │ +└───────┴────────┘ +``` + +**Output (Borderless):** +``` +NAME STATUS +Node1 Ready +``` + +**Key Changes**: +- **Deprecated Methods**: `SetBorder`, `SetRowLine`, and `SetHeaderLine` are deprecated and moved to `deprecated.go`. These are replaced by fields in `tw.Rendition` (`tw/renderer.go`): + - `Borders`: Controls outer table borders (`tw.Border`) with `tw.State` (`tw.On`, `tw.Off`) for each side (Top, Bottom, Left, Right). + - `Settings.Lines`: Manages major internal lines (`ShowHeaderLine` for header, `ShowFooterLine` for footer) (`tw.Lines`). + - `Settings.Separators`: Controls general separators between rows (`BetweenRows`) and columns (`BetweenColumns`) (`tw.Separators`). +- **Presets for Simplicity**: `tw.BorderNone`, `tw.LinesNone`, and `tw.SeparatorsNone` provide quick configurations for minimal or borderless tables (`tw/preset.go`). +- **Renderer Integration**: Border and line settings are applied via `WithRendition`, requiring a renderer to be set (`tablewriter.go:WithRendition`). +- **Granular Control**: Each border side and line type can be independently configured, offering greater flexibility than v0.0.5’s boolean toggles. +- **Dependency on Symbols**: The appearance of borders and lines depends on `tw.Rendition.Symbols`; ensure compatible symbol sets (`tw/symbols.go`). + +**Migration Tips**: +- Replace `SetBorder(false)` with `tw.Rendition.Borders = tw.BorderNone` to disable all outer borders (`tw/preset.go`). +- Use `tw.Rendition.Settings.Separators.BetweenRows = tw.On` to replicate `SetRowLine(true)`, ensuring row separators are visible (`tw/renderer.go`). +- Set `tw.Rendition.Settings.Lines.ShowHeaderLine = tw.On` to mimic `SetHeaderLine(true)` for a line below the header (`tw/renderer.go`). +- For kubectl-style borderless tables, combine `tw.BorderNone`, `tw.LinesNone`, `tw.SeparatorsNone`, and `WithPadding(tw.PaddingNone)` (applied via `ConfigBuilder` or `WithConfig`) to eliminate all lines and spacing (`tw/preset.go`, `config.go`). +- Test border and line configurations with small tables to verify visual consistency, especially when combining with custom `tw.Symbols`. +- Use `WithDebug(true)` to log rendering details if borders or lines appear incorrectly (`config.go`). + +**Potential Pitfalls**: +- **Separator Absence**: If `tw.Rendition.Settings.Separators.BetweenColumns` is `tw.Off` and borders are disabled, columns may lack visual separation; use `tw.CellPadding` or ensure content spacing (`tw/cell.go`). +- **Line and Border Conflicts**: Mismatched settings (e.g., enabling `ShowHeaderLine` but disabling `Borders.Top`) can cause uneven rendering; align `Borders`, `Lines`, and `Separators` settings (`tw/renderer.go`). +- **Renderer Dependency**: Border settings require a renderer; omitting `WithRenderer` defaults to `renderer.NewBlueprint` with minimal styling, which may not match v0.0.5 expectations (`tablewriter.go`). +- **Streaming Limitations**: In streaming mode, separator rendering is fixed after `Start()`; ensure `tw.Rendition` is set correctly before rendering begins (`stream.go`). +- **Symbol Mismatch**: Using minimal `tw.Symbols` (e.g., `StyleASCII`) with complex `Borders` settings may lead to visual artifacts; test with matching symbol sets (`tw/symbols.go`). + +### 4.3. Alignment +Alignment controls the positioning of text within table cells, now configurable per section (Header, Row, Footer) with both global and per-column options for greater precision. + +**Old (v0.0.5):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" // Assuming ALIGN_CENTER etc. were constants here + "os" +) + +const ( // Mocking v0.0.5 constants for example completeness + ALIGN_CENTER = 1 // Example values + ALIGN_LEFT = 2 +) + + +func main() { + table := tablewriter.NewWriter(os.Stdout) + // table.SetAlignment(ALIGN_CENTER) // Applied to data rows + // table.SetHeaderAlignment(ALIGN_LEFT) // Applied to header + // No specific footer alignment setter + table.SetHeader([]string{"Header1", "Header2"}) + table.Append([]string{"Cell1", "Cell2"}) + table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + cfg := tablewriter.Config{ + Header: tw.CellConfig{ + Alignment: tw.CellAlignment{Global: tw.AlignLeft}, + }, + Row: tw.CellConfig{ + Alignment: tw.CellAlignment{ + Global: tw.AlignCenter, + PerColumn: []tw.Align{tw.AlignLeft, tw.AlignRight}, // Col 0 left, Col 1 right + }, + }, + Footer: tw.CellConfig{ + Alignment: tw.CellAlignment{Global: tw.AlignRight}, + }, + } + table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg)) + + table.Header("Name", "Status") + table.Append("Node1", "Ready") + table.Footer("", "Active") // Ensure footer has content to show alignment + table.Render() +} +``` + +**Output:** +``` +┌───────┬────────┐ +│ NAME │ STATUS │ +├───────┼────────┤ +│ Node1 │ Ready │ +├───────┼────────┤ +│ │ Active │ +└───────┴────────┘ +``` + +**Key Changes**: +- **Deprecated Methods**: `SetAlignment` and `SetHeaderAlignment` are replaced by `WithRowAlignment`, `WithHeaderAlignment`, `WithFooterAlignment`, or direct `Config` settings (`config.go`). These old methods are retained in `deprecated.go` for compatibility but should be migrated. +- **Alignment Structure**: Alignment is managed within `tw.CellConfig.Alignment` (`tw/cell.go:CellAlignment`), which includes: + - `Global`: A single `tw.Align` value applied to all cells in the section (`tw.AlignLeft`, `tw.AlignCenter`, `tw.AlignRight`, `tw.AlignNone`). + - `PerColumn`: A slice of `tw.Align` values for column-specific alignment, overriding `Global` for specified columns. +- **Footer Alignment**: v1.0.x introduces explicit footer alignment via `WithFooterAlignment` or `Config.Footer.Alignment`, addressing v0.0.5’s lack of footer-specific control (`config.go`). +- **Type Safety**: `tw.Align` string constants replace v0.0.5’s integer constants (e.g., `ALIGN_CENTER`), improving clarity and reducing errors (`tw/types.go`). +- **Builder Support**: `ConfigBuilder` provides `Alignment()` methods for each section. `ForColumn(idx).WithAlignment()` applies alignment to a specific column across all sections (`config.go:ConfigBuilder`, `config.go:ColumnConfigBuilder`). +- **Deprecated Fields**: `tw.CellConfig.ColumnAligns` (slice) and `tw.CellFormatting.Alignment` (single value) are supported for backward compatibility but should be migrated to `tw.CellAlignment.Global` and `tw.CellAlignment.PerColumn` (`tw/cell.go`). + +**Migration Tips**: +- Replace `SetAlignment(ALIGN_X)` with `WithRowAlignment(tw.AlignX)` or `Config.Row.Alignment.Global = tw.AlignX` to set row alignment (`config.go`). +- Use `WithHeaderAlignment(tw.AlignX)` for headers and `WithFooterAlignment(tw.AlignX)` for footers to maintain or adjust v0.0.5 behavior (`config.go`). +- Specify per-column alignments with `ConfigBuilder.Row().Alignment().WithPerColumn([]tw.Align{...})` or by setting `Config.Row.Alignment.PerColumn` for fine-grained control (`config.go`). +- Use `ConfigBuilder.ForColumn(idx).WithAlignment(tw.AlignX)` to apply consistent alignment to a specific column across all sections (Header, Row, Footer) (`config.go`). +- Verify alignment settings against defaults (`Header: tw.AlignCenter`, `Row: tw.AlignLeft`, `Footer: tw.AlignRight`) to ensure expected output (`config.go:defaultConfig`). +- Test alignment with varied cell content lengths to confirm readability, especially when combined with wrapping or padding settings (`zoo.go:prepareContent`). + +**Potential Pitfalls**: +- **Alignment Precedence**: `PerColumn` settings override `Global` within a section; ensure column-specific alignments are intentional (`tw/cell.go:CellAlignment`). +- **Deprecated Fields**: Relying on `ColumnAligns` or `tw.CellFormatting.Alignment` is temporary; migrate to `tw.CellAlignment` to avoid future issues (`tw/cell.go`). +- **Cell Count Mismatch**: Rows or footers with fewer cells than headers can cause alignment errors; pad with empty strings (`""`) to match column count (`zoo.go`). +- **Streaming Width Impact**: In streaming mode, alignment depends on fixed column widths set by `Config.Widths`; narrow widths may truncate content, misaligning text (`stream.go:streamCalculateWidths`). +- **Default Misalignment**: The default `Footer.Alignment.Global = tw.AlignRight` differs from rows (`tw.AlignLeft`); explicitly set `WithFooterAlignment` if consistency is needed (`config.go`). + +### 4.4. Auto-Formatting (Header Title Case) +Auto-formatting applies transformations like title case to cell content, primarily for headers to enhance readability, but it can be enabled for rows or footers in v1.0.x. + +**Old (v0.0.5):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +func main() { + table := tablewriter.NewWriter(os.Stdout) + // table.SetAutoFormatHeaders(true) // Default: true; applies title case to headers + table.SetHeader([]string{"col_one", "status_report"}) + table.Append([]string{"Node1", "Ready"}) + table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + // Using Direct Config Struct to turn OFF default AutoFormat for Header + cfg := tablewriter.Config{ + Header: tw.CellConfig{Formatting: tw.CellFormatting{AutoFormat: tw.Off}}, // Turn OFF title case for headers + Row: tw.CellConfig{Formatting: tw.CellFormatting{AutoFormat: tw.Off}}, // No formatting for rows (default) + Footer: tw.CellConfig{Formatting: tw.CellFormatting{AutoFormat: tw.Off}}, // No formatting for footers (default) + } + tableNoAutoFormat := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg)) + + tableNoAutoFormat.Header("col_one", "status_report") // Stays as "col_one", "status_report" + tableNoAutoFormat.Append("Node1", "Ready") + tableNoAutoFormat.Render() + + // Using Option Function for Headers (default is ON, this makes it explicit) + tableWithAutoFormat := tablewriter.NewTable(os.Stdout, + tablewriter.WithHeaderAutoFormat(tw.On), // Explicitly enable title case (default behavior) + ) + + tableWithAutoFormat.Header("col_one", "status_report") // Becomes "COL ONE", "STATUS REPORT" + tableWithAutoFormat.Append("Node1", "Ready") + tableWithAutoFormat.Render() +} +``` + +**Output:** +``` +┌─────────┬───────────────┐ +│ col_one │ status_report │ +├─────────┼───────────────┤ +│ Node1 │ Ready │ +└─────────┴───────────────┘ +┌─────────┬───────────────┐ +│ COL ONE │ STATUS REPORT │ +├─────────┼───────────────┤ +│ Node1 │ Ready │ +└─────────┴───────────────┘ +``` + +**Key Changes**: +- **Method**: `SetAutoFormatHeaders` is replaced by `Config.Header.Formatting.AutoFormat`, or equivalent builder methods (`config.go`). +- **Extended Scope**: Auto-formatting can now be applied to rows and footers via `Config.Row.Formatting.AutoFormat` and `Config.Footer.Formatting.AutoFormat`, unlike v0.0.5’s header-only support (`tw/cell.go`). +- **Type Safety**: `tw.State` (`tw.On`, `tw.Off`, `tw.Unknown`) controls formatting state, replacing boolean flags (`tw/state.go`). +- **Behavior**: When `tw.On`, the `tw.Title` function converts text to uppercase and replaces underscores and some periods with spaces (e.g., "col_one" → "COL ONE") (`tw/fn.go:Title`, `zoo.go:prepareContent`). +- **Defaults**: `Header.Formatting.AutoFormat = tw.On`, `Row.Formatting.AutoFormat = tw.Off`, `Footer.Formatting.AutoFormat = tw.Off` (`config.go:defaultConfig`). + +**Migration Tips**: +- To maintain v0.0.5’s default header title case behavior, no explicit action is needed as `WithHeaderAutoFormat(tw.On)` is the default. If you were using `SetAutoFormatHeaders(false)`, you'd use `WithHeaderAutoFormat(tw.Off)`. +- Explicitly set `WithRowAutoFormat(tw.Off)` or `WithFooterAutoFormat(tw.Off)` if you want to ensure rows and footers remain unformatted, as v1.0.x allows enabling formatting for these sections (`config.go`). +- Use `ConfigBuilder.Header().Formatting().WithAutoFormat(tw.State)` for precise control over formatting per section (`config.go`). +- Test header output with underscores or periods (e.g., "my_column") to verify title case transformation meets expectations. +- For custom formatting beyond title case (e.g., custom capitalization), use `tw.CellFilter` instead of `AutoFormat` (`tw/cell.go`). + +**Potential Pitfalls**: +- **Default Header Formatting**: `Header.Formatting.AutoFormat = tw.On` by default may unexpectedly alter header text (e.g., "col_one" → "COL ONE"); disable with `WithHeaderAutoFormat(tw.Off)` if raw headers are preferred (`config.go`). +- **Row/Footer Formatting**: Enabling `AutoFormat` for rows or footers (not default) applies title case, which may not suit data content; verify with `tw.Off` unless intended (`tw/cell.go`). +- **Filter Conflicts**: Combining `AutoFormat` with `tw.CellFilter` can lead to overlapping transformations; prioritize filters for complex formatting (`zoo.go:prepareContent`). +- **Performance Overhead**: Auto-formatting large datasets may add minor processing time; disable for performance-critical applications (`zoo.go`). + +### 4.5. Text Wrapping +Text wrapping determines how long cell content is handled within width constraints, offering more options in v1.0.x compared to v0.0.5’s binary toggle. + +**Old (v0.0.5):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +func main() { + table := tablewriter.NewWriter(os.Stdout) + // table.SetAutoWrapText(true) // Enable normal word wrapping + // table.SetAutoWrapText(false) // Disable wrapping + table.SetHeader([]string{"Long Header Text Example", "Status"}) + table.Append([]string{"This is some very long cell content that might wrap", "Ready"}) + table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" // For tw.WrapNormal etc. + "os" +) + +func main() { + // Using Option Functions for Quick Wrapping Settings on a specific section (e.g., Row) + // To actually see wrapping, a MaxWidth for the table or columns is needed. + table := tablewriter.NewTable(os.Stdout, + tablewriter.WithRowAutoWrap(tw.WrapNormal), // Set row wrapping + tablewriter.WithHeaderAutoWrap(tw.WrapTruncate), // Header truncates (default) + tablewriter.WithMaxWidth(30), // Force wrapping by setting table max width + ) + table.Header("Long Header Text", "Status") + table.Append("This is a very long cell content", "Ready") + table.Footer("Summary", "Active") + table.Render() + + // For more fine-grained control (e.g., different wrapping for header, row, footer): + cfgBuilder := tablewriter.NewConfigBuilder() + cfgBuilder.Header().Formatting().WithAutoWrap(tw.WrapTruncate) // Header: Truncate + cfgBuilder.Row().Formatting().WithAutoWrap(tw.WrapNormal) // Row: Normal wrap + cfgBuilder.Footer().Formatting().WithAutoWrap(tw.WrapBreak) // Footer: Break words + cfgBuilder.WithMaxWidth(40) // Overall table width constraint + + tableFullCfg := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build())) + tableFullCfg.Header("Another Very Long Header Example That Will Be Truncated", "Info") + tableFullCfg.Append("This is an example of row content that should wrap normally based on available space.", "Detail") + tableFullCfg.Footer("FinalSummaryInformationThatMightBreakAcrossLines", "End") + tableFullCfg.Render() +} +``` + +**Output (tableOpt):** +``` +┌──────────────┬────────┐ +│ LONG HEADER… │ STATUS │ +├──────────────┼────────┤ +│ This is a │ Ready │ +│ very long │ │ +│ cell content │ │ +├──────────────┼────────┤ +│ Summary │ Active │ +└──────────────┴────────┘ +``` +*(Second table output will vary based on content and width)* + +**Key Changes**: +- **Method**: `SetAutoWrapText` is replaced by `Config.
.Formatting.AutoWrap` or specific `With
AutoWrap` options (`config.go`). +- **Wrapping Modes**: `int` constants for `AutoWrap` (e.g., `tw.WrapNone`, `tw.WrapNormal`, `tw.WrapTruncate`, `tw.WrapBreak`) replace v0.0.5’s binary toggle (`tw/tw.go`). +- **Section-Specific Control**: Wrapping is configurable per section (Header, Row, Footer), unlike v0.0.5’s global setting (`tw/cell.go`). +- **Defaults**: `Header: tw.WrapTruncate`, `Row: tw.WrapNormal`, `Footer: tw.WrapNormal` (`config.go:defaultConfig`). +- **Width Dependency**: Wrapping behavior relies on width constraints set by `Config.Widths` (fixed column widths), `Config.
.ColMaxWidths` (max content width), or `Config.MaxWidth` (total table width) (`zoo.go:calculateContentMaxWidth`, `zoo.go:prepareContent`). + +**Migration Tips**: +- Replace `SetAutoWrapText(true)` with `WithRowAutoWrap(tw.WrapNormal)` to maintain v0.0.5’s default wrapping for rows (`config.go`). +- Use `WithHeaderAutoWrap(tw.WrapTruncate)` to align with v1.0.x’s default header behavior, ensuring long headers are truncated (`config.go`). +- Set `Config.Widths` or `Config.MaxWidth` explicitly to enforce wrapping, as unconstrained widths may prevent wrapping (`config.go`). +- Use `ConfigBuilder.
().Formatting().WithAutoWrap(int_wrap_mode)` for precise control over wrapping per section (`config.go`). +- Test wrapping with varied content lengths (e.g., short and long text) to ensure readability and proper width allocation. +- Consider `tw.WrapNormal` for data rows to preserve content integrity, reserving `tw.WrapTruncate` for headers or footers (`tw/tw.go`). + +**Potential Pitfalls**: +- **Missing Width Constraints**: Without `Config.Widths`, `ColMaxWidths`, or `MaxWidth`, wrapping may not occur, leading to overflow; always define width limits for wrapping (`zoo.go`). +- **Streaming Width Impact**: In streaming mode, wrapping depends on fixed widths set at `Start()`; narrow widths may truncate content excessively (`stream.go:streamCalculateWidths`). +- **Truncation Data Loss**: `tw.WrapTruncate` may obscure critical data in rows; use `tw.WrapNormal` or wider columns to retain content (`tw/tw.go`). +- **Performance Overhead**: Wrapping large datasets with `tw.WrapNormal` or `tw.WrapBreak` can add processing time; optimize widths for performance-critical applications (`zoo.go:prepareContent`). +- **Inconsistent Section Wrapping**: Default wrapping differs (`Header: tw.WrapTruncate`, `Row/Footer: tw.WrapNormal`); align settings if uniform behavior is needed (`config.go`). + +### 4.6. Padding +Padding adds spacing within cells, enhancing readability and affecting cell width calculations. v1.0.x introduces granular, per-side padding, replacing v0.0.5’s single inter-column padding control. + +**Old (v0.0.5):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +func main() { + table := tablewriter.NewWriter(os.Stdout) + // table.SetTablePadding("\t") // Set inter-column space character when borders are off + // Default: single space within cells + table.SetHeader([]string{"Header1"}) + table.Append([]string{"Cell1"}) + table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + // Using Direct Config Struct + cfg := tablewriter.Config{ + Header: tw.CellConfig{ + Padding: tw.CellPadding{ + Global: tw.Padding{Left: "[", Right: "]", Top: "-", Bottom: "-"}, // Padding for all header cells + PerColumn: []tw.Padding{ // Specific padding for header column 0 + {Left: ">>", Right: "<<", Top: "=", Bottom: "="}, // Overrides Global for column 0 + }, + }, + }, + Row: tw.CellConfig{ + Padding: tw.CellPadding{ + Global: tw.PaddingDefault, // One space left/right for all row cells + }, + }, + Footer: tw.CellConfig{ + Padding: tw.CellPadding{ + Global: tw.Padding{Top: "~", Bottom: "~"}, // Top/bottom padding for all footer cells + }, + }, + } + table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg)) + + table.Header("Name", "Status") // Two columns + table.Append("Node1", "Ready") + table.Footer("End", "Active") + table.Render() +} +``` + +**Output:** +``` +┌────────┬────────┐ +│========│[------]│ +│>>NAME<<│[STATUS]│ +│========│[------]│ +├────────┼────────┤ +│ Node1 │ Ready │ +│~~~~~~~~│~~~~~~~~│ +│End │Active │ +│~~~~~~~~│~~~~~~~~│ +└────────┴────────┘ +``` + +**Key Changes**: +- **No Direct Equivalent for `SetTablePadding`**: `SetTablePadding` controlled inter-column spacing when borders were off in v0.0.5; v1.0.x has no direct equivalent for *inter-column* spacing separate from cell padding. Inter-column visual separation is now primarily handled by `tw.Rendition.Settings.Separators.BetweenColumns` and the chosen `tw.Symbols`. +- **Granular Cell Padding**: `tw.CellPadding` (`tw/cell.go:CellPadding`) supports: + - `Global`: A `tw.Padding` struct with `Left`, `Right`, `Top`, `Bottom` strings and an `Overwrite` flag (`tw/types.go:Padding`). This padding is *inside* the cell. + - `PerColumn`: A slice of `tw.Padding` for column-specific padding, overriding `Global` for specified columns. +- **Per-Side Control**: `Top` and `Bottom` padding add extra lines *within* cells, unlike v0.0.5’s left/right-only spacing (`zoo.go:prepareContent`). +- **Defaults**: `tw.PaddingDefault` is `{Left: " ", Right: " "}` for all sections (applied inside cells); `Top` and `Bottom` are empty by default (`tw/preset.go`). +- **Width Impact**: Cell padding contributes to column widths, calculated in `Config.Widths` (`zoo.go:calculateAndNormalizeWidths`). +- **Presets**: `tw.PaddingNone` (`{Left: "", Right: "", Top: "", Bottom: "", Overwrite: true}`) removes padding for tight layouts (`tw/preset.go`). + +**Migration Tips**: +- To achieve spacing similar to `SetTablePadding("\t")` when borders are off, you would set cell padding: `WithPadding(tw.Padding{Left: "\t", Right: "\t"})`. If you truly mean space *between* columns and not *inside* cells, ensure `tw.Rendition.Settings.Separators.BetweenColumns` is `tw.On` and customize `tw.Symbols.Column()` if needed. +- Use `tw.PaddingNone` (e.g., via `ConfigBuilder.
().Padding().WithGlobal(tw.PaddingNone)`) for no cell padding. +- Set `Top` and `Bottom` padding for vertical spacing *within* cells, enhancing readability for multi-line content (`tw/types.go`). +- Use `ConfigBuilder.
().Padding().WithPerColumn` for column-specific padding to differentiate sections or columns (`config.go`). +- Test padding with varied content and widths to ensure proper alignment and spacing, especially with wrapping enabled (`zoo.go`). +- Combine padding with `Config.Widths` or `ColMaxWidths` to control total cell size (`config.go`). + +**Potential Pitfalls**: +- **Inter-Column Spacing vs. Cell Padding**: Be clear whether you want space *between* columns (separators) or *inside* cells (padding). `SetTablePadding` was ambiguous; v1.0.x distinguishes these. +- **Width Inflation**: Cell padding increases column widths, potentially exceeding `Config.MaxWidth` or causing truncation in streaming; adjust `Config.Widths` accordingly (`zoo.go`). +- **Top/Bottom Padding**: Non-empty `Top` or `Bottom` padding adds vertical lines *within* cells, increasing cell height; use sparingly for dense tables (`zoo.go:prepareContent`). +- **Streaming Constraints**: Padding is fixed in streaming mode after `Start()`; ensure `Config.Widths` accommodates padding (`stream.go`). +- **Default Padding**: `tw.PaddingDefault` adds spaces *inside* cells; set `tw.PaddingNone` for no internal cell padding (`tw/preset.go`). + +### 4.7. Column Widths (Fixed Widths and Max Content Widths) +Column width control in v1.0.x is more sophisticated, offering fixed widths, maximum content widths, and overall table width constraints, replacing v0.0.5’s limited `SetColMinWidth`. + +**Old (v0.0.5):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +func main() { + table := tablewriter.NewWriter(os.Stdout) + // table.SetColMinWidth(0, 10) // Set minimum width for column 0 + table.SetHeader([]string{"Header1", "Header2"}) + table.Append([]string{"Cell1-Content", "Cell2-Content"}) + table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + // Direct Config for Width Control + cfg := tablewriter.Config{ + Widths: tw.CellWidth{ // Fixed total column widths (content + padding) + Global: 20, // Default fixed width for all columns + PerColumn: tw.NewMapper[int, int]().Set(0, 15), // Column 0 fixed at 15 + }, + Header: tw.CellConfig{ + ColMaxWidths: tw.CellWidth{ // Max content width (excluding padding) for header cells + Global: 15, // Max content width for all header cells + PerColumn: tw.NewMapper[int, int]().Set(0, 10), // Header Col 0 max content at 10 + }, + // Default header padding is " " on left/right, so content 10 + padding 2 = 12. + // If Widths.PerColumn[0] is 15, there's space. + }, + Row: tw.CellConfig{ + ColMaxWidths: tw.CellWidth{Global: 18}, // Max content width for row cells (18 + default padding 2 = 20) + }, + MaxWidth: 80, // Constrain total table width (optional, columns might shrink) + } + tableWithCfg := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg)) + tableWithCfg.Header("Very Long Header Name", "Status Information") + tableWithCfg.Append("Some long content for the first column", "Ready") + tableWithCfg.Render() + + // Option Functions for Width Settings + tableWithOpts := tablewriter.NewTable(os.Stdout, + tablewriter.WithColumnMax(20), // Sets Config.Widths.Global (fixed total col width) + tablewriter.WithColumnWidths(tw.NewMapper[int, int]().Set(0, 15)), // Sets Config.Widths.PerColumn for col 0 + tablewriter.WithMaxWidth(80), // Sets Config.MaxWidth + // For max content width per section, you'd use WithHeaderConfig, WithRowConfig, etc. + // e.g., tablewriter.WithRowMaxWidth(18) // Sets Config.Row.ColMaxWidths.Global + ) + tableWithOpts.Header("Long Header", "Status") + tableWithOpts.Append("Long Content", "Ready") + tableWithOpts.Render() +} +``` + +**Output (tableWithCfg - illustrative, exact wrapping depends on content and full config):** +``` +┌───────────┬──────────────────┐ +│ VERY LONG │ STATUS INFORMAT… │ +│ HEADER NA…│ │ +├───────────┼──────────────────┤ +│ Some long │ Ready │ +│ content f…│ │ +└───────────┴──────────────────┘ +``` + +**Key Changes**: +- **Enhanced Width System**: v1.0.x introduces three levels of width control, replacing `SetColMinWidth` (`config.go`): + - **Config.Widths**: Sets fixed total widths (content + padding) for columns, applied globally or per-column (`tw.CellWidth`). + - `Global`: Default fixed width for all columns. + - `PerColumn`: `tw.Mapper[int, int]` for specific column widths. + - **Config.
.ColMaxWidths**: Sets maximum content widths (excluding padding) for a section (Header, Row, Footer) (`tw.CellWidth`). + - `Global`: Max content width for all cells in the section. + - `PerColumn`: `tw.Mapper[int, int]` for specific columns in the section. + - **Config.MaxWidth**: Constrains the total table width, shrinking columns proportionally if needed (`config.go`). +- **Streaming Support**: In streaming mode, `Config.Widths` fixes column widths at `Start()`; `ColMaxWidths` is used only for wrapping/truncation (`stream.go:streamCalculateWidths`). +- **Calculation Logic**: Widths are computed by `calculateAndNormalizeWidths` in batch mode and `streamCalculateWidths` in streaming mode, considering content, padding, and constraints (`zoo.go`, `stream.go`). +- **Deprecated Approach**: `SetColMinWidth` is replaced by `Config.Widths.PerColumn` or `Config.
.ColMaxWidths.PerColumn` for more precise control (`deprecated.go`). + +**Migration Tips**: +- Replace `SetColMinWidth(col, w)` with `WithColumnWidths(tw.NewMapper[int, int]().Set(col, w))` for fixed column widths or `Config.
.ColMaxWidths.PerColumn` for content width limits (`config.go`). +- Use `Config.Widths.Global` or `WithColumnMax(w)` to set a default fixed width for all columns, ensuring consistency (`tablewriter.go`). +- Apply `Config.MaxWidth` to constrain total table width, especially for wide datasets (`config.go`). +- Use `ConfigBuilder.ForColumn(idx).WithMaxWidth(w)` to set per-column content width limits across sections (`config.go`). *(Note: This sets it for Header, Row, and Footer)* +- In streaming mode, set `Config.Widths` before `Start()` to fix widths, avoiding content-based sizing (`stream.go`). +- Test width settings with varied content to ensure wrapping and truncation behave as expected (`zoo.go`). + +**Potential Pitfalls**: +- **Width Precedence**: `Config.Widths.PerColumn` overrides `Widths.Global`; `ColMaxWidths` applies *within* those fixed total widths for content wrapping/truncation (`zoo.go`). +- **Streaming Width Fixing**: Widths are locked after `Start()` in streaming; inconsistent data may cause truncation (`stream.go`). +- **Padding Impact**: Padding adds to total width when considering `Config.Widths`; account for `tw.CellPadding` when setting fixed column widths (`zoo.go`). +- **MaxWidth Shrinkage**: `Config.MaxWidth` may shrink columns unevenly; test with `MaxWidth` to avoid cramped layouts (`zoo.go`). +- **No Width Constraints**: Without `Widths` or `MaxWidth`, columns size to content, potentially causing overflow; define limits (`zoo.go`). + +### 4.8. Colors +Colors in v0.0.5 were applied via specific color-setting methods, while v1.0.x embeds ANSI escape codes in cell content or uses data-driven formatting for greater flexibility. + +**Old (v0.0.5):** +```go +package main +// Assuming tablewriter.Colors and color constants existed in v0.0.5 +// This is a mock representation as the actual v0.0.5 definitions are not provided. +// import "github.com/olekukonko/tablewriter" +// import "os" + +// type Colors []interface{} // Mock +// const ( +// Bold = 1; FgGreenColor = 2; FgRedColor = 3 // Mock constants +// ) + +func main() { + // table := tablewriter.NewWriter(os.Stdout) + // table.SetColumnColor( + // tablewriter.Colors{tablewriter.Bold, tablewriter.FgGreenColor}, // Column 0 + // tablewriter.Colors{tablewriter.FgRedColor}, // Column 1 + // ) + // table.SetHeader([]string{"Name", "Status"}) + // table.Append([]string{"Node1", "Ready"}) + // table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "fmt" + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" // For tw.Formatter + "os" +) + +// Direct ANSI Code Embedding +const ( + Reset = "\033[0m" + Bold = "\033[1m" + FgGreen = "\033[32m" + FgRed = "\033[31m" +) + +// Using tw.Formatter for Custom Types +type Status string + +func (s Status) Format() string { // Implements tw.Formatter + color := FgGreen + if s == "Error" || s == "Inactive" { + color = FgRed + } + return color + string(s) + Reset +} + +func main() { + // Example 1: Direct ANSI embedding + tableDirectANSI := tablewriter.NewTable(os.Stdout, + tablewriter.WithHeaderAutoFormat(tw.Off), // Keep header text as is for coloring + ) + tableDirectANSI.Header(Bold+FgGreen+"Name"+Reset, Bold+FgRed+"Status"+Reset) + tableDirectANSI.Append([]any{"Node1", FgGreen + "Ready" + Reset}) + tableDirectANSI.Append([]any{"Node2", FgRed + "Error" + Reset}) + tableDirectANSI.Render() + + fmt.Println("\n--- Table with Formatter for Colors ---") + + // Example 2: Using tw.Formatter + tableFormatter := tablewriter.NewTable(os.Stdout) + tableFormatter.Header("Name", "Status") // AutoFormat will apply to "NAME", "STATUS" + tableFormatter.Append([]any{"Alice", Status("Active")}) + tableFormatter.Append([]any{"Bob", Status("Inactive")}) + tableFormatter.Render() + + fmt.Println("\n--- Table with CellFilter for Colors ---") + // Example 3: Using CellFilter + tableWithFilters := tablewriter.NewTable(os.Stdout, + tablewriter.WithConfig( + tablewriter.NewConfigBuilder(). + Row(). + Filter(). + WithPerColumn([]func(string) string{ + nil, // No filter for Item column + func(s string) string { // Status column: apply color + if s == "Ready" || s == "Active" { + return FgGreen + s + Reset + } + return FgRed + s + Reset + }, + }). + Build(). // Return to RowConfigBuilder + Build(). // Return to ConfigBuilder + Build(), // Finalize Config + ), + ) + tableWithFilters.Header("Item", "Availability") + tableWithFilters.Append("ItemA", "Ready") + tableWithFilters.Append("ItemB", "Unavailable") + tableWithFilters.Render() +} +``` + +**Output (Text Approximation, Colors Not Shown):** +``` +┌──────┬────────┐ +│ Name │ Status │ +├──────┼────────┤ +│Node1 │ Ready │ +│Node2 │ Error │ +└──────┴────────┘ + +--- Table with Formatter for Colors --- +┌───────┬──────────┐ +│ NAME │ STATUS │ +├───────┼──────────┤ +│ Alice │ Active │ +│ Bob │ Inactive │ +└───────┴──────────┘ + +--- Table with CellFilter for Colors --- +┌───────┬────────────┐ +│ ITEM │ AVAILABILI │ +│ │ TY │ +├───────┼────────────┤ +│ ItemA │ Ready │ +│ ItemB │ Unavailabl │ +│ │ e │ +└───────┴────────────┘ +``` + +**Key Changes**: +- **Removed Color Methods**: `SetColumnColor`, `SetHeaderColor`, and `SetFooterColor` are removed; colors are now applied by embedding ANSI escape codes in cell content or via data-driven mechanisms (`tablewriter.go`). +- **Flexible Coloring Options**: + - **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.
.Filter.Global` or `PerColumn` to apply transformations like coloring dynamically (`tw/cell.go:CellFilter`). +- **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**: +- Replace `SetColumnColor` with direct ANSI code embedding for simple cases (e.g., `FgGreen+"text"+Reset`) (`zoo.go`). +- Implement `tw.Formatter` on custom types for reusable, semantic color logic (e.g., `Status` type above) (`tw/types.go`). +- Use `ConfigBuilder.
().Filter().WithPerColumn` to apply color filters to specific columns, mimicking v0.0.5’s per-column coloring (`config.go`). +- Define ANSI constants in your codebase or use a library (e.g., `github.com/fatih/color`) to simplify color management. +- Test colored output in your target terminal to ensure ANSI codes render correctly. +- Combine filters with `AutoFormat` or wrapping for consistent styling (`zoo.go:prepareContent`). + +**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 `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`). + +## Advanced Features in v1.0.x + +v1.0.x introduces several advanced features that enhance table functionality beyond v0.0.5’s capabilities. This section covers cell merging, captions, filters, stringers, and performance optimizations, providing migration guidance and examples for leveraging these features. + +### 5. Cell Merging +Cell merging combines adjacent cells with identical content, improving readability for grouped data. v1.0.x expands merging options beyond v0.0.5’s horizontal merging. + +**Old (v0.0.5):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +func main() { + table := tablewriter.NewWriter(os.Stdout) + // table.SetAutoMergeCells(true) // Enable horizontal merging + table.SetHeader([]string{"Category", "Item", "Notes"}) + table.Append([]string{"Fruit", "Apple", "Red"}) + table.Append([]string{"Fruit", "Apple", "Green"}) // "Apple" might merge if SetAutoMergeCells was on + table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + // Horizontal Merging (Similar to v0.0.5) + tableH := tablewriter.NewTable(os.Stdout, + tablewriter.WithConfig(tablewriter.Config{Row: tw.CellConfig{Formatting: tw.CellFormatting{MergeMode: tw.MergeHorizontal}}}), + tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{Symbols: tw.NewSymbols(tw.StyleASCII)})), // Specify renderer for symbols + ) + tableH.Header("Category", "Item", "Item", "Notes") // Note: Two "Item" headers for demo + tableH.Append("Fruit", "Apple", "Apple", "Red") // "Apple" cells merge + tableH.Render() + + // Vertical Merging + tableV := tablewriter.NewTable(os.Stdout, + tablewriter.WithConfig(tablewriter.Config{Row: tw.CellConfig{Formatting: tw.CellFormatting{MergeMode: tw.MergeVertical}}}), + tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{Symbols: tw.NewSymbols(tw.StyleASCII)})), + ) + tableV.Header("User", "Permission") + tableV.Append("Alice", "Read") + tableV.Append("Alice", "Write") // "Alice" cells merge vertically + tableV.Append("Bob", "Read") + tableV.Render() + + // Hierarchical Merging + tableHier := tablewriter.NewTable(os.Stdout, + tablewriter.WithConfig(tablewriter.Config{Row: tw.CellConfig{Formatting: tw.CellFormatting{MergeMode: tw.MergeHierarchical}}}), + tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{Symbols: tw.NewSymbols(tw.StyleASCII)})), + ) + tableHier.Header("Group", "SubGroup", "Item") + tableHier.Append("Tech", "CPU", "i7") + tableHier.Append("Tech", "CPU", "i9") // "Tech" and "CPU" merge + tableHier.Append("Tech", "RAM", "16GB") // "Tech" merges, "RAM" is new + tableHier.Append("Office", "CPU", "i5") // "Office" is new + tableHier.Render() +} +``` + +**Output (Horizontal):** +``` ++----------+-------+-------+-------+ +| CATEGORY | ITEM | ITEM | NOTES | ++----------+-------+-------+-------+ +| Fruit | Apple | Red | ++----------+---------------+-------+ +``` + +**Output (Vertical):** +``` ++-------+------------+ +| USER | PERMISSION | ++-------+------------+ +| Alice | Read | +| | Write | ++-------+------------+ +| Bob | Read | ++-------+------------+ +``` + +**Output (Hierarchical):** +``` ++---------+----------+------+ +| GROUP | SUBGROUP | ITEM | ++---------+----------+------+ +| Tech | CPU | i7 | +| | | i9 | +| +----------+------+ +| | RAM | 16GB | ++---------+----------+------+ +| Office | CPU | i5 | ++---------+----------+------+ +``` + +**Key Changes**: +- **Method**: `SetAutoMergeCells` is replaced by `WithRowMergeMode(int_merge_mode)` or `Config.Row.Formatting.MergeMode` (`config.go`). Uses `tw.Merge...` constants. +- **Merge Modes**: `tw.MergeMode` constants (`tw.MergeNone`, `tw.MergeHorizontal`, `tw.MergeVertical`, `tw.MergeHierarchical`) define behavior (`tw/tw.go`). +- **Section-Specific**: Merging can be applied to `Header`, `Row`, or `Footer` via `Config.
.Formatting.MergeMode` (`tw/cell.go`). +- **Processing**: Merging is handled during content preparation (`zoo.go:prepareWithMerges`, `zoo.go:applyVerticalMerges`, `zoo.go:applyHierarchicalMerges`). +- **Width Adjustment**: Horizontal merging adjusts column widths (`zoo.go:applyHorizontalMergeWidths`). +- **Renderer Support**: `tw.MergeState` in `tw.CellContext` ensures correct border drawing for merged cells (`tw/cell.go:CellContext`). +- **Streaming Limitation**: Streaming mode supports only simple horizontal merging due to fixed widths (`stream.go:streamAppendRow`). + +**Migration Tips**: +- Replace `SetAutoMergeCells(true)` with `WithRowMergeMode(tw.MergeHorizontal)` to maintain v0.0.5’s horizontal merging behavior (`config.go`). +- Use `tw.MergeVertical` for vertical grouping (e.g., repeated user names) or `tw.MergeHierarchical` for nested data structures (`tw/tw.go`). +- Apply merging to specific sections via `ConfigBuilder.
().Formatting().WithMergeMode(int_merge_mode)` (`config.go`). +- Test merging with sample data to verify visual output, especially for hierarchical merging with complex datasets. +- In streaming mode, ensure `Config.Widths` supports merged cell widths to avoid truncation (`stream.go`). +- Use `WithDebug(true)` to log merge processing for troubleshooting (`config.go`). + +**Potential Pitfalls**: +- **Streaming Restrictions**: Vertical and hierarchical merging are unsupported in streaming mode; use batch mode for these features (`stream.go`). +- **Width Misalignment**: Merged cells may require wider columns; adjust `Config.Widths` or `ColMaxWidths` (`zoo.go`). +- **Data Dependency**: Merging requires identical content; case or whitespace differences prevent merging (`zoo.go`). +- **Renderer Errors**: Incorrect `tw.MergeState` handling in custom renderers can break merged cell borders; test thoroughly (`tw/cell.go`). +- **Performance**: Hierarchical merging with large datasets may slow rendering; optimize data or limit merging (`zoo.go`). + +### 6. Table Captions +Captions add descriptive text above or below the table, a new feature in v1.0.x not present in v0.0.5. + +**Old (v0.0.5):** +```go +package main +// No direct caption support in v0.0.5. Users might have printed text manually. +// import "github.com/olekukonko/tablewriter" +// import "os" +// import "fmt" + +func main() { + // fmt.Println("Movie ratings.") // Manual caption + // table := tablewriter.NewWriter(os.Stdout) + // table.SetHeader([]string{"Name", "Sign", "Rating"}) + // table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + table := tablewriter.NewTable(os.Stdout) + table.Caption(tw.Caption{ // tw/types.go:Caption + Text: "System Status Overview - A Very Long Caption Example To Demonstrate Wrapping Behavior", + Spot: tw.SpotTopCenter, // Placement: TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter, BottomRight + Align: tw.AlignCenter, // Text alignment within caption width + Width: 30, // Fixed width for caption text wrapping; if 0, wraps to table width + }) + table.Header("Name", "Status") + table.Append("Node1", "Ready") + table.Append("SuperLongNodeNameHere", "ActiveNow") + table.Render() +} +``` + +**Output:** +``` + System Status Overview - A Very +Long Caption Example To Demonst… +┌─────────────────────┬──────────┐ +│ NAME │ STATUS │ +├─────────────────────┼──────────┤ +│ Node1 │ Ready │ +│ SuperLongNodeNameHe │ ActiveNo │ +│ re │ w │ +└─────────────────────┴──────────┘ +``` + +**Key Changes**: +- **New Feature**: `Table.Caption(tw.Caption)` introduces captions, absent in v0.0.5 (`tablewriter.go:Caption`). +- **Configuration**: `tw.Caption` (`tw/types.go:Caption`) includes: + - `Text`: Caption content. + - `Spot`: Placement (`tw.SpotTopLeft`, `tw.SpotBottomCenter`, etc.); defaults to `tw.SpotBottomCenter` if `tw.SpotNone`. + - `Align`: Text alignment (`tw.AlignLeft`, `tw.AlignCenter`, `tw.AlignRight`). + - `Width`: Optional fixed width for wrapping; defaults to table width. +- **Rendering**: Captions are rendered by `printTopBottomCaption` before or after the table based on `Spot` (`tablewriter.go:printTopBottomCaption`). +- **Streaming**: Captions are rendered during `Close()` in streaming mode if placed at the bottom (`stream.go`). + +**Migration Tips**: +- Add captions to enhance table context, especially for reports or documentation (`tw/types.go`). +- Use `tw.SpotTopCenter` for prominent placement above the table, aligning with common report formats. +- Set `Align` to match table aesthetics (e.g., `tw.AlignCenter` for balanced appearance). +- Specify `Width` for consistent wrapping, especially with long captions or narrow tables (`tablewriter.go`). +- Test caption placement and alignment with different table sizes to ensure readability. + +**Potential Pitfalls**: +- **Spot Default**: If `Spot` is `tw.SpotNone`, caption defaults to `tw.SpotBottomCenter`, which may surprise users expecting no caption (`tablewriter.go`). +- **Width Overflow**: Without `Width`, captions wrap to table width, potentially causing misalignment; set explicitly for control (`tw/types.go`). +- **Streaming Delay**: Bottom-placed captions in streaming mode appear only at `Close()`; ensure `Close()` is called (`stream.go`). +- **Alignment Confusion**: Caption `Align` is independent of table cell alignment; verify separately (`tw/cell.go`). + +### 7. Filters +Filters allow dynamic transformation of cell content during rendering, a new feature in v1.0.x for tasks like formatting, coloring, or sanitizing data. + +**Old (v0.0.5):** +```go +package main +// No direct support for cell content transformation in v0.0.5. +// Users would typically preprocess data before appending. +// import "github.com/olekukonko/tablewriter" +// import "os" +// import "strings" + +func main() { + // table := tablewriter.NewWriter(os.Stdout) + // table.SetHeader([]string{"Name", "Status"}) + // status := " Ready " + // preprocessedStatus := "Status: " + strings.TrimSpace(status) + // table.Append([]string{"Node1", preprocessedStatus}) + // table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" // For tw.CellConfig etc. + "os" + "strings" +) + +func main() { + // Per-Column Filter for Specific Transformations + cfgBuilder := tablewriter.NewConfigBuilder() + cfgBuilder.Row().Filter().WithPerColumn([]func(string) string{ + nil, // No filter for Name column + func(s string) string { // Status column: prefix and trim + return "Status: " + strings.TrimSpace(s) + }, + }) + + tableWithFilter := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build())) + tableWithFilter.Header("Name", "Status") + tableWithFilter.Append("Node1", " Ready ") // Note the extra spaces + tableWithFilter.Append("Node2", "Pending") + tableWithFilter.Render() + + // Global filter example (applied to all cells in the Row section) + cfgGlobalFilter := tablewriter.NewConfigBuilder() + cfgGlobalFilter.Row().Filter().WithGlobal(func(s string) string { + return "[" + s + "]" // Wrap all row cells in brackets + }) + tableGlobalFilter := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgGlobalFilter.Build())) + tableGlobalFilter.Header("Item", "Count") + tableGlobalFilter.Append("Apple", "5") + tableGlobalFilter.Render() +} +``` + +**Output (Per-Column Filter):** +``` +┌───────┬─────────────────┐ +│ NAME │ STATUS │ +├───────┼─────────────────┤ +│ Node1 │ Status: Ready │ +│ Node2 │ Status: Pending │ +└───────┴─────────────────┘ +``` + +**Output (Global Filter):** +``` +┌───────┬─────────┐ +│ ITEM │ COUNT │ +├───────┼─────────┤ +│[Apple]│ [5] │ +└───────┴─────────┘ +``` + +**Key Changes**: +- **New Feature**: `tw.CellFilter` (`tw/cell.go:CellFilter`) introduces: + - `Global`: A `func(s []string) []string` applied to entire rows (all cells in that row) of a section. + - `PerColumn`: A slice of `func(string) string` for column-specific transformations on individual cells. +- **Configuration**: Set via `Config.
.Filter` (`Header`, `Row`, `Footer`) using `ConfigBuilder` or direct `Config` (`config.go`). +- **Processing**: Filters are applied during content preparation, after `AutoFormat` but before rendering (`zoo.go:convertCellsToStrings` calls `prepareContent` which applies some transformations, filters are applied in `convertCellsToStrings` itself). +- **Use Cases**: Formatting (e.g., uppercase, prefixes), coloring (via ANSI codes), sanitization (e.g., removing sensitive data), or data normalization. + +**Migration Tips**: +- Use filters to replace manual content preprocessing in v0.0.5 (e.g., string manipulation before `Append`). +- Apply `Global` filters for uniform transformations across all cells of rows in a section (e.g., uppercasing all row data) (`tw/cell.go`). +- Use `PerColumn` filters for column-specific formatting (e.g., adding prefixes to status columns) (`config.go`). +- Combine filters with `tw.Formatter` for complex types or ANSI coloring for visual enhancements (`tw/cell.go`). +- Test filters with diverse inputs to ensure transformations preserve data integrity (`zoo.go`). + +**Potential Pitfalls**: +- **Filter Order**: Filters apply before some other transformations like padding and alignment; combining can lead to interactions. +- **Performance**: Complex filters on large datasets may slow rendering; optimize logic (`zoo.go`). +- **Nil Filters**: Unset filters (`nil`) are ignored, but incorrect indexing in `PerColumn` can skip columns (`tw/cell.go`). +- **Streaming Consistency**: Filters must be consistent in streaming mode, as widths are fixed at `Start()` (`stream.go`). + +### 8. Stringers and Caching +Stringers allow custom string conversion for data types, with v1.0.x adding caching for performance. + +**Old (v0.0.5):** +```go +package main +// v0.0.5 primarily relied on fmt.Stringer for custom types. +// import "fmt" +// import "github.com/olekukonko/tablewriter" +// import "os" + +// type MyCustomType struct { +// Value string +// } +// func (m MyCustomType) String() string { return "Formatted: " + m.Value } + +func main() { + // table := tablewriter.NewWriter(os.Stdout) + // table.SetHeader([]string{"Custom Data"}) + // table.Append([]string{MyCustomType{"test"}.String()}) // Manual call to String() + // table.Render() +} +``` + +**New (v1.0.x):** +```go +package main + +import ( + "fmt" + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" // For tw.Formatter + "os" + "strings" // For Person example +) + +// Example 1: Using WithStringer for general conversion +type CustomInt int + +func main() { + // Table with a general stringer (func(any) []string) + tableWithStringer := tablewriter.NewTable(os.Stdout, + tablewriter.WithStringer(func(v any) []string { // Must return []string + if ci, ok := v.(CustomInt); ok { + return []string{fmt.Sprintf("CustomInt Value: %d!", ci)} + } + return []string{fmt.Sprintf("Value: %v", v)} // Fallback + }), + tablewriter.WithDebug(true), // Enable caching if WithStringerCache() is also used + // tablewriter.WithStringerCache(), // Optional: enable caching + ) + tableWithStringer.Header("Data") + tableWithStringer.Append(123) + tableWithStringer.Append(CustomInt(456)) + tableWithStringer.Render() + + fmt.Println("\n--- Table with Type-Specific Stringer for Structs ---") + + // Example 2: Stringer for a specific struct type + type Person struct { + ID int + Name string + City string + } + + // Stringer for Person to produce 3 cells + personToStrings := func(p Person) []string { + return []string{ + fmt.Sprintf("ID: %d", p.ID), + p.Name, + strings.ToUpper(p.City), + } + } + + tablePersonStringer := tablewriter.NewTable(os.Stdout, + tablewriter.WithStringer(personToStrings), // Pass the type-specific function + ) + tablePersonStringer.Header("User ID", "Full Name", "Location") + tablePersonStringer.Append(Person{1, "Alice", "New York"}) + tablePersonStringer.Append(Person{2, "Bob", "London"}) + tablePersonStringer.Render() + + fmt.Println("\n--- Table with tw.Formatter ---") + // Example 3: Using tw.Formatter for types + type Product struct { + Name string + Price float64 + } + func (p Product) Format() string { // Implements tw.Formatter + return fmt.Sprintf("%s - $%.2f", p.Name, p.Price) + } + tableFormatter := tablewriter.NewTable(os.Stdout) + tableFormatter.Header("Product Details") + tableFormatter.Append(Product{"Laptop", 1200.99}) // Will use Format() + tableFormatter.Render() +} +``` + +**Output (Stringer Examples):** +``` +┌─────────────────────┐ +│ DATA │ +├─────────────────────┤ +│ Value: 123 │ +│ CustomInt Value: 456! │ +└─────────────────────┘ + +--- Table with Type-Specific Stringer for Structs --- +┌─────────┬───────────┬──────────┐ +│ USER ID │ FULL NAME │ LOCATION │ +├─────────┼───────────┼──────────┤ +│ ID: 1 │ Alice │ NEW YORK │ +│ ID: 2 │ Bob │ LONDON │ +└─────────┴───────────┴──────────┘ + +--- Table with tw.Formatter --- +┌─────────────────┐ +│ PRODUCT DETAILS │ +├─────────────────┤ +│ Laptop - $1200… │ +└─────────────────┘ +``` + +**Key Changes**: +- **Stringer Support**: `WithStringer(fn any)` sets a table-wide string conversion function. This function must have a signature like `func(SomeType) []string` or `func(any) []string`. It's used to convert an input item (e.g., a struct) into a slice of strings, where each string is a cell for the row (`tablewriter.go:WithStringer`). +- **Caching**: `WithStringerCache()` enables caching for the function provided via `WithStringer`, improving performance for repeated conversions of the same input type (`tablewriter.go:WithStringerCache`). +- **Formatter**: `tw.Formatter` interface (`Format() string`) allows types to define their own single-string representation for a cell. This is checked before `fmt.Stringer` (`tw/types.go:Formatter`). +- **Priority**: When converting an item to cell(s): + 1. `WithStringer` (if provided and compatible with the item's type). + 2. If not handled by `WithStringer`, or if `WithStringer` is not set: + * If the item is a struct that does *not* implement `tw.Formatter` or `fmt.Stringer`, its exported fields are reflected into multiple cells. + * If the item (or struct) implements `tw.Formatter` (`Format() string`), that's used for a single cell. + * Else, if it implements `fmt.Stringer` (`String() string`), that's used for a single cell. + * Else, default `fmt.Sprintf("%v", ...)` for a single cell. + (`zoo.go:convertCellsToStrings`, `zoo.go:convertItemToCells`) + +**Migration Tips**: +- For types that should produce a single cell with custom formatting, implement `tw.Formatter`. +- For types (especially structs) that should be expanded into multiple cells with custom logic, use `WithStringer` with a function like `func(MyType) []string`. +- If you have a general way to convert *any* type into a set of cells, use `WithStringer(func(any) []string)`. +- Enable `WithStringerCache` for large datasets with repetitive data types if using `WithStringer`. +- Test stringer/formatter output to ensure formatting meets expectations (`zoo.go`). + +**Potential Pitfalls**: +- **Cache Overhead**: `WithStringerCache` may increase memory usage for diverse data; disable for small tables or if not using `WithStringer` (`tablewriter.go`). +- **Stringer Signature**: The function passed to `WithStringer` *must* return `[]string`. A function returning `string` will lead to a warning and fallback behavior. +- **Formatter vs. Stringer Priority**: Be aware of the conversion priority if your types implement multiple interfaces or if you also use `WithStringer`. +- **Streaming**: Stringers/Formatters must produce consistent cell counts in streaming mode to maintain width alignment (`stream.go`). + +## Examples + +This section provides practical examples to demonstrate v1.0.x features, covering common and advanced use cases to aid migration. Each example includes code, output, and notes to illustrate functionality. + +### Example: Minimal Setup +A basic table with default settings, ideal for quick setups. + +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "os" +) + +func main() { + table := tablewriter.NewTable(os.Stdout) + table.Header("Name", "Status") + table.Append("Node1", "Ready") + table.Render() +} +``` + +**Output**: +``` +┌───────┬────────┐ +│ NAME │ STATUS │ +├───────┼────────┤ +│ Node1 │ Ready │ +└───────┴────────┘ +``` + +**Notes**: +- Uses default `renderer.NewBlueprint()` and `defaultConfig()` settings (`tablewriter.go`, `config.go`). +- `Header: tw.AlignCenter`, `Row: tw.AlignLeft` (`config.go:defaultConfig`). +- Simple replacement for v0.0.5’s `NewWriter` and `SetHeader`/`Append`. + +### Example: Streaming with Fixed Widths +Demonstrates streaming mode for real-time data output. + +```go +package main + +import ( + "fmt" + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + "log" + "os" +) + +func main() { + table := tablewriter.NewTable(os.Stdout, + tablewriter.WithStreaming(tw.StreamConfig{Enable: true}), + tablewriter.WithColumnMax(10), // Sets Config.Widths.Global + ) + if err := table.Start(); err != nil { + log.Fatalf("Start failed: %v", err) + } + table.Header("Name", "Status") + for i := 0; i < 2; i++ { + err := table.Append(fmt.Sprintf("Node%d", i+1), "Ready") + if err != nil { + log.Printf("Append failed: %v", err) + } + } + if err := table.Close(); err != nil { + log.Fatalf("Close failed: %v", err) + } +} +``` + +**Output**: +``` +┌──────────┬──────────┐ +│ NAME │ STATUS │ +├──────────┼──────────┤ +│ Node1 │ Ready │ +│ Node2 │ Ready │ +└──────────┴──────────┘ +``` + +**Notes**: +- Streaming requires `Start()` and `Close()`; `Config.Widths` (here set via `WithColumnMax`) fixes widths (`stream.go`). +- Replaces v0.0.5’s batch rendering for real-time use cases. +- Ensure error handling for `Start()`, `Append()`, and `Close()`. + +### Example: Markdown-Style Table +Creates a Markdown-compatible table for documentation. + +```go +package main + +import ( + "fmt" + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" // Import renderer + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + // Example 1: Using Blueprint renderer with Markdown symbols + tableBlueprintMarkdown := tablewriter.NewTable(os.Stdout, + tablewriter.WithRenderer(renderer.NewBlueprint()), // Use Blueprint + tablewriter.WithRendition(tw.Rendition{ + Symbols: tw.NewSymbols(tw.StyleMarkdown), + Borders: tw.Border{Left: tw.On, Right: tw.On}, // Markdown needs left/right borders + }), + tablewriter.WithRowAlignment(tw.AlignLeft), // Common for Markdown + tablewriter.WithHeaderAlignment(tw.AlignCenter), // Center align headers + ) + tableBlueprintMarkdown.Header("Name", "Status") + tableBlueprintMarkdown.Append("Node1", "Ready") + tableBlueprintMarkdown.Append("Node2", "NotReady") + tableBlueprintMarkdown.Render() + + fmt.Println("\n--- Using dedicated Markdown Renderer (if one exists or is built) ---") + // Example 2: Assuming a dedicated Markdown renderer (hypothetical example) + // If a `renderer.NewMarkdown()` existed that directly outputs GitHub Flavored Markdown table syntax: + /* + tableDedicatedMarkdown := tablewriter.NewTable(os.Stdout, + tablewriter.WithRenderer(renderer.NewMarkdown()), // Hypothetical Markdown renderer + ) + tableDedicatedMarkdown.Header("Name", "Status") + tableDedicatedMarkdown.Append("Node1", "Ready") + tableDedicatedMarkdown.Append("Node2", "NotReady") + tableDedicatedMarkdown.Render() + */ + // Since `renderer.NewMarkdown()` isn't shown in the provided code, + // the first example (Blueprint with StyleMarkdown) is the current viable way. +} +``` + +**Output (Blueprint with StyleMarkdown):** +``` +| NAME | STATUS | +|--------|----------| +| Node1 | Ready | +| Node2 | NotReady | +``` + +**Notes**: +- `StyleMarkdown` ensures compatibility with Markdown parsers (`tw/symbols.go`). +- Left alignment for rows and center for headers is common for Markdown readability (`config.go`). +- Ideal for GitHub READMEs or documentation. +- A dedicated Markdown renderer (like the commented-out example) would typically handle alignment syntax (e.g., `|:---:|:---|`). With `Blueprint` and `StyleMarkdown`, alignment is visual within the text rather than Markdown syntax. + +### Example: ASCII-Style Table +Uses `StyleASCII` for maximum terminal compatibility. + +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + table := tablewriter.NewTable(os.Stdout, + tablewriter.WithRenderer(renderer.NewBlueprint()), + tablewriter.WithRendition(tw.Rendition{ + Symbols: tw.NewSymbols(tw.StyleASCII), + }), + tablewriter.WithRowAlignment(tw.AlignLeft), + ) + table.Header("ID", "Value") + table.Append("1", "Test") + table.Render() +} +``` + +**Output**: +``` ++----+-------+ +│ ID │ VALUE │ ++----+-------+ +│ 1 │ Test │ ++----+-------+ +``` + +**Notes**: +- `StyleASCII` is robust for all terminals (`tw/symbols.go`). +- Replaces v0.0.5’s default style with explicit configuration. + + +### Example: Kubectl-Style Output +Creates a borderless, minimal table similar to `kubectl` command output, emphasizing simplicity and readability. + +```go +package main + +import ( + "os" + "sync" + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" +) + +var wg sync.WaitGroup + +func main() { + data := [][]any{ + {"node1.example.com", "Ready", "compute", "1.11"}, + {"node2.example.com", "Ready", "compute", "1.11"}, + {"node3.example.com", "Ready", "compute", "1.11"}, + {"node4.example.com", "NotReady", "compute", "1.11"}, + } + + table := tablewriter.NewTable(os.Stdout, + tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{ + Borders: tw.BorderNone, + Settings: tw.Settings{ + Separators: tw.SeparatorsNone, + Lines: tw.LinesNone, + }, + })), + tablewriter.WithConfig(tablewriter.Config{ + Header: tw.CellConfig{ + Formatting: tw.CellFormatting{Alignment: tw.AlignLeft}, + }, + Row: tw.CellConfig{ + Formatting: tw.CellFormatting{Alignment: tw.AlignLeft}, + Padding: tw.CellPadding{Global: tw.PaddingNone}, + }, + }), + ) + table.Header("Name", "Status", "Role", "Version") + table.Bulk(data) + table.Render() +} +``` + +**Output:** +``` +NAME STATUS ROLE VERSION +node1.example.com Ready compute 1.21.3 +node2.example.com Ready infra 1.21.3 +node3.example.com NotReady compute 1.20.7 +``` + +**Notes**: +- **Configuration**: Uses `tw.BorderNone`, `tw.LinesNone`, `tw.SeparatorsNone`, `tw.NewSymbols(tw.StyleNone)` and specific padding (`Padding{Right:" "}`) for a minimal, borderless layout. +- **Migration from v0.0.5**: Replaces `SetBorder(false)` and manual spacing with `tw.Rendition` and `Config` settings, achieving a cleaner kubectl-like output. +- **Key Features**: + - Left-aligned text for readability (`config.go`). + - `WithTrimSpace(tw.Off)` preserves spacing (`config.go`). + - `Bulk` efficiently adds multiple rows (`tablewriter.go`). + - Padding `Right: " "` is used to create space between columns as separators are off. +- **Best Practices**: Test in terminals to ensure spacing aligns with command-line aesthetics; use `WithDebug(true)` for layout issues (`config.go`). +- **Potential Issues**: Column widths are content-based. For very long content in one column and short in others, it might not look perfectly aligned like fixed-width CLI tools. `Config.Widths` could be used for more control if needed. + +### Example: Hierarchical Merging +Demonstrates hierarchical cell merging for nested data structures, a new feature in v1.0.x. + +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + data := [][]string{ + // Header row is separate + {"table\nwriter", "v0.0.1", "legacy"}, + {"table\nwriter", "v0.0.2", "legacy"}, + {"table\nwriter", "v0.0.2", "legacy"}, // Duplicate for testing merge + {"table\nwriter", "v0.0.2", "legacy"}, // Duplicate for testing merge + {"table\nwriter", "v0.0.5", "legacy"}, + {"table\nwriter", "v1.0.6", "latest"}, + } + + rendition := tw.Rendition{ + Symbols: tw.NewSymbols(tw.StyleLight), // Use light for clearer merge lines + Settings: tw.Settings{ + Separators: tw.Separators{BetweenRows: tw.On}, + Lines: tw.Lines{ShowHeaderLine: tw.On, ShowFooterLine: tw.On}, // Show header line + }, + Borders: tw.Border{Left:tw.On, Right:tw.On, Top:tw.On, Bottom:tw.On}, + } + + tableHier := tablewriter.NewTable(os.Stdout, + tablewriter.WithRenderer(renderer.NewBlueprint()), + tablewriter.WithRendition(rendition), + tablewriter.WithConfig(tablewriter.Config{ + Row: tw.CellConfig{ + Formatting: tw.CellFormatting{ + MergeMode: tw.MergeHierarchical, + // Alignment: tw.AlignCenter, // Default is Left, often better for hierarchical + AutoWrap: tw.WrapNormal, // Allow wrapping for "table\nwriter" + }, + }, + }), + ) + + tableHier.Header("Package", "Version", "Status") // Header + tableHier.Bulk(data) // Bulk data + tableHier.Render() + + // --- Vertical Merging Example for Contrast --- + tableVert := tablewriter.NewTable(os.Stdout, + tablewriter.WithRenderer(renderer.NewBlueprint()), + tablewriter.WithRendition(rendition), // Reuse same rendition + tablewriter.WithConfig(tablewriter.Config{ + Row: tw.CellConfig{ + Formatting: tw.CellFormatting{ + MergeMode: tw.MergeVertical, + AutoWrap: tw.WrapNormal, + }, + }, + }), + ) + tableVert.Header("Package", "Version", "Status") + tableVert.Bulk(data) + tableVert.Render() +} +``` + +**Output (Hierarchical):** +``` +┌─────────┬─────────┬────────┐ +│ PACKAGE │ VERSION │ STATUS │ +├─────────┼─────────┼────────┤ +│ table │ v0.0.1 │ legacy │ +│ writer │ │ │ +│ ├─────────┼────────┤ +│ │ v0.0.2 │ legacy │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ ├─────────┼────────┤ +│ │ v0.0.5 │ legacy │ +│ ├─────────┼────────┤ +│ │ v1.0.6 │ latest │ +└─────────┴─────────┴────────┘ +``` +**Output (Vertical):** +``` +┌─────────┬─────────┬────────┐ +│ PACKAGE │ VERSION │ STATUS │ +├─────────┼─────────┼────────┤ +│ table │ v0.0.1 │ legacy │ +│ writer │ │ │ +│ ├─────────┤ │ +│ │ v0.0.2 │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ │ │ │ +│ ├─────────┤ │ +│ │ v0.0.5 │ │ +│ ├─────────┼────────┤ +│ │ v1.0.6 │ latest │ +└─────────┴─────────┴────────┘ +``` + +**Notes**: +- **Configuration**: Uses `tw.MergeHierarchical` to merge cells based on matching values in preceding columns (`tw/tw.go`). +- **Migration from v0.0.5**: Extends `SetAutoMergeCells(true)` (horizontal only) with hierarchical merging for complex data (`tablewriter.go`). +- **Key Features**: + - `StyleLight` for clear visuals (`tw/symbols.go`). + - `Bulk` for efficient data loading (`tablewriter.go`). +- **Best Practices**: Test merging with nested data; use batch mode, as streaming doesn’t support hierarchical merging (`stream.go`). +- **Potential Issues**: Ensure data is sorted appropriately for hierarchical merging to work as expected; mismatches prevent merging (`zoo.go`). `AutoWrap: tw.WrapNormal` helps with multi-line cell content like "table\nwriter". + +### Example: Colorized Table +Applies ANSI colors to highlight status values, replacing v0.0.5’s `SetColumnColor`. + +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" // For tw.State, tw.CellConfig etc. + "os" +) + +func main() { + const ( + FgGreen = "\033[32m" + FgRed = "\033[31m" + Reset = "\033[0m" + ) + + cfgBuilder := tablewriter.NewConfigBuilder() + cfgBuilder.Row().Filter().WithPerColumn([]func(string) string{ + nil, // No filter for Name + func(s string) string { // Color Status + if s == "Ready" { + return FgGreen + s + Reset + } + return FgRed + s + Reset + }, + }) + + table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build())) + table.Header("Name", "Status") + table.Append("Node1", "Ready") + table.Append("Node2", "Error") + table.Render() +} +``` + +**Output (Text Approximation, Colors Not Shown):** +``` +┌───────┬────────┐ +│ NAME │ STATUS │ +├───────┼────────┤ +│ Node1 │ Ready │ +│ Node2 │ Error │ +└───────┴────────┘ +``` + +**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; `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`). + +### Example: Vertical Merging +Shows vertical cell merging for repeated values in a column. (This example was very similar to the hierarchical one, so I'll ensure it's distinct by using simpler data). + +```go +package main + +import ( + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" + "os" +) + +func main() { + table := tablewriter.NewTable(os.Stdout, + tablewriter.WithRenderer(renderer.NewBlueprint()), // Default renderer + tablewriter.WithRendition(tw.Rendition{ + Symbols: tw.NewSymbols(tw.StyleLight), + Settings: tw.Settings{Separators: tw.Separators{BetweenRows: tw.On}}, + Borders: tw.Border{Left:tw.On, Right:tw.On, Top:tw.On, Bottom:tw.On}, + }), + tablewriter.WithConfig( + tablewriter.NewConfigBuilder(). + Row().Formatting().WithMergeMode(tw.MergeVertical).Build(). // Enable Vertical Merge + Build(), + ), + ) + table.Header("User", "Permission", "Target") + table.Append("Alice", "Read", "FileA") + table.Append("Alice", "Write", "FileA") // Alice and FileA will merge vertically + table.Append("Alice", "Read", "FileB") + table.Append("Bob", "Read", "FileA") + table.Append("Bob", "Read", "FileC") // Bob and Read will merge + table.Render() +} +``` + +**Output:** +``` +┌───────┬────────────┬────────┐ +│ USER │ PERMISSION │ TARGET │ +├───────┼────────────┼────────┤ +│ Alice │ Read │ FileA │ +│ │ │ │ +│ ├────────────┤ │ +│ │ Write │ │ +│ ├────────────┼────────┤ +│ │ Read │ FileB │ +├───────┼────────────┼────────┤ +│ Bob │ Read │ FileA │ +│ │ ├────────┤ +│ │ │ FileC │ +└───────┴────────────┴────────┘ +``` + +**Notes**: +- **Configuration**: `tw.MergeVertical` merges identical cells vertically (`tw/tw.go`). +- **Migration from v0.0.5**: Extends `SetAutoMergeCells` with vertical merging (`tablewriter.go`). +- **Key Features**: Enhances grouped data display; requires batch mode (`stream.go`). +- **Best Practices**: Sort data by the columns you intend to merge for best results; test with `StyleLight` for clarity (`tw/symbols.go`). +- **Potential Issues**: Streaming doesn’t support vertical merging; use batch mode (`stream.go`). + +### Example: Custom Renderer (CSV Output) +Implements a custom renderer for CSV output. + +```go +package main + +import ( + "fmt" + "github.com/olekukonko/ll" // For logger type + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + "io" + "os" + "strings" // For CSV escaping +) + +// CSVRenderer implements tw.Renderer +type CSVRenderer struct { + writer io.Writer + config tw.Rendition // Store the rendition + logger *ll.Logger + err error +} + +func (r *CSVRenderer) Start(w io.Writer) error { + r.writer = w + return nil // No initial output for CSV typically +} + +func (r *CSVRenderer) escapeCSVCell(data string) string { + // Basic CSV escaping: double quotes if it contains comma, newline, or quote + if strings.ContainsAny(data, ",\"\n") { + return `"` + strings.ReplaceAll(data, `"`, `""`) + `"` + } + return data +} + +func (r *CSVRenderer) writeLine(cells map[int]tw.CellContext, numCols int) { + if r.err != nil { return } + var lineParts []string + // Need to iterate in column order for CSV + keys := make([]int, 0, len(cells)) + for k := range cells { + keys = append(keys, k) + } + // This simple sort works for int keys 0,1,2... + // For more complex scenarios, a proper sort might be needed if keys aren't sequential. + for i := 0; i < numCols; i++ { // Assume numCols reflects the intended max columns + if cellCtx, ok := cells[i]; ok { + lineParts = append(lineParts, r.escapeCSVCell(cellCtx.Data)) + } else { + lineParts = append(lineParts, "") // Empty cell if not present + } + } + _, r.err = r.writer.Write([]byte(strings.Join(lineParts, ",") + "\n")) +} + + +func (r *CSVRenderer) Header(headers [][]string, ctx tw.Formatting) { + // For CSV, usually only the first line of headers is relevant + // The ctx.Row.Current will contain the cells for the first line of the header being processed + r.writeLine(ctx.Row.Current, len(ctx.Row.Current)) +} + +func (r *CSVRenderer) Row(row []string, ctx tw.Formatting) { + // ctx.Row.Current contains the cells for the current row line + r.writeLine(ctx.Row.Current, len(ctx.Row.Current)) +} + +func (r *CSVRenderer) Footer(footers [][]string, ctx tw.Formatting) { + // Similar to Header/Row, using ctx.Row.Current for the footer line data + r.writeLine(ctx.Row.Current, len(ctx.Row.Current)) +} + +func (r *CSVRenderer) Line(ctx tw.Formatting) { /* No separator lines in CSV */ } + +func (r *CSVRenderer) Close() error { return r.err } + +func (r *CSVRenderer) Config() tw.Rendition { return r.config } +func (r *CSVRenderer) Logger(logger *ll.Logger) { r.logger = logger } + +func main() { + table := tablewriter.NewTable(os.Stdout, tablewriter.WithRenderer(&CSVRenderer{ + // config can be minimal for CSV as symbols/borders aren't used + config: tw.Rendition{}, + })) + table.Header("Name", "Status", "Notes, with comma") + table.Append("Node1", "Ready", "All systems \"go\"!") + table.Append("Node2", "Error", "Needs\nattention") + table.Footer("Summary", "2 Nodes", "Check logs") + table.Render() +} +``` + +**Output:** +```csv +Name,Status,"Notes, with comma" +Node1,Ready,"All systems ""go""!" +Node2,Error,"Needs +attention" +Summary,2 Nodes,Check logs +``` + +**Notes**: +- **Configuration**: Custom `CSVRenderer` implements `tw.Renderer` for CSV output (`tw/renderer.go`). +- **Migration from v0.0.5**: Extends v0.0.5’s text-only output with custom formats (`tablewriter.go`). +- **Key Features**: Handles basic CSV cell escaping; supports streaming if `Append` is called multiple times. `ctx.Row.Current` (map[int]tw.CellContext) is used to access cell data. +- **Best Practices**: Test with complex data (e.g., commas, quotes, newlines); implement all renderer methods. A more robust CSV renderer would use the `encoding/csv` package. +- **Potential Issues**: Custom renderers require careful error handling and correct interpretation of `tw.Formatting` and `tw.RowContext`. This example's `writeLine` assumes columns are 0-indexed and contiguous for simplicity. + +## Troubleshooting and Common Pitfalls + +This section addresses common migration issues with detailed solutions, covering 30+ scenarios to reduce support tickets. + +| Issue | Cause/Solution | +|-------------------------------------------|-------------------------------------------------------------------------------| +| No output from `Render()` | **Cause**: Missing `Start()`/`Close()` in streaming mode or invalid `io.Writer`. **Solution**: Ensure `Start()` and `Close()` are called in streaming; verify `io.Writer` (`stream.go`). | +| Incorrect column widths | **Cause**: Missing `Config.Widths` in streaming or content-based sizing. **Solution**: Set `Config.Widths` before `Start()`; use `WithColumnMax` (`stream.go`). | +| 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 `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`). | +| Deprecated methods used | **Cause**: Using `WithBorders`, old `tablewriter.Behavior` constants. **Solution**: Migrate to `WithRendition`, `tw.Behavior` struct (`tablewriter.go`, `deprecated.go`). | +| Streaming footer missing | **Cause**: `Close()` not called. **Solution**: Always call `Close()` (`stream.go`). | +| Hierarchical merging fails | **Cause**: Unsorted data or streaming mode. **Solution**: Sort data; use batch mode (`zoo.go`). | +| Custom renderer errors | **Cause**: Incomplete method implementation or misinterpreting `tw.Formatting`. **Solution**: Implement all `tw.Renderer` methods; test thoroughly (`tw/renderer.go`). | +| Width overflow | **Cause**: No `MaxWidth` or wide content. **Solution**: Set `Config.MaxWidth` (`config.go`). | +| Truncated content | **Cause**: Narrow `Config.Widths` or `tw.WrapTruncate`. **Solution**: Widen columns or use `tw.WrapNormal` (`zoo.go`). | +| Debug logs absent | **Cause**: `Debug = false`. **Solution**: Enable `WithDebug(true)` (`config.go`). | +| Alignment mismatch across sections | **Cause**: Different defaults. **Solution**: Set uniform alignment options (e.g., via `ConfigBuilder.
.Alignment()`) (`config.go`). | +| ANSI code artifacts | **Cause**: Non-ANSI terminal. **Solution**: Provide non-colored fallback (`tw/fn.go`). | +| Slow rendering | **Cause**: Complex filters or merging. **Solution**: Optimize logic; limit merging (`zoo.go`). | +| Uneven cell counts | **Cause**: Mismatched rows/headers. **Solution**: Pad with `""` (`zoo.go`). | +| Border inconsistencies | **Cause**: Mismatched `Borders`/`Symbols`. **Solution**: Align settings (`tw/renderer.go`). | +| Streaming width issues | **Cause**: No `Config.Widths`. **Solution**: Set before `Start()` (`stream.go`). | +| Formatter ignored | **Cause**: `WithStringer` might take precedence if compatible. **Solution**: Review conversion priority; `tw.Formatter` is high-priority for single-item-to-single-cell conversion (`zoo.go`). | +| Caption misalignment | **Cause**: Incorrect `Width` or `Align`. **Solution**: Set `tw.Caption.Width`/`Align` (`tablewriter.go`). | +| Per-column padding errors | **Cause**: Incorrect indexing in `Padding.PerColumn`. **Solution**: Verify indices (`tw/cell.go`). | +| Vertical merging in streaming | **Cause**: Unsupported. **Solution**: Use batch mode (`stream.go`). | +| Filter performance | **Cause**: Complex logic. **Solution**: Simplify filters (`zoo.go`). | +| Custom symbols incomplete | **Cause**: Missing characters. **Solution**: Define all symbols (`tw/symbols.go`). | +| Table too wide | **Cause**: No `MaxWidth`. **Solution**: Set `Config.MaxWidth` (`config.go`). | +| Streaming errors | **Cause**: Missing `Start()`. **Solution**: Call `Start()` before data input (`stream.go`). | + +## Additional Notes + +- **Performance Optimization**: Enable `WithStringerCache` for repetitive data types when using `WithStringer`; optimize filters and merging for large datasets (`tablewriter.go`, `zoo.go`). +- **Debugging**: Use `WithDebug(true)` and `table.Debug()` to log configuration and rendering details; invaluable for troubleshooting (`config.go`). +- **Testing Resources**: The `tests/` directory contains examples of various configurations. +- **Community Support**: For advanced use cases or issues, consult the source code or open an issue on the `tablewriter` repository. +- **Future Considerations**: Deprecated methods in `deprecated.go` (e.g., `WithBorders`) are slated for removal in future releases; migrate promptly to ensure compatibility. + +This guide aims to cover all migration scenarios comprehensively. For highly specific or advanced use cases, refer to the source files (`config.go`, `tablewriter.go`, `stream.go`, `tw/*`) or engage with the `tablewriter` community for support. \ No newline at end of file diff --git a/vendor/github.com/olekukonko/tablewriter/README.md b/vendor/github.com/olekukonko/tablewriter/README.md index 8aecd0f5..70480d69 100644 --- a/vendor/github.com/olekukonko/tablewriter/README.md +++ b/vendor/github.com/olekukonko/tablewriter/README.md @@ -28,14 +28,14 @@ go get github.com/olekukonko/tablewriter@v0.0.5 #### Latest Version The latest stable version ```bash -go get github.com/olekukonko/tablewriter@v1.0.6 +go get github.com/olekukonko/tablewriter@v1.0.7 ``` **Warning:** Version `v1.0.0` contains missing functionality and should not be used. > **Version Guidance** -> - Production: Use `v0.0.5` (stable) +> - Legacy: Use `v0.0.5` (stable) > - New Features: Use `@latest` (includes generics, super fast streaming APIs) > - Legacy Docs: See [README_LEGACY.md](README_LEGACY.md) @@ -62,7 +62,7 @@ func main() { data := [][]string{ {"Package", "Version", "Status"}, {"tablewriter", "v0.0.5", "legacy"}, - {"tablewriter", "v1.0.6", "latest"}, + {"tablewriter", "v1.0.7", "latest"}, } table := tablewriter.NewWriter(os.Stdout) @@ -77,7 +77,7 @@ func main() { │ PACKAGE │ VERSION │ STATUS │ ├─────────────┼─────────┼────────┤ │ tablewriter │ v0.0.5 │ legacy │ -│ tablewriter │ v1.0.6 │ latest │ +│ tablewriter │ v1.0.7 │ 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 @@ -297,7 +334,7 @@ func main() { } table.Configure(func(config *tablewriter.Config) { - config.Row.Formatting.Alignment = tw.AlignLeft + config.Row.Alignment.Global = tw.AlignLeft }) table.Render() } @@ -368,14 +405,12 @@ func main() { tablewriter.WithRenderer(renderer.NewColorized(colorCfg)), tablewriter.WithConfig(tablewriter.Config{ Row: tw.CellConfig{ - Formatting: tw.CellFormatting{ - AutoWrap: tw.WrapNormal, // Wrap long content - Alignment: tw.AlignLeft, // Left-align rows - }, + Formatting: tw.CellFormatting{AutoWrap: tw.WrapNormal}, // Wrap long content + Alignment: tw.CellAlignment{Global: tw.AlignLeft}, // Left-align rows ColMaxWidths: tw.CellWidth{Global: 25}, }, Footer: tw.CellConfig{ - Formatting: tw.CellFormatting{Alignment: tw.AlignRight}, + Alignment: tw.CellAlignment{Global: tw.AlignRight}, }, }), ) @@ -480,19 +515,13 @@ func main() { table := tablewriter.NewTable(os.Stdout, tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{ - Settings: tw.Settings{ - Separators: tw.Separators{BetweenRows: tw.On}, - }, + Settings: tw.Settings{Separators: tw.Separators{BetweenRows: tw.On}}, })), tablewriter.WithConfig(tablewriter.Config{ - Header: tw.CellConfig{ - Formatting: tw.CellFormatting{Alignment: tw.AlignCenter}, - }, + Header: tw.CellConfig{Alignment: tw.CellAlignment{Global: tw.AlignCenter}}, Row: tw.CellConfig{ - Formatting: tw.CellFormatting{ - MergeMode: tw.MergeHierarchical, - Alignment: tw.AlignLeft, - }, + Formatting: tw.CellFormatting{MergeMode: tw.MergeHierarchical}, + Alignment: tw.CellAlignment{Global: tw.AlignLeft}, }, }), ) @@ -546,21 +575,20 @@ func main() { table := tablewriter.NewTable(os.Stdout, tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{ - Settings: tw.Settings{ - Separators: tw.Separators{BetweenRows: tw.On}, - }, + Settings: tw.Settings{Separators: tw.Separators{BetweenRows: tw.On}}, })), tablewriter.WithConfig(tablewriter.Config{ Row: tw.CellConfig{ - Formatting: tw.CellFormatting{MergeMode: tw.MergeBoth}, - ColumnAligns: []tw.Align{tw.Skip, tw.Skip, tw.AlignRight, tw.AlignLeft}, + Formatting: tw.CellFormatting{MergeMode: tw.MergeBoth}, + Alignment: tw.CellAlignment{PerColumn: []tw.Align{tw.Skip, tw.Skip, tw.AlignRight, tw.AlignLeft}}, }, + Footer: tw.CellConfig{ Padding: tw.CellPadding{ Global: tw.Padding{Left: "*", Right: "*"}, PerColumn: []tw.Padding{{}, {}, {Bottom: "^"}, {Bottom: "^"}}, }, - ColumnAligns: []tw.Align{tw.Skip, tw.Skip, tw.AlignRight, tw.AlignLeft}, + Alignment: tw.CellAlignment{PerColumn: []tw.Align{tw.Skip, tw.Skip, tw.AlignRight, tw.AlignLeft}}, }, }), ) @@ -617,9 +645,7 @@ func main() { })), tablewriter.WithConfig(tablewriter.Config{ MaxWidth: 10, - Row: tw.CellConfig{ - Formatting: tw.CellFormatting{Alignment: tw.AlignCenter}, - }, + Row: tw.CellConfig{Alignment: tw.CellAlignment{Global: tw.AlignCenter}}, }), ) table.Append([]string{s, s}) @@ -631,16 +657,12 @@ func main() { // Main table table := tablewriter.NewTable(os.Stdout, tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{ - Borders: tw.BorderNone, - Settings: tw.Settings{ - Separators: tw.Separators{BetweenColumns: tw.On}, - }, + Borders: tw.BorderNone, + Settings: tw.Settings{Separators: tw.Separators{BetweenColumns: tw.On}}, })), tablewriter.WithConfig(tablewriter.Config{ MaxWidth: 30, - Row: tw.CellConfig{ - Formatting: tw.CellFormatting{Alignment: tw.AlignCenter}, - }, + Row: tw.CellConfig{Alignment: tw.CellAlignment{Global: tw.AlignCenter}}, }), ) table.Append([]string{createSubTable("A"), createSubTable("B")}) @@ -711,14 +733,11 @@ func main() { tablewriter.WithStringer(employeeStringer), tablewriter.WithConfig(tablewriter.Config{ Header: tw.CellConfig{ - Formatting: tw.CellFormatting{Alignment: tw.AlignCenter, AutoFormat: tw.On}, - }, - Row: tw.CellConfig{ - Formatting: tw.CellFormatting{Alignment: tw.AlignLeft}, - }, - Footer: tw.CellConfig{ - Formatting: tw.CellFormatting{Alignment: tw.AlignRight}, + Formatting: tw.CellFormatting{AutoFormat: tw.On}, + Alignment: tw.CellAlignment{Global: tw.AlignCenter}, }, + Row: tw.CellConfig{Alignment: tw.CellAlignment{Global: tw.AlignLeft}}, + Footer: tw.CellConfig{Alignment: tw.CellAlignment{Global: tw.AlignRight}}, }), ) table.Header([]string{"ID", "Name", "Age", "Department", "Salary"}) @@ -784,23 +803,17 @@ func main() { } table := tablewriter.NewTable(os.Stdout, - tablewriter.WithRenderer(renderer.NewHTML(os.Stdout, false, htmlCfg)), + tablewriter.WithRenderer(renderer.NewHTML(htmlCfg)), tablewriter.WithConfig(tablewriter.Config{ Header: tw.CellConfig{ - Formatting: tw.CellFormatting{ - Alignment: tw.AlignCenter, - MergeMode: tw.MergeHorizontal, // Merge identical header cells - }, + Formatting: tw.CellFormatting{MergeMode: tw.MergeHorizontal}, // Merge identical header cells + Alignment: tw.CellAlignment{Global: tw.AlignCenter}, }, Row: tw.CellConfig{ - Formatting: tw.CellFormatting{ - MergeMode: tw.MergeHorizontal, // Merge identical row cells - Alignment: tw.AlignLeft, - }, - }, - Footer: tw.CellConfig{ - Formatting: tw.CellFormatting{Alignment: tw.AlignRight}, + Formatting: tw.CellFormatting{MergeMode: tw.MergeHorizontal}, // Merge identical row cells + Alignment: tw.CellAlignment{Global: tw.AlignLeft}, }, + Footer: tw.CellConfig{Alignment: tw.CellAlignment{Global: tw.AlignRight}}, }), ) diff --git a/vendor/github.com/olekukonko/tablewriter/config.go b/vendor/github.com/olekukonko/tablewriter/config.go index 64c55786..94094f1b 100644 --- a/vendor/github.com/olekukonko/tablewriter/config.go +++ b/vendor/github.com/olekukonko/tablewriter/config.go @@ -1,89 +1,71 @@ -// Package tablewriter provides functionality for creating and formatting tables with customizable configurations. package tablewriter import ( - "github.com/olekukonko/ll" // Logging library for debug output - "github.com/olekukonko/tablewriter/tw" // Table writer core types and utilities - "io" // Input/output interfaces - "reflect" // Reflection for type handling + "github.com/olekukonko/tablewriter/tw" ) -// ColumnConfigBuilder is used to configure settings for a specific column across all table sections (header, row, footer). -type ColumnConfigBuilder struct { - parent *ConfigBuilder // Reference to the parent ConfigBuilder for chaining - col int // Index of the column being configured -} - -// Build returns the parent ConfigBuilder to allow method chaining. -func (c *ColumnConfigBuilder) Build() *ConfigBuilder { - return c.parent +// Config represents the table configuration +type Config struct { + MaxWidth int + Header tw.CellConfig + Row tw.CellConfig + Footer tw.CellConfig + Debug bool + Stream tw.StreamConfig + Behavior tw.Behavior + Widths tw.CellWidth } -// WithAlignment sets the text alignment for a specific column in the header section only. -// Invalid alignments are ignored, and the method returns the builder for chaining. -func (c *ColumnConfigBuilder) WithAlignment(align tw.Align) *ColumnConfigBuilder { - if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone { - return c - } - // Ensure the ColumnAligns slice is large enough to accommodate the column index - if len(c.parent.config.Header.ColumnAligns) <= c.col { - newAligns := make([]tw.Align, c.col+1) - copy(newAligns, c.parent.config.Header.ColumnAligns) - c.parent.config.Header.ColumnAligns = newAligns - } - c.parent.config.Header.ColumnAligns[c.col] = align - return c +// ConfigBuilder provides a fluent interface for building Config +type ConfigBuilder struct { + config Config } -// WithMaxWidth sets the maximum width for a specific column across all sections (header, row, footer). -// Negative widths are ignored, and the method returns the builder for chaining. -func (c *ColumnConfigBuilder) WithMaxWidth(width int) *ColumnConfigBuilder { - if width < 0 { - return c - } - // Initialize PerColumn maps if they don't exist - if c.parent.config.Header.ColMaxWidths.PerColumn == nil { - c.parent.config.Header.ColMaxWidths.PerColumn = make(map[int]int) - c.parent.config.Row.ColMaxWidths.PerColumn = make(map[int]int) - c.parent.config.Footer.ColMaxWidths.PerColumn = make(map[int]int) +// NewConfigBuilder creates a new ConfigBuilder with defaults +func NewConfigBuilder() *ConfigBuilder { + return &ConfigBuilder{ + config: defaultConfig(), } - c.parent.config.Header.ColMaxWidths.PerColumn[c.col] = width - c.parent.config.Row.ColMaxWidths.PerColumn[c.col] = width - c.parent.config.Footer.ColMaxWidths.PerColumn[c.col] = width - return c } -// Config represents the overall configuration for a table, including settings for header, rows, footer, and behavior. -type Config struct { - MaxWidth int // Maximum width of the entire table (0 for unlimited) - Header tw.CellConfig // Configuration for the header section - Row tw.CellConfig // Configuration for the row section - Footer tw.CellConfig // Configuration for the footer section - Debug bool // Enables debug logging when true - Stream tw.StreamConfig // Configuration specific to streaming mode - Behavior Behavior // Behavioral settings like auto-hiding and trimming +// Build returns the built Config +func (b *ConfigBuilder) Build() Config { + return b.config } -// ConfigBuilder provides a fluent interface for building a Config struct with both direct and nested configuration methods. -type ConfigBuilder struct { - config Config // The configuration being built +// Header returns a HeaderConfigBuilder for header configuration +func (b *ConfigBuilder) Header() *HeaderConfigBuilder { + return &HeaderConfigBuilder{ + parent: b, + config: &b.config.Header, + } } -// Build finalizes and returns the Config struct after all modifications. -func (b *ConfigBuilder) Build() Config { - return b.config +// Row returns a RowConfigBuilder for row configuration +func (b *ConfigBuilder) Row() *RowConfigBuilder { + return &RowConfigBuilder{ + parent: b, + config: &b.config.Row, + } } -// Footer returns a builder for advanced configuration of the footer section. +// Footer returns a FooterConfigBuilder for footer configuration func (b *ConfigBuilder) Footer() *FooterConfigBuilder { return &FooterConfigBuilder{ - parent: b, - config: &b.config.Footer, - section: "footer", + parent: b, + config: &b.config.Footer, + } +} + +// Behavior returns a BehaviorConfigBuilder for behavior configuration +func (b *ConfigBuilder) Behavior() *BehaviorConfigBuilder { + return &BehaviorConfigBuilder{ + parent: b, + config: &b.config.Behavior, } } -// ForColumn returns a builder for configuring a specific column across all sections. +// ForColumn returns a ColumnConfigBuilder for column-specific configuration func (b *ConfigBuilder) ForColumn(col int) *ColumnConfigBuilder { return &ColumnConfigBuilder{ parent: b, @@ -91,22 +73,17 @@ func (b *ConfigBuilder) ForColumn(col int) *ColumnConfigBuilder { } } -// Header returns a builder for advanced configuration of the header section. -func (b *ConfigBuilder) Header() *HeaderConfigBuilder { - return &HeaderConfigBuilder{ - parent: b, - config: &b.config.Header, - section: "header", - } +// WithTrimSpace enables or disables automatic trimming of leading/trailing spaces. +// Ignored in streaming mode. +func (b *ConfigBuilder) WithTrimSpace(state tw.State) *ConfigBuilder { + b.config.Behavior.TrimSpace = state + return b } -// Row returns a builder for advanced configuration of the row section. -func (b *ConfigBuilder) Row() *RowConfigBuilder { - return &RowConfigBuilder{ - parent: b, - config: &b.config.Row, - section: "row", - } +// WithDebug enables/disables debug logging +func (b *ConfigBuilder) WithDebug(debug bool) *ConfigBuilder { + b.config.Debug = debug + return b } // WithAutoHide enables or disables automatic hiding of empty columns (ignored in streaming mode). @@ -115,19 +92,13 @@ func (b *ConfigBuilder) WithAutoHide(state tw.State) *ConfigBuilder { return b } -// WithDebug enables or disables debug logging for the table. -func (b *ConfigBuilder) WithDebug(debug bool) *ConfigBuilder { - b.config.Debug = debug - return b -} - // WithFooterAlignment sets the text alignment for all footer cells. // Invalid alignments are ignored. func (b *ConfigBuilder) WithFooterAlignment(align tw.Align) *ConfigBuilder { if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone { return b } - b.config.Footer.Formatting.Alignment = align + b.config.Footer.Alignment.Global = align return b } @@ -179,7 +150,7 @@ func (b *ConfigBuilder) WithHeaderAlignment(align tw.Align) *ConfigBuilder { if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone { return b } - b.config.Header.Formatting.Alignment = align + b.config.Header.Alignment.Global = align return b } @@ -242,7 +213,7 @@ func (b *ConfigBuilder) WithRowAlignment(align tw.Align) *ConfigBuilder { if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone { return b } - b.config.Row.Formatting.Alignment = align + b.config.Row.Alignment.Global = align return b } @@ -288,864 +259,702 @@ func (b *ConfigBuilder) WithRowMergeMode(mergeMode int) *ConfigBuilder { return b } -// WithTrimSpace enables or disables automatic trimming of leading/trailing spaces. -// Ignored in streaming mode. -func (b *ConfigBuilder) WithTrimSpace(state tw.State) *ConfigBuilder { - b.config.Behavior.TrimSpace = state - return b +// HeaderConfigBuilder configures header settings +type HeaderConfigBuilder struct { + parent *ConfigBuilder + config *tw.CellConfig } -// FooterConfigBuilder provides advanced configuration options for the footer section. -type FooterConfigBuilder struct { - parent *ConfigBuilder // Reference to the parent ConfigBuilder - config *tw.CellConfig // Footer configuration being modified - section string // Section name for logging/debugging +// Build returns the parent ConfigBuilder +func (h *HeaderConfigBuilder) Build() *ConfigBuilder { + return h.parent } -// Build returns the parent ConfigBuilder for chaining. -func (f *FooterConfigBuilder) Build() *ConfigBuilder { - return f.parent +// Alignment returns an AlignmentConfigBuilder for header alignment +func (h *HeaderConfigBuilder) Alignment() *AlignmentConfigBuilder { + return &AlignmentConfigBuilder{ + parent: h.parent, + config: &h.config.Alignment, + section: "header", + } } -// Formatting returns a builder for configuring footer formatting settings. -func (f *FooterConfigBuilder) Formatting() *FooterFormattingBuilder { - return &FooterFormattingBuilder{ - parent: f, - config: &f.config.Formatting, - section: f.section, +// Formatting returns a HeaderFormattingBuilder for header formatting +func (h *HeaderConfigBuilder) Formatting() *HeaderFormattingBuilder { + return &HeaderFormattingBuilder{ + parent: h, + config: &h.config.Formatting, + section: "header", } } -// Padding returns a builder for configuring footer padding settings. -func (f *FooterConfigBuilder) Padding() *FooterPaddingBuilder { - return &FooterPaddingBuilder{ - parent: f, - config: &f.config.Padding, - section: f.section, +// Padding returns a HeaderPaddingBuilder for header padding +func (h *HeaderConfigBuilder) Padding() *HeaderPaddingBuilder { + return &HeaderPaddingBuilder{ + parent: h, + config: &h.config.Padding, + section: "header", } } -// FooterFormattingBuilder configures formatting options for the footer section. -type FooterFormattingBuilder struct { - parent *FooterConfigBuilder // Reference to the parent FooterConfigBuilder - config *tw.CellFormatting // Formatting configuration being modified - section string // Section name for logging/debugging +// Filter returns a HeaderFilterBuilder for header filtering +func (h *HeaderConfigBuilder) Filter() *HeaderFilterBuilder { + return &HeaderFilterBuilder{ + parent: h, + config: &h.config.Filter, + section: "header", + } } -// Build returns the parent FooterConfigBuilder for chaining. -func (ff *FooterFormattingBuilder) Build() *FooterConfigBuilder { - return ff.parent +// Callbacks returns a HeaderCallbacksBuilder for header callbacks +func (h *HeaderConfigBuilder) Callbacks() *HeaderCallbacksBuilder { + return &HeaderCallbacksBuilder{ + parent: h, + config: &h.config.Callbacks, + section: "header", + } } -// WithAlignment sets the text alignment for footer cells. -// Invalid alignments are ignored. -func (ff *FooterFormattingBuilder) WithAlignment(align tw.Align) *FooterFormattingBuilder { - if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone { - return ff +// RowConfigBuilder configures row settings +type RowConfigBuilder struct { + parent *ConfigBuilder + config *tw.CellConfig +} + +// Build returns the parent ConfigBuilder +func (r *RowConfigBuilder) Build() *ConfigBuilder { + return r.parent +} + +// Alignment returns an AlignmentConfigBuilder for row alignment +func (r *RowConfigBuilder) Alignment() *AlignmentConfigBuilder { + return &AlignmentConfigBuilder{ + parent: r.parent, + config: &r.config.Alignment, + section: "row", } - ff.config.Alignment = align - return ff } -// WithAutoFormat enables or disables automatic formatting for footer cells. -func (ff *FooterFormattingBuilder) WithAutoFormat(autoFormat tw.State) *FooterFormattingBuilder { - ff.config.AutoFormat = autoFormat - return ff +// Formatting returns a RowFormattingBuilder for row formatting +func (r *RowConfigBuilder) Formatting() *RowFormattingBuilder { + return &RowFormattingBuilder{ + parent: r, + config: &r.config.Formatting, + section: "row", + } } -// WithAutoWrap sets the wrapping behavior for footer cells. -// Invalid wrap modes are ignored. -func (ff *FooterFormattingBuilder) WithAutoWrap(autoWrap int) *FooterFormattingBuilder { - if autoWrap < tw.WrapNone || autoWrap > tw.WrapBreak { - return ff +// Padding returns a RowPaddingBuilder for row padding +func (r *RowConfigBuilder) Padding() *RowPaddingBuilder { + return &RowPaddingBuilder{ + parent: r, + config: &r.config.Padding, + section: "row", } - ff.config.AutoWrap = autoWrap - return ff } -// WithMaxWidth sets the maximum content width for footer cells. -// Negative values are ignored. -//func (ff *FooterFormattingBuilder) WithMaxWidth(maxWidth int) *FooterFormattingBuilder { -// if maxWidth < 0 { -// return ff -// } -// ff.config.Foo = maxWidth -// return ff -//} - -// WithNewarkMode sets the merge behavior for footer cells (likely a typo, should be WithMergeMode). -// Invalid merge modes are ignored. -func (ff *FooterFormattingBuilder) WithNewarkMode(mergeMode int) *FooterFormattingBuilder { - if mergeMode < tw.MergeNone || mergeMode > tw.MergeHierarchical { - return ff +// Filter returns a RowFilterBuilder for row filtering +func (r *RowConfigBuilder) Filter() *RowFilterBuilder { + return &RowFilterBuilder{ + parent: r, + config: &r.config.Filter, + section: "row", } - ff.config.MergeMode = mergeMode - return ff } -// FooterPaddingBuilder configures padding options for the footer section. -type FooterPaddingBuilder struct { - parent *FooterConfigBuilder // Reference to the parent FooterConfigBuilder - config *tw.CellPadding // Padding configuration being modified - section string // Section name for logging/debugging +// Callbacks returns a RowCallbacksBuilder for row callbacks +func (r *RowConfigBuilder) Callbacks() *RowCallbacksBuilder { + return &RowCallbacksBuilder{ + parent: r, + config: &r.config.Callbacks, + section: "row", + } } -// AddColumnPadding adds padding for a specific column in the footer. -func (fp *FooterPaddingBuilder) AddColumnPadding(padding tw.Padding) *FooterPaddingBuilder { - fp.config.PerColumn = append(fp.config.PerColumn, padding) - return fp +// FooterConfigBuilder configures footer settings +type FooterConfigBuilder struct { + parent *ConfigBuilder + config *tw.CellConfig } -// Build returns the parent FooterConfigBuilder for chaining. -func (fp *FooterPaddingBuilder) Build() *FooterConfigBuilder { - return fp.parent +// Build returns the parent ConfigBuilder +func (f *FooterConfigBuilder) Build() *ConfigBuilder { + return f.parent } -// WithGlobal sets the global padding for all footer cells. -func (fp *FooterPaddingBuilder) WithGlobal(padding tw.Padding) *FooterPaddingBuilder { - fp.config.Global = padding - return fp +// Alignment returns an AlignmentConfigBuilder for footer alignment +func (f *FooterConfigBuilder) Alignment() *AlignmentConfigBuilder { + return &AlignmentConfigBuilder{ + parent: f.parent, + config: &f.config.Alignment, + section: "footer", + } } -// WithPerColumn sets per-column padding for the footer. -func (fp *FooterPaddingBuilder) WithPerColumn(padding []tw.Padding) *FooterPaddingBuilder { - fp.config.PerColumn = padding - return fp +// Formatting returns a FooterFormattingBuilder for footer formatting +func (f *FooterConfigBuilder) Formatting() *FooterFormattingBuilder { + return &FooterFormattingBuilder{ + parent: f, + config: &f.config.Formatting, + section: "footer", + } } -// HeaderConfigBuilder provides advanced configuration options for the header section. -type HeaderConfigBuilder struct { - parent *ConfigBuilder // Reference to the parent ConfigBuilder - config *tw.CellConfig // Header configuration being modified - section string // Section name for logging/debugging +// Padding returns a FooterPaddingBuilder for footer padding +func (f *FooterConfigBuilder) Padding() *FooterPaddingBuilder { + return &FooterPaddingBuilder{ + parent: f, + config: &f.config.Padding, + section: "footer", + } } -// Build returns the parent ConfigBuilder for chaining. -func (h *HeaderConfigBuilder) Build() *ConfigBuilder { - return h.parent +// Filter returns a FooterFilterBuilder for footer filtering +func (f *FooterConfigBuilder) Filter() *FooterFilterBuilder { + return &FooterFilterBuilder{ + parent: f, + config: &f.config.Filter, + section: "footer", + } } -// Formatting returns a builder for configuring header formatting settings. -func (h *HeaderConfigBuilder) Formatting() *HeaderFormattingBuilder { - return &HeaderFormattingBuilder{ - parent: h, - config: &h.config.Formatting, - section: h.section, +// Callbacks returns a FooterCallbacksBuilder for footer callbacks +func (f *FooterConfigBuilder) Callbacks() *FooterCallbacksBuilder { + return &FooterCallbacksBuilder{ + parent: f, + config: &f.config.Callbacks, + section: "footer", } } -// Padding returns a builder for configuring header padding settings. -func (h *HeaderConfigBuilder) Padding() *HeaderPaddingBuilder { - return &HeaderPaddingBuilder{ - parent: h, - config: &h.config.Padding, - section: h.section, +// AlignmentConfigBuilder configures alignment settings +type AlignmentConfigBuilder struct { + parent *ConfigBuilder + config *tw.CellAlignment + section string +} + +// Build returns the parent ConfigBuilder +func (a *AlignmentConfigBuilder) Build() *ConfigBuilder { + return a.parent +} + +// WithGlobal sets global alignment +func (a *AlignmentConfigBuilder) WithGlobal(align tw.Align) *AlignmentConfigBuilder { + if err := align.Validate(); err == nil { + a.config.Global = align } + return a } -// HeaderFormattingBuilder configures formatting options for the header section. +// WithPerColumn sets per-column alignments +func (a *AlignmentConfigBuilder) WithPerColumn(alignments []tw.Align) *AlignmentConfigBuilder { + if len(alignments) > 0 { + a.config.PerColumn = alignments + } + return a +} + +// HeaderFormattingBuilder configures header formatting type HeaderFormattingBuilder struct { - parent *HeaderConfigBuilder // Reference to the parent HeaderConfigBuilder - config *tw.CellFormatting // Formatting configuration being modified - section string // Section name for logging/debugging + parent *HeaderConfigBuilder + config *tw.CellFormatting + section string } -// Build returns the parent HeaderConfigBuilder for chaining. +// Build returns the parent HeaderConfigBuilder func (hf *HeaderFormattingBuilder) Build() *HeaderConfigBuilder { return hf.parent } -// WithAlignment sets the text alignment for header cells. -// Invalid alignments are ignored. -func (hf *HeaderFormattingBuilder) WithAlignment(align tw.Align) *HeaderFormattingBuilder { - if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone { - return hf - } - hf.config.Alignment = align - return hf -} - -// WithAutoFormat enables or disables automatic formatting for header cells. +// WithAutoFormat enables/disables auto formatting func (hf *HeaderFormattingBuilder) WithAutoFormat(autoFormat tw.State) *HeaderFormattingBuilder { hf.config.AutoFormat = autoFormat return hf } -// WithAutoWrap sets the wrapping behavior for header cells. -// Invalid wrap modes are ignored. +// WithAutoWrap sets auto wrap mode func (hf *HeaderFormattingBuilder) WithAutoWrap(autoWrap int) *HeaderFormattingBuilder { - if autoWrap < tw.WrapNone || autoWrap > tw.WrapBreak { - return hf + if autoWrap >= tw.WrapNone && autoWrap <= tw.WrapBreak { + hf.config.AutoWrap = autoWrap } - hf.config.AutoWrap = autoWrap return hf } -// WithMaxWidth sets the maximum content width for header cells. -// Negative values are ignored. -//func (hf *HeaderFormattingBuilder) WithMaxWidth(maxWidth int) *HeaderFormattingBuilder { -// if maxWidth < 0 { -// return hf -// } -// hf.config.MaxWidth = maxWidth -// return hf -//} - -// WithMergeMode sets the merge behavior for header cells. -// Invalid merge modes are ignored. +// WithMergeMode sets merge mode func (hf *HeaderFormattingBuilder) WithMergeMode(mergeMode int) *HeaderFormattingBuilder { - if mergeMode < tw.MergeNone || mergeMode > tw.MergeHierarchical { - return hf + if mergeMode >= tw.MergeNone && mergeMode <= tw.MergeHierarchical { + hf.config.MergeMode = mergeMode } - hf.config.MergeMode = mergeMode return hf } -// HeaderPaddingBuilder configures padding options for the header section. -type HeaderPaddingBuilder struct { - parent *HeaderConfigBuilder // Reference to the parent HeaderConfigBuilder - config *tw.CellPadding // Padding configuration being modified - section string // Section name for logging/debugging +// RowFormattingBuilder configures row formatting +type RowFormattingBuilder struct { + parent *RowConfigBuilder + config *tw.CellFormatting + section string } -// AddColumnPadding adds padding for a specific column in the header. -func (hp *HeaderPaddingBuilder) AddColumnPadding(padding tw.Padding) *HeaderPaddingBuilder { - hp.config.PerColumn = append(hp.config.PerColumn, padding) - return hp +// Build returns the parent RowConfigBuilder +func (rf *RowFormattingBuilder) Build() *RowConfigBuilder { + return rf.parent } -// Build returns the parent HeaderConfigBuilder for chaining. -func (hp *HeaderPaddingBuilder) Build() *HeaderConfigBuilder { - return hp.parent +// WithAutoFormat enables/disables auto formatting +func (rf *RowFormattingBuilder) WithAutoFormat(autoFormat tw.State) *RowFormattingBuilder { + rf.config.AutoFormat = autoFormat + return rf } -// WithGlobal sets the global padding for all header cells. -func (hp *HeaderPaddingBuilder) WithGlobal(padding tw.Padding) *HeaderPaddingBuilder { - hp.config.Global = padding - return hp +// WithAutoWrap sets auto wrap mode +func (rf *RowFormattingBuilder) WithAutoWrap(autoWrap int) *RowFormattingBuilder { + if autoWrap >= tw.WrapNone && autoWrap <= tw.WrapBreak { + rf.config.AutoWrap = autoWrap + } + return rf } -// WithPerColumn sets per-column padding for the header. -func (hp *HeaderPaddingBuilder) WithPerColumn(padding []tw.Padding) *HeaderPaddingBuilder { - hp.config.PerColumn = padding - return hp +// WithMergeMode sets merge mode +func (rf *RowFormattingBuilder) WithMergeMode(mergeMode int) *RowFormattingBuilder { + if mergeMode >= tw.MergeNone && mergeMode <= tw.MergeHierarchical { + rf.config.MergeMode = mergeMode + } + return rf } -// Option defines a function type for configuring a Table instance. -type Option func(target *Table) - -// RowConfigBuilder provides advanced configuration options for the row section. -type RowConfigBuilder struct { - parent *ConfigBuilder // Reference to the parent ConfigBuilder - config *tw.CellConfig // Row configuration being modified - section string // Section name for logging/debugging +// FooterFormattingBuilder configures footer formatting +type FooterFormattingBuilder struct { + parent *FooterConfigBuilder + config *tw.CellFormatting + section string } -// Build returns the parent ConfigBuilder for chaining. -func (r *RowConfigBuilder) Build() *ConfigBuilder { - return r.parent +// Build returns the parent FooterConfigBuilder +func (ff *FooterFormattingBuilder) Build() *FooterConfigBuilder { + return ff.parent } -// Formatting returns a builder for configuring row formatting settings. -func (r *RowConfigBuilder) Formatting() *RowFormattingBuilder { - return &RowFormattingBuilder{ - parent: r, - config: &r.config.Formatting, - section: r.section, - } +// WithAutoFormat enables/disables auto formatting +func (ff *FooterFormattingBuilder) WithAutoFormat(autoFormat tw.State) *FooterFormattingBuilder { + ff.config.AutoFormat = autoFormat + return ff } -// Padding returns a builder for configuring row padding settings. -func (r *RowConfigBuilder) Padding() *RowPaddingBuilder { - return &RowPaddingBuilder{ - parent: r, - config: &r.config.Padding, - section: r.section, +// WithAutoWrap sets auto wrap mode +func (ff *FooterFormattingBuilder) WithAutoWrap(autoWrap int) *FooterFormattingBuilder { + if autoWrap >= tw.WrapNone && autoWrap <= tw.WrapBreak { + ff.config.AutoWrap = autoWrap } + return ff } -// RowFormattingBuilder configures formatting options for the row section. -type RowFormattingBuilder struct { - parent *RowConfigBuilder // Reference to the parent RowConfigBuilder - config *tw.CellFormatting // Formatting configuration being modified - section string // Section name for logging/debugging +// WithMergeMode sets merge mode +func (ff *FooterFormattingBuilder) WithMergeMode(mergeMode int) *FooterFormattingBuilder { + if mergeMode >= tw.MergeNone && mergeMode <= tw.MergeHierarchical { + ff.config.MergeMode = mergeMode + } + return ff } -// Build returns the parent RowConfigBuilder for chaining. -func (rf *RowFormattingBuilder) Build() *RowConfigBuilder { - return rf.parent +// HeaderPaddingBuilder configures header padding +type HeaderPaddingBuilder struct { + parent *HeaderConfigBuilder + config *tw.CellPadding + section string } -// WithAlignment sets the text alignment for row cells. -// Invalid alignments are ignored. -func (rf *RowFormattingBuilder) WithAlignment(align tw.Align) *RowFormattingBuilder { - if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone { - return rf - } - rf.config.Alignment = align - return rf +// Build returns the parent HeaderConfigBuilder +func (hp *HeaderPaddingBuilder) Build() *HeaderConfigBuilder { + return hp.parent } -// WithAutoFormat enables or disables automatic formatting for row cells. -func (rf *RowFormattingBuilder) WithAutoFormat(autoFormat tw.State) *RowFormattingBuilder { - rf.config.AutoFormat = autoFormat - return rf +// WithGlobal sets global padding +func (hp *HeaderPaddingBuilder) WithGlobal(padding tw.Padding) *HeaderPaddingBuilder { + hp.config.Global = padding + return hp } -// WithAutoWrap sets the wrapping behavior for row cells. -// Invalid wrap modes are ignored. -func (rf *RowFormattingBuilder) WithAutoWrap(autoWrap int) *RowFormattingBuilder { - if autoWrap < tw.WrapNone || autoWrap > tw.WrapBreak { - return rf - } - rf.config.AutoWrap = autoWrap - return rf +// WithPerColumn sets per-column padding +func (hp *HeaderPaddingBuilder) WithPerColumn(padding []tw.Padding) *HeaderPaddingBuilder { + hp.config.PerColumn = padding + return hp } -// WithMergeMode sets the merge behavior for row cells. -// Invalid merge modes are ignored. -func (rf *RowFormattingBuilder) WithMergeMode(mergeMode int) *RowFormattingBuilder { - if mergeMode < tw.MergeNone || mergeMode > tw.MergeHierarchical { - return rf - } - rf.config.MergeMode = mergeMode - return rf +// AddColumnPadding adds padding for a specific column in the header +func (hp *HeaderPaddingBuilder) AddColumnPadding(padding tw.Padding) *HeaderPaddingBuilder { + hp.config.PerColumn = append(hp.config.PerColumn, padding) + return hp } -// RowPaddingBuilder configures padding options for the row section. +// RowPaddingBuilder configures row padding type RowPaddingBuilder struct { - parent *RowConfigBuilder // Reference to the parent RowConfigBuilder - config *tw.CellPadding // Padding configuration being modified - section string // Section name for logging/debugging + parent *RowConfigBuilder + config *tw.CellPadding + section string } -// AddColumnPadding adds padding for a specific column in the rows. -func (rp *RowPaddingBuilder) AddColumnPadding(padding tw.Padding) *RowPaddingBuilder { - rp.config.PerColumn = append(rp.config.PerColumn, padding) - return rp -} - -// Build returns the parent RowConfigBuilder for chaining. +// Build returns the parent RowConfigBuilder func (rp *RowPaddingBuilder) Build() *RowConfigBuilder { return rp.parent } -// WithGlobal sets the global padding for all row cells. +// WithGlobal sets global padding func (rp *RowPaddingBuilder) WithGlobal(padding tw.Padding) *RowPaddingBuilder { rp.config.Global = padding return rp } -// WithPerColumn sets per-column padding for the rows. +// WithPerColumn sets per-column padding func (rp *RowPaddingBuilder) WithPerColumn(padding []tw.Padding) *RowPaddingBuilder { rp.config.PerColumn = padding return rp } -// NewConfigBuilder creates a new ConfigBuilder initialized with default settings. -func NewConfigBuilder() *ConfigBuilder { - return &ConfigBuilder{ - config: defaultConfig(), - } +// AddColumnPadding adds padding for a specific column in the rows +func (rp *RowPaddingBuilder) AddColumnPadding(padding tw.Padding) *RowPaddingBuilder { + rp.config.PerColumn = append(rp.config.PerColumn, padding) + return rp } -// NewWriter creates a new table with default settings for backward compatibility. -// It logs the creation if debugging is enabled. -func NewWriter(w io.Writer) *Table { - t := NewTable(w) - if t.logger != nil { - t.logger.Debug("NewWriter created buffered Table") - } - return t +// FooterPaddingBuilder configures footer padding +type FooterPaddingBuilder struct { + parent *FooterConfigBuilder + config *tw.CellPadding + section string } -// WithAutoHide enables or disables automatic hiding of columns with empty data rows. -// Logs the change if debugging is enabled. -func WithAutoHide(state tw.State) Option { - return func(target *Table) { - target.config.Behavior.AutoHide = state - if target.logger != nil { - target.logger.Debugf("Option: WithAutoHide applied to Table: %v", state) - } - } +// Build returns the parent FooterConfigBuilder +func (fp *FooterPaddingBuilder) Build() *FooterConfigBuilder { + return fp.parent } -// WithColumnMax sets a global maximum column width for the table in streaming mode. -// Negative values are ignored, and the change is logged if debugging is enabled. -func WithColumnMax(width int) Option { - return func(target *Table) { - if width < 0 { - return - } - target.config.Stream.Widths.Global = width - if target.logger != nil { - target.logger.Debugf("Option: WithColumnMax applied to Table: %v", width) - } - } +// WithGlobal sets global padding +func (fp *FooterPaddingBuilder) WithGlobal(padding tw.Padding) *FooterPaddingBuilder { + fp.config.Global = padding + return fp } -// WithTableMax sets a global maximum table width for the table -// Negative values are ignored, and the change is logged if debugging is enabled. -func WithTableMax(width int) Option { - return func(target *Table) { - if width < 0 { - return - } - target.config.MaxWidth = width - if target.logger != nil { - target.logger.Debugf("Option: WithTableMax applied to Table: %v", width) - } - } +// WithPerColumn sets per-column padding +func (fp *FooterPaddingBuilder) WithPerColumn(padding []tw.Padding) *FooterPaddingBuilder { + fp.config.PerColumn = padding + return fp } -// WithColumnWidths sets per-column widths for the table in streaming mode. -// Negative widths are removed, and the change is logged if debugging is enabled. -func WithColumnWidths(widths map[int]int) Option { - return func(target *Table) { - for k, v := range widths { - if v < 0 { - delete(widths, k) - } - } - target.config.Stream.Widths.PerColumn = widths - if target.logger != nil { - target.logger.Debugf("Option: WithColumnWidths applied to Table: %v", widths) - } - } +// AddColumnPadding adds padding for a specific column in the footer +func (fp *FooterPaddingBuilder) AddColumnPadding(padding tw.Padding) *FooterPaddingBuilder { + fp.config.PerColumn = append(fp.config.PerColumn, padding) + return fp } -// WithConfig applies a custom configuration to the table by merging it with the default configuration. -func WithConfig(cfg Config) Option { - return func(target *Table) { - target.config = mergeConfig(defaultConfig(), cfg) - } +// BehaviorConfigBuilder configures behavior settings +type BehaviorConfigBuilder struct { + parent *ConfigBuilder + config *tw.Behavior } -// WithDebug enables or disables debug logging and adjusts the logger level accordingly. -// Logs the change if debugging is enabled. -func WithDebug(debug bool) Option { - return func(target *Table) { - target.config.Debug = debug - } +// Build returns the parent ConfigBuilder +func (bb *BehaviorConfigBuilder) Build() *ConfigBuilder { + return bb.parent } -// WithFooter sets the table footers by calling the Footer method. -func WithFooter(footers []string) Option { - return func(target *Table) { - target.Footer(footers) - } +// WithAutoHide enables/disables auto-hide +func (bb *BehaviorConfigBuilder) WithAutoHide(state tw.State) *BehaviorConfigBuilder { + bb.config.AutoHide = state + return bb } -// WithFooterConfig applies a full footer configuration to the table. -// Logs the change if debugging is enabled. -func WithFooterConfig(config tw.CellConfig) Option { - return func(target *Table) { - target.config.Footer = config - if target.logger != nil { - target.logger.Debug("Option: WithFooterConfig applied to Table.") - } - } +// WithTrimSpace enables/disables trim space +func (bb *BehaviorConfigBuilder) WithTrimSpace(state tw.State) *BehaviorConfigBuilder { + bb.config.TrimSpace = state + return bb } -// WithFooterMergeMode sets the merge mode for footer cells. -// Invalid merge modes are ignored, and the change is logged if debugging is enabled. -func WithFooterMergeMode(mergeMode int) Option { - return func(target *Table) { - if mergeMode < tw.MergeNone || mergeMode > tw.MergeHierarchical { - return - } - target.config.Footer.Formatting.MergeMode = mergeMode - if target.logger != nil { - target.logger.Debugf("Option: WithFooterMergeMode applied to Table: %v", mergeMode) - } - } +// WithHeaderHide enables/disables header visibility +func (bb *BehaviorConfigBuilder) WithHeaderHide(state tw.State) *BehaviorConfigBuilder { + bb.config.Header.Hide = state + return bb } -// WithHeader sets the table headers by calling the Header method. -func WithHeader(headers []string) Option { - return func(target *Table) { - target.Header(headers) - } +// WithFooterHide enables/disables footer visibility +func (bb *BehaviorConfigBuilder) WithFooterHide(state tw.State) *BehaviorConfigBuilder { + bb.config.Footer.Hide = state + return bb +} + +// WithCompactMerge enables/disables compact width optimization for merged cells +func (bb *BehaviorConfigBuilder) WithCompactMerge(state tw.State) *BehaviorConfigBuilder { + bb.config.Compact.Merge = state + return bb +} + +// ColumnConfigBuilder configures column-specific settings +type ColumnConfigBuilder struct { + parent *ConfigBuilder + col int +} + +// Build returns the parent ConfigBuilder +func (c *ColumnConfigBuilder) Build() *ConfigBuilder { + return c.parent } -// WithHeaderAlignment sets the text alignment for header cells. -// Invalid alignments are ignored, and the change is logged if debugging is enabled. -func WithHeaderAlignment(align tw.Align) Option { - return func(target *Table) { - if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone { - return +// WithAlignment sets alignment for the column +func (c *ColumnConfigBuilder) WithAlignment(align tw.Align) *ColumnConfigBuilder { + if err := align.Validate(); err == nil { + // Ensure slices are large enough + if len(c.parent.config.Header.Alignment.PerColumn) <= c.col { + newAligns := make([]tw.Align, c.col+1) + copy(newAligns, c.parent.config.Header.Alignment.PerColumn) + c.parent.config.Header.Alignment.PerColumn = newAligns } - target.config.Header.Formatting.Alignment = align - if target.logger != nil { - target.logger.Debugf("Option: WithHeaderAlignment applied to Table: %v", align) + c.parent.config.Header.Alignment.PerColumn[c.col] = align + + if len(c.parent.config.Row.Alignment.PerColumn) <= c.col { + newAligns := make([]tw.Align, c.col+1) + copy(newAligns, c.parent.config.Row.Alignment.PerColumn) + c.parent.config.Row.Alignment.PerColumn = newAligns } - } -} + c.parent.config.Row.Alignment.PerColumn[c.col] = align -// WithHeaderConfig applies a full header configuration to the table. -// Logs the change if debugging is enabled. -func WithHeaderConfig(config tw.CellConfig) Option { - return func(target *Table) { - target.config.Header = config - if target.logger != nil { - target.logger.Debug("Option: WithHeaderConfig applied to Table.") + if len(c.parent.config.Footer.Alignment.PerColumn) <= c.col { + newAligns := make([]tw.Align, c.col+1) + copy(newAligns, c.parent.config.Footer.Alignment.PerColumn) + c.parent.config.Footer.Alignment.PerColumn = newAligns } + c.parent.config.Footer.Alignment.PerColumn[c.col] = align } + return c } -// WithLogger sets a custom logger for the table and updates the renderer if present. -// Logs the change if debugging is enabled. -func WithLogger(logger *ll.Logger) Option { - return func(target *Table) { - target.logger = logger - if target.logger != nil { - target.logger.Debug("Option: WithLogger applied to Table.") - if target.renderer != nil { - target.renderer.Logger(target.logger) - } +// WithMaxWidth sets max width for the column +func (c *ColumnConfigBuilder) WithMaxWidth(width int) *ColumnConfigBuilder { + if width >= 0 { + // Initialize maps if needed + if c.parent.config.Header.ColMaxWidths.PerColumn == nil { + c.parent.config.Header.ColMaxWidths.PerColumn = make(tw.Mapper[int, int]) + c.parent.config.Row.ColMaxWidths.PerColumn = make(tw.Mapper[int, int]) + c.parent.config.Footer.ColMaxWidths.PerColumn = make(tw.Mapper[int, int]) } + c.parent.config.Header.ColMaxWidths.PerColumn[c.col] = width + c.parent.config.Row.ColMaxWidths.PerColumn[c.col] = width + c.parent.config.Footer.ColMaxWidths.PerColumn[c.col] = width } + return c } -// WithRenderer sets a custom renderer for the table and attaches the logger if present. -// Logs the change if debugging is enabled. -func WithRenderer(f tw.Renderer) Option { - return func(target *Table) { - target.renderer = f - if target.logger != nil { - target.logger.Debugf("Option: WithRenderer applied to Table: %T", f) - f.Logger(target.logger) - } - } +// HeaderFilterBuilder configures header filtering +type HeaderFilterBuilder struct { + parent *HeaderConfigBuilder + config *tw.CellFilter + section string } -// WithRowConfig applies a full row configuration to the table. -// Logs the change if debugging is enabled. -func WithRowConfig(config tw.CellConfig) Option { - return func(target *Table) { - target.config.Row = config - if target.logger != nil { - target.logger.Debug("Option: WithRowConfig applied to Table.") - } - } +// Build returns the parent HeaderConfigBuilder +func (hf *HeaderFilterBuilder) Build() *HeaderConfigBuilder { + return hf.parent } -// WithRowMaxWidth sets the maximum content width for row cells. -// Negative values are ignored, and the change is logged if debugging is enabled. -func WithRowMaxWidth(maxWidth int) Option { - return func(target *Table) { - if maxWidth < 0 { - return - } - target.config.Row.ColMaxWidths.Global = maxWidth - if target.logger != nil { - target.logger.Debugf("Option: WithRowMaxWidth applied to Table: %v", maxWidth) - } +// WithGlobal sets the global filter function for the header +func (hf *HeaderFilterBuilder) WithGlobal(filter func([]string) []string) *HeaderFilterBuilder { + if filter != nil { + hf.config.Global = filter } + return hf } -// WithStreaming applies a streaming configuration to the table by merging it with the existing configuration. -// Logs the change if debugging is enabled. -func WithStreaming(c tw.StreamConfig) Option { - return func(target *Table) { - target.config.Stream = mergeStreamConfig(target.config.Stream, c) - if target.logger != nil { - target.logger.Debug("Option: WithStreaming applied to Table.") - } +// WithPerColumn sets per-column filter functions for the header +func (hf *HeaderFilterBuilder) WithPerColumn(filters []func(string) string) *HeaderFilterBuilder { + if len(filters) > 0 { + hf.config.PerColumn = filters } + return hf } -// WithStringer sets a custom stringer function for converting row data and clears the stringer cache. -// Logs the change if debugging is enabled. -func WithStringer(stringer interface{}) Option { - return func(t *Table) { - t.stringer = stringer - t.stringerCacheMu.Lock() - t.stringerCache = make(map[reflect.Type]reflect.Value) - t.stringerCacheMu.Unlock() - t.logger.Debug("Stringer updated, cache cleared") +// AddColumnFilter adds a filter function for a specific column in the header +func (hf *HeaderFilterBuilder) AddColumnFilter(filter func(string) string) *HeaderFilterBuilder { + if filter != nil { + hf.config.PerColumn = append(hf.config.PerColumn, filter) } + return hf } -// WithStringerCache enables caching for the stringer function. -func WithStringerCache() Option { - return func(t *Table) { - t.stringerCacheEnabled = true - } +// RowFilterBuilder configures row filtering +type RowFilterBuilder struct { + parent *RowConfigBuilder + config *tw.CellFilter + section string } -// WithSymbols sets the symbols used for table drawing and updates the renderer's configuration. -// Logs the change if debugging is enabled. -func WithSymbols(symbols tw.Symbols) Option { - return func(target *Table) { - if target.renderer != nil { - cfg := target.renderer.Config() - cfg.Symbols = symbols - if target.logger != nil { - target.logger.Debug("Option: WithSymbols applied to Table.") - } - } - } +// Build returns the parent RowConfigBuilder +func (rf *RowFilterBuilder) Build() *RowConfigBuilder { + return rf.parent } -// WithTrimSpace sets whether leading and trailing spaces are automatically trimmed. -// Logs the change if debugging is enabled. -func WithTrimSpace(state tw.State) Option { - return func(target *Table) { - target.config.Behavior.TrimSpace = state - if target.logger != nil { - target.logger.Debugf("Option: WithTrimSpace applied to Table: %v", state) - } +// WithGlobal sets the global filter function for the rows +func (rf *RowFilterBuilder) WithGlobal(filter func([]string) []string) *RowFilterBuilder { + if filter != nil { + rf.config.Global = filter } + return rf } -func WithHeaderAutoFormat(state tw.State) Option { - return func(target *Table) { - target.config.Header.Formatting.AutoFormat = state +// WithPerColumn sets per-column filter functions for the rows +func (rf *RowFilterBuilder) WithPerColumn(filters []func(string) string) *RowFilterBuilder { + if len(filters) > 0 { + rf.config.PerColumn = filters } + return rf } -// WithHeaderControl sets the control behavior for the table header. -// Logs the change if debugging is enabled. -func WithHeaderControl(control tw.Control) Option { - return func(target *Table) { - target.config.Behavior.Header = control - if target.logger != nil { - target.logger.Debugf("Option: WithHeaderControl applied to Table: %v", control) // Fixed 'state' to 'control' - } +// AddColumnFilter adds a filter function for a specific column in the rows +func (rf *RowFilterBuilder) AddColumnFilter(filter func(string) string) *RowFilterBuilder { + if filter != nil { + rf.config.PerColumn = append(rf.config.PerColumn, filter) } + return rf } -// WithFooterControl sets the control behavior for the table footer. -// Logs the change if debugging is enabled. -func WithFooterControl(control tw.Control) Option { - return func(target *Table) { - target.config.Behavior.Footer = control - if target.logger != nil { - target.logger.Debugf("Option: WithFooterControl applied to Table: %v", control) // Fixed log message and 'state' to 'control' - } +// FooterFilterBuilder configures footer filtering +type FooterFilterBuilder struct { + parent *FooterConfigBuilder + config *tw.CellFilter + section string +} + +// Build returns the parent FooterConfigBuilder +func (ff *FooterFilterBuilder) Build() *FooterConfigBuilder { + return ff.parent +} + +// WithGlobal sets the global filter function for the footer +func (ff *FooterFilterBuilder) WithGlobal(filter func([]string) []string) *FooterFilterBuilder { + if filter != nil { + ff.config.Global = filter } + return ff } -// WithAlignment sets the default column alignment for the header, rows, and footer. -func WithAlignment(alignment tw.Alignment) Option { - return func(target *Table) { - target.config.Header.ColumnAligns = alignment - target.config.Row.ColumnAligns = alignment - target.config.Footer.ColumnAligns = alignment +// WithPerColumn sets per-column filter functions for the footer +func (ff *FooterFilterBuilder) WithPerColumn(filters []func(string) string) *FooterFilterBuilder { + if len(filters) > 0 { + ff.config.PerColumn = filters } + return ff } -// WithPadding sets the global padding for the header, rows, and footer. -func WithPadding(padding tw.Padding) Option { - return func(target *Table) { - target.config.Header.Padding.Global = padding - target.config.Row.Padding.Global = padding - target.config.Footer.Padding.Global = padding +// AddColumnFilter adds a filter function for a specific column in the footer +func (ff *FooterFilterBuilder) AddColumnFilter(filter func(string) string) *FooterFilterBuilder { + if filter != nil { + ff.config.PerColumn = append(ff.config.PerColumn, filter) } + return ff } -// WithRendition allows updating the active renderer's rendition configuration -// by merging the provided rendition. -// If the renderer does not implement tw.Renditioning, a warning is logged. -func WithRendition(rendition tw.Rendition) Option { - return func(target *Table) { - if target.renderer == nil { - target.logger.Warn("Option: WithRendition: No renderer set on table.") - return - } +// HeaderCallbacksBuilder configures header callbacks +type HeaderCallbacksBuilder struct { + parent *HeaderConfigBuilder + config *tw.CellCallbacks + section string +} - if ru, ok := target.renderer.(tw.Renditioning); ok { - ru.Rendition(rendition) - target.logger.Debugf("Option: WithRendition: Applied to renderer via Renditioning.SetRendition(): %+v", rendition) - } else { - target.logger.Warnf("Option: WithRendition: Current renderer type %T does not implement tw.Renditioning. Rendition may not be applied as expected.", target.renderer) - } - } +// Build returns the parent HeaderConfigBuilder +func (hc *HeaderCallbacksBuilder) Build() *HeaderConfigBuilder { + return hc.parent } -// defaultConfig returns a default Config with sensible settings for headers, rows, footers, and behavior. -func defaultConfig() Config { - defaultPadding := tw.Padding{Left: tw.Space, Right: tw.Space, Top: tw.Empty, Bottom: tw.Empty} - return Config{ - MaxWidth: 0, - Header: tw.CellConfig{ - Formatting: tw.CellFormatting{ - AutoWrap: tw.WrapTruncate, - Alignment: tw.AlignCenter, - AutoFormat: tw.On, - MergeMode: tw.MergeNone, - }, - Padding: tw.CellPadding{ - Global: defaultPadding, - }, - }, - Row: tw.CellConfig{ - Formatting: tw.CellFormatting{ - AutoWrap: tw.WrapNormal, - Alignment: tw.AlignLeft, - AutoFormat: tw.Off, - MergeMode: tw.MergeNone, - }, - Padding: tw.CellPadding{ - Global: defaultPadding, - }, - }, - Footer: tw.CellConfig{ - Formatting: tw.CellFormatting{ - AutoWrap: tw.WrapNormal, - Alignment: tw.AlignRight, - AutoFormat: tw.Off, - MergeMode: tw.MergeNone, - }, - Padding: tw.CellPadding{ - Global: defaultPadding, - }, - }, - Stream: tw.StreamConfig{}, - Debug: false, - Behavior: Behavior{ - AutoHide: tw.Off, - TrimSpace: tw.On, - }, +// WithGlobal sets the global callback function for the header +func (hc *HeaderCallbacksBuilder) WithGlobal(callback func()) *HeaderCallbacksBuilder { + if callback != nil { + hc.config.Global = callback } + return hc } -// mergeCellConfig merges a source CellConfig into a destination CellConfig, prioritizing non-default source values. -// It handles deep merging for complex fields like padding and callbacks. -func mergeCellConfig(dst, src tw.CellConfig) tw.CellConfig { - if src.Formatting.Alignment != tw.Empty { - dst.Formatting.Alignment = src.Formatting.Alignment - } - if src.Formatting.AutoWrap != 0 { - dst.Formatting.AutoWrap = src.Formatting.AutoWrap +// WithPerColumn sets per-column callback functions for the header +func (hc *HeaderCallbacksBuilder) WithPerColumn(callbacks []func()) *HeaderCallbacksBuilder { + if len(callbacks) > 0 { + hc.config.PerColumn = callbacks } - if src.ColMaxWidths.Global != 0 { - dst.ColMaxWidths.Global = src.ColMaxWidths.Global - } - if src.Formatting.MergeMode != 0 { - dst.Formatting.MergeMode = src.Formatting.MergeMode + return hc +} + +// AddColumnCallback adds a callback function for a specific column in the header +func (hc *HeaderCallbacksBuilder) AddColumnCallback(callback func()) *HeaderCallbacksBuilder { + if callback != nil { + hc.config.PerColumn = append(hc.config.PerColumn, callback) } + return hc +} - dst.Formatting.AutoFormat = src.Formatting.AutoFormat +// RowCallbacksBuilder configures row callbacks +type RowCallbacksBuilder struct { + parent *RowConfigBuilder + config *tw.CellCallbacks + section string +} - if src.Padding.Global != (tw.Padding{}) { - dst.Padding.Global = src.Padding.Global - } - if len(src.Padding.PerColumn) > 0 { - if dst.Padding.PerColumn == nil { - dst.Padding.PerColumn = make([]tw.Padding, len(src.Padding.PerColumn)) - } else if len(src.Padding.PerColumn) > len(dst.Padding.PerColumn) { - dst.Padding.PerColumn = append(dst.Padding.PerColumn, make([]tw.Padding, len(src.Padding.PerColumn)-len(dst.Padding.PerColumn))...) - } - for i, pad := range src.Padding.PerColumn { - if pad != (tw.Padding{}) { - dst.Padding.PerColumn[i] = pad - } - } - } - if src.Callbacks.Global != nil { - dst.Callbacks.Global = src.Callbacks.Global - } - if len(src.Callbacks.PerColumn) > 0 { - if dst.Callbacks.PerColumn == nil { - dst.Callbacks.PerColumn = make([]func(), len(src.Callbacks.PerColumn)) - } else if len(src.Callbacks.PerColumn) > len(dst.Callbacks.PerColumn) { - dst.Callbacks.PerColumn = append(dst.Callbacks.PerColumn, make([]func(), len(src.Callbacks.PerColumn)-len(dst.Callbacks.PerColumn))...) - } - for i, cb := range src.Callbacks.PerColumn { - if cb != nil { - dst.Callbacks.PerColumn[i] = cb - } - } - } - if src.Filter.Global != nil { - dst.Filter.Global = src.Filter.Global - } - if len(src.Filter.PerColumn) > 0 { - if dst.Filter.PerColumn == nil { - dst.Filter.PerColumn = make([]func(string) string, len(src.Filter.PerColumn)) - } else if len(src.Filter.PerColumn) > len(dst.Filter.PerColumn) { - dst.Filter.PerColumn = append(dst.Filter.PerColumn, make([]func(string) string, len(src.Filter.PerColumn)-len(dst.Filter.PerColumn))...) - } - for i, filter := range src.Filter.PerColumn { - if filter != nil { - dst.Filter.PerColumn[i] = filter - } - } - } - if len(src.ColumnAligns) > 0 { - if dst.ColumnAligns == nil { - dst.ColumnAligns = make([]tw.Align, len(src.ColumnAligns)) - } else if len(src.ColumnAligns) > len(dst.ColumnAligns) { - dst.ColumnAligns = append(dst.ColumnAligns, make([]tw.Align, len(src.ColumnAligns)-len(dst.ColumnAligns))...) - } - for i, align := range src.ColumnAligns { - if align != tw.Empty && align != tw.Skip { - dst.ColumnAligns[i] = align - } - } +// Build returns the parent RowConfigBuilder +func (rc *RowCallbacksBuilder) Build() *RowConfigBuilder { + return rc.parent +} + +// WithGlobal sets the global callback function for the rows +func (rc *RowCallbacksBuilder) WithGlobal(callback func()) *RowCallbacksBuilder { + if callback != nil { + rc.config.Global = callback } - if len(src.ColMaxWidths.PerColumn) > 0 { - if dst.ColMaxWidths.PerColumn == nil { - dst.ColMaxWidths.PerColumn = make(map[int]int) - } - for k, v := range src.ColMaxWidths.PerColumn { - if v != 0 { - dst.ColMaxWidths.PerColumn[k] = v - } - } + return rc +} + +// WithPerColumn sets per-column callback functions for the rows +func (rc *RowCallbacksBuilder) WithPerColumn(callbacks []func()) *RowCallbacksBuilder { + if len(callbacks) > 0 { + rc.config.PerColumn = callbacks } - return dst + return rc } -// mergeConfig merges a source Config into a destination Config, prioritizing non-default source values. -// It performs deep merging for complex types like Header, Row, Footer, and Stream. -func mergeConfig(dst, src Config) Config { - if src.MaxWidth != 0 { - dst.MaxWidth = src.MaxWidth +// AddColumnCallback adds a callback function for a specific column in the rows +func (rc *RowCallbacksBuilder) AddColumnCallback(callback func()) *RowCallbacksBuilder { + if callback != nil { + rc.config.PerColumn = append(rc.config.PerColumn, callback) } - dst.Debug = src.Debug || dst.Debug - dst.Behavior.AutoHide = src.Behavior.AutoHide - dst.Behavior.TrimSpace = src.Behavior.TrimSpace - dst.Header = mergeCellConfig(dst.Header, src.Header) - dst.Row = mergeCellConfig(dst.Row, src.Row) - dst.Footer = mergeCellConfig(dst.Footer, src.Footer) - dst.Stream = mergeStreamConfig(dst.Stream, src.Stream) + return rc +} - return dst +// FooterCallbacksBuilder configures footer callbacks +type FooterCallbacksBuilder struct { + parent *FooterConfigBuilder + config *tw.CellCallbacks + section string } -// mergeStreamConfig merges a source StreamConfig into a destination StreamConfig, prioritizing non-default source values. -func mergeStreamConfig(dst, src tw.StreamConfig) tw.StreamConfig { - if src.Enable { - dst.Enable = true - } - if src.Widths.Global != 0 { - dst.Widths.Global = src.Widths.Global - } - if len(src.Widths.PerColumn) > 0 { - if dst.Widths.PerColumn == nil { - dst.Widths.PerColumn = make(map[int]int) - } - for k, v := range src.Widths.PerColumn { - if v != 0 { - dst.Widths.PerColumn[k] = v - } - } +// Build returns the parent FooterConfigBuilder +func (fc *FooterCallbacksBuilder) Build() *FooterConfigBuilder { + return fc.parent +} + +// WithGlobal sets the global callback function for the footer +func (fc *FooterCallbacksBuilder) WithGlobal(callback func()) *FooterCallbacksBuilder { + if callback != nil { + fc.config.Global = callback } - return dst + return fc } -// padLine pads a line to the specified column count by appending empty strings as needed. -func padLine(line []string, numCols int) []string { - if len(line) >= numCols { - return line +// WithPerColumn sets per-column callback functions for the footer +func (fc *FooterCallbacksBuilder) WithPerColumn(callbacks []func()) *FooterCallbacksBuilder { + if len(callbacks) > 0 { + fc.config.PerColumn = callbacks } - padded := make([]string, numCols) - copy(padded, line) - for i := len(line); i < numCols; i++ { - padded[i] = tw.Empty + return fc +} + +// AddColumnCallback adds a callback function for a specific column in the footer +func (fc *FooterCallbacksBuilder) AddColumnCallback(callback func()) *FooterCallbacksBuilder { + if callback != nil { + fc.config.PerColumn = append(fc.config.PerColumn, callback) } - return padded + return fc } diff --git a/vendor/github.com/olekukonko/tablewriter/deprecated.go b/vendor/github.com/olekukonko/tablewriter/deprecated.go index 3958a25b..aa119e48 100644 --- a/vendor/github.com/olekukonko/tablewriter/deprecated.go +++ b/vendor/github.com/olekukonko/tablewriter/deprecated.go @@ -1,10 +1,30 @@ package tablewriter -import "github.com/olekukonko/tablewriter/tw" +import ( + "github.com/olekukonko/tablewriter/tw" +) -// Deprecated: WithBorders is no longer used. -// Border control has been moved to the renderer, which now manages its own borders. -// This Option has no effect on the Table and may be removed in future versions. +// 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. +// +// Deprecated: Use [WithRendition] to configure border settings for renderers that support +// [tw.Renditioning], or update the renderer's [tw.RenderConfig] directly via its Config() method. +// This function has no effect if no renderer is set on the table. +// +// Example migration: +// +// // Old (deprecated) +// table.Options(WithBorders(tw.Border{Top: true, Bottom: true})) +// // New (recommended) +// table.Options(WithRendition(tw.Rendition{Borders: tw.Border{Top: true, Bottom: true}})) +// +// Parameters: +// - borders: The [tw.Border] configuration to apply to the renderer's borders. +// +// Returns: +// +// An [Option] that updates the renderer's border settings if a renderer is set. +// Logs a debug message if debugging is enabled and a renderer is present. func WithBorders(borders tw.Border) Option { return func(target *Table) { if target.renderer != nil { @@ -17,16 +37,55 @@ func WithBorders(borders tw.Border) Option { } } -// Deprecated: WithBorders is no longer supported. -// Use [tw.Behavior] directly to configure border settings. +// Behavior is an alias for [tw.Behavior] to configure table behavior settings. +// This type is deprecated and will be removed in a future version. +// +// Deprecated: Use [tw.Behavior] directly to configure settings such as auto-hiding empty +// columns, trimming spaces, or controlling header/footer visibility. +// +// Example migration: +// +// // Old (deprecated) +// var b tablewriter.Behavior = tablewriter.Behavior{AutoHide: tw.On} +// // New (recommended) +// var b tw.Behavior = tw.Behavior{AutoHide: tw.On} type Behavior tw.Behavior -// Deprecated: WithRendererSettings i sno longer supported. +// Settings is an alias for [tw.Settings] to configure renderer settings. +// This type is deprecated and will be removed in a future version. +// +// Deprecated: Use [tw.Settings] directly to configure renderer settings, such as +// separators and line styles. +// +// Example migration: +// +// // Old (deprecated) +// var s tablewriter.Settings = tablewriter.Settings{Separator: "|"} +// // New (recommended) +// var s tw.Settings = tw.Settings{Separator: "|"} type Settings tw.Settings -// WithRendererSettings updates the renderer's settings (e.g., separators, lines). -// Render setting has move to renders directly -// you can also use WithRendition for renders that have rendition support +// WithRendererSettings updates the renderer's settings, such as separators and line styles. +// This function is deprecated and will be removed in a future version. +// +// Deprecated: Use [WithRendition] to update renderer settings for renderers that implement +// [tw.Renditioning], or configure the renderer's [tw.Settings] directly via its +// [tw.Renderer.Config] method. This function has no effect if no renderer is set. +// +// Example migration: +// +// // Old (deprecated) +// table.Options(WithRendererSettings(tw.Settings{Separator: "|"})) +// // New (recommended) +// table.Options(WithRendition(tw.Rendition{Settings: tw.Settings{Separator: "|"}})) +// +// Parameters: +// - settings: The [tw.Settings] configuration to apply to the renderer. +// +// Returns: +// +// An [Option] that updates the renderer's settings if a renderer is set. +// Logs a debug message if debugging is enabled and a renderer is present. func WithRendererSettings(settings tw.Settings) Option { return func(target *Table) { if target.renderer != nil { @@ -38,3 +97,124 @@ func WithRendererSettings(settings tw.Settings) Option { } } } + +// WithAlignment sets the text alignment for footer cells within the formatting configuration. +// This method is deprecated and will be removed in the next version. +// +// Deprecated: Use [FooterConfigBuilder.Alignment] with [AlignmentConfigBuilder.WithGlobal] +// or [AlignmentConfigBuilder.WithPerColumn] to configure footer alignments. +// Alternatively, apply a complete [tw.CellAlignment] configuration using +// [WithFooterAlignmentConfig]. +// +// Example migration: +// +// // Old (deprecated) +// builder.Footer().Formatting().WithAlignment(tw.AlignRight) +// // New (recommended) +// builder.Footer().Alignment().WithGlobal(tw.AlignRight) +// // Or +// table.Options(WithFooterAlignmentConfig(tw.CellAlignment{Global: tw.AlignRight})) +// +// Parameters: +// - align: The [tw.Align] value to set for footer cells. Valid values are +// [tw.AlignLeft], [tw.AlignRight], [tw.AlignCenter], and [tw.AlignNone]. +// Invalid alignments are ignored. +// +// Returns: +// +// The [FooterFormattingBuilder] instance for method chaining. +func (ff *FooterFormattingBuilder) WithAlignment(align tw.Align) *FooterFormattingBuilder { + if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone { + return ff + } + ff.config.Alignment = align + return ff +} + +// WithAlignment sets the text alignment for header cells within the formatting configuration. +// This method is deprecated and will be removed in the next version. +// +// Deprecated: Use [HeaderConfigBuilder.Alignment] with [AlignmentConfigBuilder.WithGlobal] +// or [AlignmentConfigBuilder.WithPerColumn] to configure header alignments. +// Alternatively, apply a complete [tw.CellAlignment] configuration using +// [WithHeaderAlignmentConfig]. +// +// Example migration: +// +// // Old (deprecated) +// builder.Header().Formatting().WithAlignment(tw.AlignCenter) +// // New (recommended) +// builder.Header().Alignment().WithGlobal(tw.AlignCenter) +// // Or +// table.Options(WithHeaderAlignmentConfig(tw.CellAlignment{Global: tw.AlignCenter})) +// +// Parameters: +// - align: The [tw.Align] value to set for header cells. Valid values are +// [tw.AlignLeft], [tw.AlignRight], [tw.AlignCenter], and [tw.AlignNone]. +// Invalid alignments are ignored. +// +// Returns: +// +// The [HeaderFormattingBuilder] instance for method chaining. +func (hf *HeaderFormattingBuilder) WithAlignment(align tw.Align) *HeaderFormattingBuilder { + if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone { + return hf + } + hf.config.Alignment = align + return hf +} + +// WithAlignment sets the text alignment for row cells within the formatting configuration. +// This method is deprecated and will be removed in the next version. +// +// Deprecated: Use [RowConfigBuilder.Alignment] with [AlignmentConfigBuilder.WithGlobal] +// or [AlignmentConfigBuilder.WithPerColumn] to configure row alignments. +// Alternatively, apply a complete [tw.CellAlignment] configuration using +// [WithRowAlignmentConfig]. +// +// Example migration: +// +// // Old (deprecated) +// builder.Row().Formatting().WithAlignment(tw.AlignLeft) +// // New (recommended) +// builder.Row().Alignment().WithGlobal(tw.AlignLeft) +// // Or +// table.Options(WithRowAlignmentConfig(tw.CellAlignment{Global: tw.AlignLeft})) +// +// Parameters: +// - align: The [tw.Align] value to set for row cells. Valid values are +// [tw.AlignLeft], [tw.AlignRight], [tw.AlignCenter], and [tw.AlignNone]. +// Invalid alignments are ignored. +// +// Returns: +// +// The [RowFormattingBuilder] instance for method chaining. +func (rf *RowFormattingBuilder) WithAlignment(align tw.Align) *RowFormattingBuilder { + if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone { + return rf + } + rf.config.Alignment = align + return rf +} + +// WithTableMax sets the maximum width of the entire table in characters. +// Negative values are ignored, and the change is logged if debugging is enabled. +// The width constrains the table's rendering, potentially causing text wrapping or truncation +// based on the configuration's wrapping settings (e.g., tw.WrapTruncate). +// If debug logging is enabled via WithDebug(true), the applied width is logged. +// +// Deprecated: Use WithMaxWidth instead, which provides the same functionality with a clearer name +// and consistent naming across the package. For example: +// +// tablewriter.NewTable(os.Stdout, tablewriter.WithMaxWidth(80)) +func WithTableMax(width int) Option { + return func(target *Table) { + if width < 0 { + return + } + target.config.MaxWidth = width + if target.logger != nil { + target.logger.Debugf("Option: WithTableMax applied to Table: %v", width) + } + } +} diff --git a/vendor/github.com/olekukonko/tablewriter/option.go b/vendor/github.com/olekukonko/tablewriter/option.go new file mode 100644 index 00000000..7270b768 --- /dev/null +++ b/vendor/github.com/olekukonko/tablewriter/option.go @@ -0,0 +1,891 @@ +package tablewriter + +import ( + "github.com/mattn/go-runewidth" + "github.com/olekukonko/ll" + "github.com/olekukonko/tablewriter/pkg/twwidth" + "github.com/olekukonko/tablewriter/tw" + "reflect" +) + +// Option defines a function type for configuring a Table instance. +type Option func(target *Table) + +// WithAutoHide enables or disables automatic hiding of columns with empty data rows. +// Logs the change if debugging is enabled. +func WithAutoHide(state tw.State) Option { + return func(target *Table) { + target.config.Behavior.AutoHide = state + if target.logger != nil { + target.logger.Debugf("Option: WithAutoHide applied to Table: %v", state) + } + } +} + +// WithColumnMax sets a global maximum column width for the table in streaming mode. +// Negative values are ignored, and the change is logged if debugging is enabled. +func WithColumnMax(width int) Option { + return func(target *Table) { + if width < 0 { + return + } + target.config.Widths.Global = width + if target.logger != nil { + target.logger.Debugf("Option: WithColumnMax applied to Table: %v", width) + } + } +} + +// WithMaxWidth sets a global maximum table width for the table. +// Negative values are ignored, and the change is logged if debugging is enabled. +func WithMaxWidth(width int) Option { + return func(target *Table) { + if width < 0 { + return + } + target.config.MaxWidth = width + if target.logger != nil { + target.logger.Debugf("Option: WithTableMax applied to Table: %v", width) + } + } +} + +// WithWidths sets per-column widths for the table. +// Negative widths are removed, and the change is logged if debugging is enabled. +func WithWidths(width tw.CellWidth) Option { + return func(target *Table) { + target.config.Widths = width + if target.logger != nil { + target.logger.Debugf("Option: WithColumnWidths applied to Table: %v", width) + } + } +} + +// WithColumnWidths sets per-column widths for the table. +// Negative widths are removed, and the change is logged if debugging is enabled. +func WithColumnWidths(widths tw.Mapper[int, int]) Option { + return func(target *Table) { + for k, v := range widths { + if v < 0 { + delete(widths, k) + } + } + target.config.Widths.PerColumn = widths + if target.logger != nil { + target.logger.Debugf("Option: WithColumnWidths applied to Table: %v", widths) + } + } +} + +// WithConfig applies a custom configuration to the table by merging it with the default configuration. +func WithConfig(cfg Config) Option { + return func(target *Table) { + target.config = mergeConfig(defaultConfig(), cfg) + } +} + +// WithDebug enables or disables debug logging and adjusts the logger level accordingly. +// Logs the change if debugging is enabled. +func WithDebug(debug bool) Option { + return func(target *Table) { + target.config.Debug = debug + } +} + +// WithFooter sets the table footers by calling the Footer method. +func WithFooter(footers []string) Option { + return func(target *Table) { + target.Footer(footers) + } +} + +// WithFooterConfig applies a full footer configuration to the table. +// Logs the change if debugging is enabled. +func WithFooterConfig(config tw.CellConfig) Option { + return func(target *Table) { + target.config.Footer = config + if target.logger != nil { + target.logger.Debug("Option: WithFooterConfig applied to Table.") + } + } +} + +// WithFooterAlignmentConfig applies a footer alignment configuration to the table. +// Logs the change if debugging is enabled. +func WithFooterAlignmentConfig(alignment tw.CellAlignment) Option { + return func(target *Table) { + target.config.Footer.Alignment = alignment + if target.logger != nil { + target.logger.Debugf("Option: WithFooterAlignmentConfig applied to Table: %+v", alignment) + } + } +} + +// WithFooterMergeMode sets the merge mode for footer cells. +// Invalid merge modes are ignored, and the change is logged if debugging is enabled. +func WithFooterMergeMode(mergeMode int) Option { + return func(target *Table) { + if mergeMode < tw.MergeNone || mergeMode > tw.MergeHierarchical { + return + } + target.config.Footer.Formatting.MergeMode = mergeMode + if target.logger != nil { + target.logger.Debugf("Option: WithFooterMergeMode applied to Table: %v", mergeMode) + } + } +} + +// WithFooterAutoWrap sets the wrapping behavior for footer cells. +// Invalid wrap modes are ignored, and the change is logged if debugging is enabled. +func WithFooterAutoWrap(wrap int) Option { + return func(target *Table) { + if wrap < tw.WrapNone || wrap > tw.WrapBreak { + return + } + target.config.Footer.Formatting.AutoWrap = wrap + if target.logger != nil { + target.logger.Debugf("Option: WithFooterAutoWrap applied to Table: %v", wrap) + } + } +} + +// WithFooterFilter sets the filter configuration for footer cells. +// Logs the change if debugging is enabled. +func WithFooterFilter(filter tw.CellFilter) Option { + return func(target *Table) { + target.config.Footer.Filter = filter + if target.logger != nil { + target.logger.Debug("Option: WithFooterFilter applied to Table.") + } + } +} + +// WithFooterCallbacks sets the callback configuration for footer cells. +// Logs the change if debugging is enabled. +func WithFooterCallbacks(callbacks tw.CellCallbacks) Option { + return func(target *Table) { + target.config.Footer.Callbacks = callbacks + if target.logger != nil { + target.logger.Debug("Option: WithFooterCallbacks applied to Table.") + } + } +} + +// WithFooterPaddingPerColumn sets per-column padding for footer cells. +// Logs the change if debugging is enabled. +func WithFooterPaddingPerColumn(padding []tw.Padding) Option { + return func(target *Table) { + target.config.Footer.Padding.PerColumn = padding + if target.logger != nil { + target.logger.Debugf("Option: WithFooterPaddingPerColumn applied to Table: %+v", padding) + } + } +} + +// WithFooterMaxWidth sets the maximum content width for footer cells. +// Negative values are ignored, and the change is logged if debugging is enabled. +func WithFooterMaxWidth(maxWidth int) Option { + return func(target *Table) { + if maxWidth < 0 { + return + } + target.config.Footer.ColMaxWidths.Global = maxWidth + if target.logger != nil { + target.logger.Debugf("Option: WithFooterMaxWidth applied to Table: %v", maxWidth) + } + } +} + +// WithHeader sets the table headers by calling the Header method. +func WithHeader(headers []string) Option { + return func(target *Table) { + target.Header(headers) + } +} + +// WithHeaderAlignment sets the text alignment for header cells. +// Invalid alignments are ignored, and the change is logged if debugging is enabled. +func WithHeaderAlignment(align tw.Align) Option { + return func(target *Table) { + if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone { + return + } + target.config.Header.Alignment.Global = align + if target.logger != nil { + target.logger.Debugf("Option: WithHeaderAlignment applied to Table: %v", align) + } + } +} + +// WithHeaderAutoWrap sets the wrapping behavior for header cells. +// Invalid wrap modes are ignored, and the change is logged if debugging is enabled. +func WithHeaderAutoWrap(wrap int) Option { + return func(target *Table) { + if wrap < tw.WrapNone || wrap > tw.WrapBreak { + return + } + target.config.Header.Formatting.AutoWrap = wrap + if target.logger != nil { + target.logger.Debugf("Option: WithHeaderAutoWrap applied to Table: %v", wrap) + } + } +} + +// WithHeaderMergeMode sets the merge mode for header cells. +// Invalid merge modes are ignored, and the change is logged if debugging is enabled. +func WithHeaderMergeMode(mergeMode int) Option { + return func(target *Table) { + if mergeMode < tw.MergeNone || mergeMode > tw.MergeHierarchical { + return + } + target.config.Header.Formatting.MergeMode = mergeMode + if target.logger != nil { + target.logger.Debugf("Option: WithHeaderMergeMode applied to Table: %v", mergeMode) + } + } +} + +// WithHeaderFilter sets the filter configuration for header cells. +// Logs the change if debugging is enabled. +func WithHeaderFilter(filter tw.CellFilter) Option { + return func(target *Table) { + target.config.Header.Filter = filter + if target.logger != nil { + target.logger.Debug("Option: WithHeaderFilter applied to Table.") + } + } +} + +// WithHeaderCallbacks sets the callback configuration for header cells. +// Logs the change if debugging is enabled. +func WithHeaderCallbacks(callbacks tw.CellCallbacks) Option { + return func(target *Table) { + target.config.Header.Callbacks = callbacks + if target.logger != nil { + target.logger.Debug("Option: WithHeaderCallbacks applied to Table.") + } + } +} + +// WithHeaderPaddingPerColumn sets per-column padding for header cells. +// Logs the change if debugging is enabled. +func WithHeaderPaddingPerColumn(padding []tw.Padding) Option { + return func(target *Table) { + target.config.Header.Padding.PerColumn = padding + if target.logger != nil { + target.logger.Debugf("Option: WithHeaderPaddingPerColumn applied to Table: %+v", padding) + } + } +} + +// WithHeaderMaxWidth sets the maximum content width for header cells. +// Negative values are ignored, and the change is logged if debugging is enabled. +func WithHeaderMaxWidth(maxWidth int) Option { + return func(target *Table) { + if maxWidth < 0 { + return + } + target.config.Header.ColMaxWidths.Global = maxWidth + if target.logger != nil { + target.logger.Debugf("Option: WithHeaderMaxWidth applied to Table: %v", maxWidth) + } + } +} + +// WithRowAlignment sets the text alignment for row cells. +// Invalid alignments are ignored, and the change is logged if debugging is enabled. +func WithRowAlignment(align tw.Align) Option { + return func(target *Table) { + if err := align.Validate(); err != nil { + return + } + target.config.Row.Alignment.Global = align + if target.logger != nil { + target.logger.Debugf("Option: WithRowAlignment applied to Table: %v", align) + } + } +} + +// WithRowAutoWrap sets the wrapping behavior for row cells. +// Invalid wrap modes are ignored, and the change is logged if debugging is enabled. +func WithRowAutoWrap(wrap int) Option { + return func(target *Table) { + if wrap < tw.WrapNone || wrap > tw.WrapBreak { + return + } + target.config.Row.Formatting.AutoWrap = wrap + if target.logger != nil { + target.logger.Debugf("Option: WithRowAutoWrap applied to Table: %v", wrap) + } + } +} + +// WithRowMergeMode sets the merge mode for row cells. +// Invalid merge modes are ignored, and the change is logged if debugging is enabled. +func WithRowMergeMode(mergeMode int) Option { + return func(target *Table) { + if mergeMode < tw.MergeNone || mergeMode > tw.MergeHierarchical { + return + } + target.config.Row.Formatting.MergeMode = mergeMode + if target.logger != nil { + target.logger.Debugf("Option: WithRowMergeMode applied to Table: %v", mergeMode) + } + } +} + +// WithRowFilter sets the filter configuration for row cells. +// Logs the change if debugging is enabled. +func WithRowFilter(filter tw.CellFilter) Option { + return func(target *Table) { + target.config.Row.Filter = filter + if target.logger != nil { + target.logger.Debug("Option: WithRowFilter applied to Table.") + } + } +} + +// WithRowCallbacks sets the callback configuration for row cells. +// Logs the change if debugging is enabled. +func WithRowCallbacks(callbacks tw.CellCallbacks) Option { + return func(target *Table) { + target.config.Row.Callbacks = callbacks + if target.logger != nil { + target.logger.Debug("Option: WithRowCallbacks applied to Table.") + } + } +} + +// WithRowPaddingPerColumn sets per-column padding for row cells. +// Logs the change if debugging is enabled. +func WithRowPaddingPerColumn(padding []tw.Padding) Option { + return func(target *Table) { + target.config.Row.Padding.PerColumn = padding + if target.logger != nil { + target.logger.Debugf("Option: WithRowPaddingPerColumn applied to Table: %+v", padding) + } + } +} + +// WithHeaderAlignmentConfig applies a header alignment configuration to the table. +// Logs the change if debugging is enabled. +func WithHeaderAlignmentConfig(alignment tw.CellAlignment) Option { + return func(target *Table) { + target.config.Header.Alignment = alignment + if target.logger != nil { + target.logger.Debugf("Option: WithHeaderAlignmentConfig applied to Table: %+v", alignment) + } + } +} + +// WithHeaderConfig applies a full header configuration to the table. +// Logs the change if debugging is enabled. +func WithHeaderConfig(config tw.CellConfig) Option { + return func(target *Table) { + target.config.Header = config + if target.logger != nil { + target.logger.Debug("Option: WithHeaderConfig applied to Table.") + } + } +} + +// WithLogger sets a custom logger for the table and updates the renderer if present. +// Logs the change if debugging is enabled. +func WithLogger(logger *ll.Logger) Option { + return func(target *Table) { + target.logger = logger + if target.logger != nil { + target.logger.Debug("Option: WithLogger applied to Table.") + if target.renderer != nil { + target.renderer.Logger(target.logger) + } + } + } +} + +// WithRenderer sets a custom renderer for the table and attaches the logger if present. +// Logs the change if debugging is enabled. +func WithRenderer(f tw.Renderer) Option { + return func(target *Table) { + target.renderer = f + if target.logger != nil { + target.logger.Debugf("Option: WithRenderer applied to Table: %T", f) + f.Logger(target.logger) + } + } +} + +// WithRowConfig applies a full row configuration to the table. +// Logs the change if debugging is enabled. +func WithRowConfig(config tw.CellConfig) Option { + return func(target *Table) { + target.config.Row = config + if target.logger != nil { + target.logger.Debug("Option: WithRowConfig applied to Table.") + } + } +} + +// WithRowAlignmentConfig applies a row alignment configuration to the table. +// Logs the change if debugging is enabled. +func WithRowAlignmentConfig(alignment tw.CellAlignment) Option { + return func(target *Table) { + target.config.Row.Alignment = alignment + if target.logger != nil { + target.logger.Debugf("Option: WithRowAlignmentConfig applied to Table: %+v", alignment) + } + } +} + +// WithRowMaxWidth sets the maximum content width for row cells. +// Negative values are ignored, and the change is logged if debugging is enabled. +func WithRowMaxWidth(maxWidth int) Option { + return func(target *Table) { + if maxWidth < 0 { + return + } + target.config.Row.ColMaxWidths.Global = maxWidth + if target.logger != nil { + target.logger.Debugf("Option: WithRowMaxWidth applied to Table: %v", maxWidth) + } + } +} + +// WithStreaming applies a streaming configuration to the table by merging it with the existing configuration. +// Logs the change if debugging is enabled. +func WithStreaming(c tw.StreamConfig) Option { + return func(target *Table) { + target.config.Stream = mergeStreamConfig(target.config.Stream, c) + if target.logger != nil { + target.logger.Debug("Option: WithStreaming applied to Table.") + } + } +} + +// WithStringer sets a custom stringer function for converting row data and clears the stringer cache. +// Logs the change if debugging is enabled. +func WithStringer(stringer interface{}) Option { + return func(t *Table) { + t.stringer = stringer + t.stringerCacheMu.Lock() + t.stringerCache = make(map[reflect.Type]reflect.Value) + t.stringerCacheMu.Unlock() + if t.logger != nil { + t.logger.Debug("Stringer updated, cache cleared") + } + } +} + +// WithStringerCache enables caching for the stringer function. +// Logs the change if debugging is enabled. +func WithStringerCache() Option { + return func(t *Table) { + t.stringerCacheEnabled = true + if t.logger != nil { + t.logger.Debug("Option: WithStringerCache enabled") + } + } +} + +// WithTrimSpace sets whether leading and trailing spaces are automatically trimmed. +// Logs the change if debugging is enabled. +func WithTrimSpace(state tw.State) Option { + return func(target *Table) { + target.config.Behavior.TrimSpace = state + if target.logger != nil { + target.logger.Debugf("Option: WithTrimSpace 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 { + return func(target *Table) { + target.config.Header.Formatting.AutoFormat = state + if target.logger != nil { + target.logger.Debugf("Option: WithHeaderAutoFormat applied to Table: %v", state) + } + } +} + +// WithFooterAutoFormat enables or disables automatic formatting for footer cells. +// Logs the change if debugging is enabled. +func WithFooterAutoFormat(state tw.State) Option { + return func(target *Table) { + target.config.Footer.Formatting.AutoFormat = state + if target.logger != nil { + target.logger.Debugf("Option: WithFooterAutoFormat applied to Table: %v", state) + } + } +} + +// WithRowAutoFormat enables or disables automatic formatting for row cells. +// Logs the change if debugging is enabled. +func WithRowAutoFormat(state tw.State) Option { + return func(target *Table) { + target.config.Row.Formatting.AutoFormat = state + if target.logger != nil { + target.logger.Debugf("Option: WithRowAutoFormat applied to Table: %v", state) + } + } +} + +// WithHeaderControl sets the control behavior for the table header. +// Logs the change if debugging is enabled. +func WithHeaderControl(control tw.Control) Option { + return func(target *Table) { + target.config.Behavior.Header = control + if target.logger != nil { + target.logger.Debugf("Option: WithHeaderControl applied to Table: %v", control) + } + } +} + +// WithFooterControl sets the control behavior for the table footer. +// Logs the change if debugging is enabled. +func WithFooterControl(control tw.Control) Option { + return func(target *Table) { + target.config.Behavior.Footer = control + if target.logger != nil { + target.logger.Debugf("Option: WithFooterControl applied to Table: %v", control) + } + } +} + +// WithAlignment sets the default column alignment for the header, rows, and footer. +// Logs the change if debugging is enabled. +func WithAlignment(alignment tw.Alignment) Option { + return func(target *Table) { + target.config.Header.Alignment.PerColumn = alignment + target.config.Row.Alignment.PerColumn = alignment + target.config.Footer.Alignment.PerColumn = alignment + if target.logger != nil { + target.logger.Debugf("Option: WithAlignment applied to Table: %+v", alignment) + } + } +} + +// WithBehavior applies a behavior configuration to the table. +// Logs the change if debugging is enabled. +func WithBehavior(behavior tw.Behavior) Option { + return func(target *Table) { + target.config.Behavior = behavior + if target.logger != nil { + target.logger.Debugf("Option: WithBehavior applied to Table: %+v", behavior) + } + } +} + +// WithPadding sets the global padding for the header, rows, and footer. +// Logs the change if debugging is enabled. +func WithPadding(padding tw.Padding) Option { + return func(target *Table) { + target.config.Header.Padding.Global = padding + target.config.Row.Padding.Global = padding + target.config.Footer.Padding.Global = padding + if target.logger != nil { + target.logger.Debugf("Option: WithPadding applied to Table: %+v", padding) + } + } +} + +// WithRendition allows updating the active renderer's rendition configuration +// by merging the provided rendition. +// If the renderer does not implement tw.Renditioning, a warning is logged. +// Logs the change if debugging is enabled. +func WithRendition(rendition tw.Rendition) Option { + return func(target *Table) { + if target.renderer == nil { + if target.logger != nil { + target.logger.Warn("Option: WithRendition: No renderer set on table.") + } + return + } + if ru, ok := target.renderer.(tw.Renditioning); ok { + ru.Rendition(rendition) + 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) + } + } + } +} + +// 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. . +func WithSymbols(symbols tw.Symbols) Option { + return func(target *Table) { + if target.renderer != nil { + cfg := target.renderer.Config() + cfg.Symbols = symbols + + if ru, ok := target.renderer.(tw.Renditioning); ok { + ru.Rendition(cfg) + 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) + } + } + } + } +} + +// defaultConfig returns a default Config with sensible settings for headers, rows, footers, and behavior. +func defaultConfig() Config { + return Config{ + MaxWidth: 0, + Header: tw.CellConfig{ + Formatting: tw.CellFormatting{ + AutoWrap: tw.WrapTruncate, + AutoFormat: tw.On, + MergeMode: tw.MergeNone, + }, + Padding: tw.CellPadding{ + Global: tw.PaddingDefault, + }, + Alignment: tw.CellAlignment{ + Global: tw.AlignCenter, + PerColumn: []tw.Align{}, + }, + }, + Row: tw.CellConfig{ + Formatting: tw.CellFormatting{ + AutoWrap: tw.WrapNormal, + AutoFormat: tw.Off, + MergeMode: tw.MergeNone, + }, + Padding: tw.CellPadding{ + Global: tw.PaddingDefault, + }, + Alignment: tw.CellAlignment{ + Global: tw.AlignLeft, + PerColumn: []tw.Align{}, + }, + }, + Footer: tw.CellConfig{ + Formatting: tw.CellFormatting{ + AutoWrap: tw.WrapNormal, + AutoFormat: tw.Off, + MergeMode: tw.MergeNone, + }, + Padding: tw.CellPadding{ + Global: tw.PaddingDefault, + }, + Alignment: tw.CellAlignment{ + Global: tw.AlignRight, + PerColumn: []tw.Align{}, + }, + }, + Stream: tw.StreamConfig{ + Enable: false, + StrictColumns: false, + }, + Debug: false, + Behavior: tw.Behavior{ + AutoHide: tw.Off, + TrimSpace: tw.On, + }, + } +} + +// mergeCellConfig merges a source CellConfig into a destination CellConfig, prioritizing non-default source values. +// It handles deep merging for complex fields like padding and callbacks. +func mergeCellConfig(dst, src tw.CellConfig) tw.CellConfig { + if src.Formatting.Alignment != tw.Empty { + dst.Formatting.Alignment = src.Formatting.Alignment + } + + if src.Formatting.AutoWrap != 0 { + dst.Formatting.AutoWrap = src.Formatting.AutoWrap + } + if src.ColMaxWidths.Global != 0 { + dst.ColMaxWidths.Global = src.ColMaxWidths.Global + } + if src.Formatting.MergeMode != 0 { + dst.Formatting.MergeMode = src.Formatting.MergeMode + } + + dst.Formatting.AutoFormat = src.Formatting.AutoFormat + + if src.Padding.Global.Paddable() { + dst.Padding.Global = src.Padding.Global + } + + if len(src.Padding.PerColumn) > 0 { + if dst.Padding.PerColumn == nil { + dst.Padding.PerColumn = make([]tw.Padding, len(src.Padding.PerColumn)) + } else if len(src.Padding.PerColumn) > len(dst.Padding.PerColumn) { + dst.Padding.PerColumn = append(dst.Padding.PerColumn, make([]tw.Padding, len(src.Padding.PerColumn)-len(dst.Padding.PerColumn))...) + } + for i, pad := range src.Padding.PerColumn { + if pad.Paddable() { + dst.Padding.PerColumn[i] = pad + } + } + } + if src.Callbacks.Global != nil { + dst.Callbacks.Global = src.Callbacks.Global + } + if len(src.Callbacks.PerColumn) > 0 { + if dst.Callbacks.PerColumn == nil { + dst.Callbacks.PerColumn = make([]func(), len(src.Callbacks.PerColumn)) + } else if len(src.Callbacks.PerColumn) > len(dst.Callbacks.PerColumn) { + dst.Callbacks.PerColumn = append(dst.Callbacks.PerColumn, make([]func(), len(src.Callbacks.PerColumn)-len(dst.Callbacks.PerColumn))...) + } + for i, cb := range src.Callbacks.PerColumn { + if cb != nil { + dst.Callbacks.PerColumn[i] = cb + } + } + } + if src.Filter.Global != nil { + dst.Filter.Global = src.Filter.Global + } + if len(src.Filter.PerColumn) > 0 { + if dst.Filter.PerColumn == nil { + dst.Filter.PerColumn = make([]func(string) string, len(src.Filter.PerColumn)) + } else if len(src.Filter.PerColumn) > len(dst.Filter.PerColumn) { + dst.Filter.PerColumn = append(dst.Filter.PerColumn, make([]func(string) string, len(src.Filter.PerColumn)-len(dst.Filter.PerColumn))...) + } + for i, filter := range src.Filter.PerColumn { + if filter != nil { + dst.Filter.PerColumn[i] = filter + } + } + } + + // Merge Alignment + if src.Alignment.Global != tw.Empty { + dst.Alignment.Global = src.Alignment.Global + } + + if len(src.Alignment.PerColumn) > 0 { + if dst.Alignment.PerColumn == nil { + dst.Alignment.PerColumn = make([]tw.Align, len(src.Alignment.PerColumn)) + } else if len(src.Alignment.PerColumn) > len(dst.Alignment.PerColumn) { + dst.Alignment.PerColumn = append(dst.Alignment.PerColumn, make([]tw.Align, len(src.Alignment.PerColumn)-len(dst.Alignment.PerColumn))...) + } + for i, align := range src.Alignment.PerColumn { + if align != tw.Skip { + dst.Alignment.PerColumn[i] = align + } + } + } + + if len(src.ColumnAligns) > 0 { + if dst.ColumnAligns == nil { + dst.ColumnAligns = make([]tw.Align, len(src.ColumnAligns)) + } else if len(src.ColumnAligns) > len(dst.ColumnAligns) { + dst.ColumnAligns = append(dst.ColumnAligns, make([]tw.Align, len(src.ColumnAligns)-len(dst.ColumnAligns))...) + } + for i, align := range src.ColumnAligns { + if align != tw.Skip { + dst.ColumnAligns[i] = align + } + } + } + + if len(src.ColMaxWidths.PerColumn) > 0 { + if dst.ColMaxWidths.PerColumn == nil { + dst.ColMaxWidths.PerColumn = make(map[int]int) + } + for k, v := range src.ColMaxWidths.PerColumn { + if v != 0 { + dst.ColMaxWidths.PerColumn[k] = v + } + } + } + return dst +} + +// mergeConfig merges a source Config into a destination Config, prioritizing non-default source values. +// It performs deep merging for complex types like Header, Row, Footer, and Stream. +func mergeConfig(dst, src Config) Config { + if src.MaxWidth != 0 { + dst.MaxWidth = src.MaxWidth + } + + dst.Debug = src.Debug || dst.Debug + dst.Behavior.AutoHide = src.Behavior.AutoHide + dst.Behavior.TrimSpace = src.Behavior.TrimSpace + dst.Behavior.Compact = src.Behavior.Compact + dst.Behavior.Header = src.Behavior.Header + dst.Behavior.Footer = src.Behavior.Footer + + if src.Widths.Global != 0 { + dst.Widths.Global = src.Widths.Global + } + if len(src.Widths.PerColumn) > 0 { + if dst.Widths.PerColumn == nil { + dst.Widths.PerColumn = make(map[int]int) + } + for k, v := range src.Widths.PerColumn { + if v != 0 { + dst.Widths.PerColumn[k] = v + } + } + } + + dst.Header = mergeCellConfig(dst.Header, src.Header) + dst.Row = mergeCellConfig(dst.Row, src.Row) + dst.Footer = mergeCellConfig(dst.Footer, src.Footer) + dst.Stream = mergeStreamConfig(dst.Stream, src.Stream) + + return dst +} + +// mergeStreamConfig merges a source StreamConfig into a destination StreamConfig, prioritizing non-default source values. +func mergeStreamConfig(dst, src tw.StreamConfig) tw.StreamConfig { + if src.Enable { + dst.Enable = true + } + + dst.StrictColumns = src.StrictColumns + return dst +} + +// padLine pads a line to the specified column count by appending empty strings as needed. +func padLine(line []string, numCols int) []string { + if len(line) >= numCols { + return line + } + padded := make([]string, numCols) + copy(padded, line) + for i := len(line); i < numCols; i++ { + padded[i] = tw.Empty + } + return padded +} diff --git a/vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go b/vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go index f32f0542..a577c1eb 100644 --- a/vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go +++ b/vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go @@ -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] diff --git a/vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go b/vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go new file mode 100644 index 00000000..d46ce4a8 --- /dev/null +++ b/vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go @@ -0,0 +1,321 @@ +package twwidth + +import ( + "bytes" + "github.com/mattn/go-runewidth" + "regexp" + "strings" + "sync" +) + +// 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 { + var regESC = "\x1b" // ASCII escape character + var regBEL = "\x07" // ASCII bell character + + // ANSI string terminator: either ESC+\ or BEL + var regST = "(" + regexp.QuoteMeta(regESC+"\\") + "|" + regexp.QuoteMeta(regBEL) + ")" + // Control Sequence Introducer (CSI): ESC[ followed by parameters and a final byte + var regCSI = regexp.QuoteMeta(regESC+"[") + "[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]" + // Operating System Command (OSC): ESC] followed by arbitrary content until a terminator + var 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] + if introducer == '[' { + if seqLen >= 3 && r >= 0x40 && r <= 0x7E { + terminated = true + } + } else if introducer == ']' { + 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 +} diff --git a/vendor/github.com/olekukonko/tablewriter/renderer/blueprint.go b/vendor/github.com/olekukonko/tablewriter/renderer/blueprint.go index 355b3af3..42966ebc 100644 --- a/vendor/github.com/olekukonko/tablewriter/renderer/blueprint.go +++ b/vendor/github.com/olekukonko/tablewriter/renderer/blueprint.go @@ -2,6 +2,7 @@ package renderer import ( "github.com/olekukonko/ll" + "github.com/olekukonko/tablewriter/pkg/twwidth" "io" "strings" @@ -42,7 +43,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 +107,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 +120,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 +134,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 +157,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 +173,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 +184,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 +196,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 +215,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 +233,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,7 +277,7 @@ 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 @@ -292,8 +293,8 @@ 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 @@ -304,8 +305,8 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al // 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) } @@ -363,10 +364,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 +375,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 +408,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 @@ -542,7 +543,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 +562,19 @@ 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 diff --git a/vendor/github.com/olekukonko/tablewriter/renderer/colorized.go b/vendor/github.com/olekukonko/tablewriter/renderer/colorized.go index 2f4a0863..5eaa1924 100644 --- a/vendor/github.com/olekukonko/tablewriter/renderer/colorized.go +++ b/vendor/github.com/olekukonko/tablewriter/renderer/colorized.go @@ -4,6 +4,7 @@ import ( "github.com/fatih/color" "github.com/olekukonko/ll" "github.com/olekukonko/ll/lh" + "github.com/olekukonko/tablewriter/pkg/twwidth" "io" "strings" @@ -254,7 +255,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 +267,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,7 +374,7 @@ 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 @@ -386,8 +387,8 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al } // 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 { @@ -396,8 +397,8 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al // 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) } @@ -472,12 +473,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 +487,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 +530,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 @@ -693,8 +694,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, diff --git a/vendor/github.com/olekukonko/tablewriter/renderer/html.go b/vendor/github.com/olekukonko/tablewriter/renderer/html.go index 71c0cf5f..62430a17 100644 --- a/vendor/github.com/olekukonko/tablewriter/renderer/html.go +++ b/vendor/github.com/olekukonko/tablewriter/renderer/html.go @@ -63,6 +63,7 @@ func NewHTML(configs ...HTMLConfig) *HTML { tableStarted: false, tbodyStarted: false, tfootStarted: false, + logger: ll.New("html"), } } diff --git a/vendor/github.com/olekukonko/tablewriter/renderer/markdown.go b/vendor/github.com/olekukonko/tablewriter/renderer/markdown.go index d9435f35..a565cc38 100644 --- a/vendor/github.com/olekukonko/tablewriter/renderer/markdown.go +++ b/vendor/github.com/olekukonko/tablewriter/renderer/markdown.go @@ -2,6 +2,7 @@ package renderer import ( "github.com/olekukonko/ll" + "github.com/olekukonko/tablewriter/pkg/twwidth" "io" "strings" @@ -35,7 +36,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. @@ -140,10 +141,10 @@ func (m *Markdown) resolveAlignment(ctx tw.Formatting) tw.Alignment { // build default alignment for i := 0; i < total; i++ { - m.alignment = append(m.alignment, tw.AlignLeft) + m.alignment = append(m.alignment, tw.AlignNone) // Default to AlignNone } - // add per colum alignment if it exits + // add per column alignment if it exists for i := 0; i < total; i++ { m.alignment[i] = ctx.Row.Current[i].Align } @@ -157,7 +158,7 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding //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,8 +171,8 @@ 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) @@ -212,7 +213,7 @@ 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) @@ -229,9 +230,9 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding 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)", @@ -255,17 +256,18 @@ func (m *Markdown) formatSeparator(width int, align tw.Align) string { sb.WriteRune(':') sb.WriteString(strings.Repeat("-", targetWidth-2)) sb.WriteRune(':') + case tw.AlignNone: + sb.WriteString(strings.Repeat("-", targetWidth)) default: - sb.WriteRune(':') - sb.WriteString(strings.Repeat("-", targetWidth-1)) + sb.WriteString(strings.Repeat("-", targetWidth)) // Fallback } 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) @@ -313,7 +315,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] @@ -321,12 +323,11 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader defaultPadding := tw.Padding{Left: tw.Space, Right: tw.Space} if !ok { - cellCtx = tw.CellContext{ Data: tw.Empty, Align: align, Padding: defaultPadding, Width: ctx.Row.Widths.Get(colIndex), Merge: tw.MergeState{}, } - } else if cellCtx.Padding == (tw.Padding{}) { + } else if !cellCtx.Padding.Paddable() { cellCtx.Padding = defaultPadding } @@ -339,18 +340,6 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader // Calculate width and span span := 1 - - if align == tw.AlignNone || align == tw.Empty { - if ctx.Row.Position == tw.Header && !isHeaderSep { - align = tw.AlignCenter - } else if ctx.Row.Position == tw.Footer { - align = tw.AlignRight - } else { - align = tw.AlignLeft - } - m.logger.Debugf("renderMarkdownLine: Col %d using default align '%s'", colIndex, align) - } - visualWidth := 0 isHMergeStart := ok && cellCtx.Merge.Horizontal.Present && cellCtx.Merge.Horizontal.Start if isHMergeStart { @@ -383,10 +372,11 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader var formattedSegment string if isHeaderSep { // Use header's alignment from ctx.Row.Previous - headerAlign := tw.AlignCenter // Default for headers + headerAlign := align if headerCellCtx, headerOK := ctx.Row.Previous[colIndex]; headerOK { headerAlign = headerCellCtx.Align - if headerAlign == tw.AlignNone || headerAlign == tw.Empty { + // Preserve tw.AlignNone for separator + if headerAlign != tw.AlignNone && (headerAlign == tw.Empty || headerAlign == tw.Skip) { headerAlign = tw.AlignCenter } } @@ -403,6 +393,16 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader rowAlign = headerCellCtx.Align } } + if rowAlign == tw.AlignNone || rowAlign == tw.Empty { + if ctx.Row.Position == tw.Header { + rowAlign = tw.AlignCenter + } else if ctx.Row.Position == tw.Footer { + rowAlign = tw.AlignRight + } else { + rowAlign = tw.AlignLeft + } + m.logger.Debugf("renderMarkdownLine: Col %d using default align '%s'", colIndex, rowAlign) + } formattedSegment = m.formatCell(content, visualWidth, rowAlign, cellCtx.Padding) } output.WriteString(formattedSegment) diff --git a/vendor/github.com/olekukonko/tablewriter/renderer/ocean.go b/vendor/github.com/olekukonko/tablewriter/renderer/ocean.go index 12f9d45c..7a943e9f 100644 --- a/vendor/github.com/olekukonko/tablewriter/renderer/ocean.go +++ b/vendor/github.com/olekukonko/tablewriter/renderer/ocean.go @@ -1,6 +1,7 @@ package renderer import ( + "github.com/olekukonko/tablewriter/pkg/twwidth" "io" "strings" @@ -106,17 +107,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 +126,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 +135,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 +152,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 +170,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 +233,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 +267,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 } @@ -340,7 +305,7 @@ func (o *Ocean) renderContentLine(ctx tw.Formatting, lineData []string) { if cellCtx.Align.Validate() == nil && cellCtx.Align != tw.AlignNone { align = cellCtx.Align } - if cellCtx.Padding != (tw.Padding{}) { + if cellCtx.Padding.Paddable() { padding = cellCtx.Padding } } else if colIdx < len(lineData) { @@ -374,7 +339,7 @@ func (o *Ocean) renderContentLine(ctx tw.Formatting, lineData []string) { } 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 +393,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,8 +404,8 @@ 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 { @@ -448,8 +413,8 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t } if contentDisplayWidth > spaceForContentAndAlignment { - content = tw.TruncateString(content, spaceForContentAndAlignment) - contentDisplayWidth = tw.DisplayWidth(content) + content = twwidth.Truncate(content, spaceForContentAndAlignment) + contentDisplayWidth = twwidth.Width(content) } remainingSpace := spaceForContentAndAlignment - contentDisplayWidth @@ -477,7 +442,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 +455,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() diff --git a/vendor/github.com/olekukonko/tablewriter/renderer/svg.go b/vendor/github.com/olekukonko/tablewriter/renderer/svg.go index 013e8b8b..f49e2d77 100644 --- a/vendor/github.com/olekukonko/tablewriter/renderer/svg.go +++ b/vendor/github.com/olekukonko/tablewriter/renderer/svg.go @@ -138,6 +138,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) diff --git a/vendor/github.com/olekukonko/tablewriter/stream.go b/vendor/github.com/olekukonko/tablewriter/stream.go index a990c6e0..7467e9cb 100644 --- a/vendor/github.com/olekukonko/tablewriter/stream.go +++ b/vendor/github.com/olekukonko/tablewriter/stream.go @@ -3,6 +3,7 @@ package tablewriter import ( "fmt" "github.com/olekukonko/errors" + "github.com/olekukonko/tablewriter/pkg/twwidth" "github.com/olekukonko/tablewriter/tw" "math" ) @@ -113,10 +114,10 @@ func (t *Table) Start() error { // Calculate initial fixed widths if provided in StreamConfig.Widths // These widths will be used for all subsequent rendering in streaming mode. - if t.config.Stream.Widths.PerColumn != nil && t.config.Stream.Widths.PerColumn.Len() > 0 { + if t.config.Widths.PerColumn != nil && t.config.Widths.PerColumn.Len() > 0 { // Use per-column stream widths if set - t.logger.Debugf("Using per-column stream widths from StreamConfig: %v", t.config.Stream.Widths.PerColumn) - t.streamWidths = t.config.Stream.Widths.PerColumn.Clone() + t.logger.Debugf("Using per-column stream widths from StreamConfig: %v", t.config.Widths.PerColumn) + 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) { @@ -139,14 +140,14 @@ func (t *Table) Start() error { t.logger.Debugf("PerColumn widths map is effectively empty or contains only negative values, streamNumCols = 0.") } - } else if t.config.Stream.Widths.Global > 0 { + } else if t.config.Widths.Global > 0 { // Global width is set, but we don't know the number of columns yet. // Defer applying global width until the first data (Header or first Row) arrives. // Store a placeholder or flag indicating global width should be used. // The simple way for now: Keep streamWidths empty, signal the global width preference. // The width calculation function called later will need to check StreamConfig.Widths.Global // if streamWidths is empty. - t.logger.Debugf("Global stream width %d set in StreamConfig. Will derive numCols from first data.", t.config.Stream.Widths.Global) + t.logger.Debugf("Global stream width %d set in StreamConfig. Will derive numCols from first data.", t.config.Widths.Global) t.streamWidths = tw.NewMapper[int, int]() // Initialize as empty, will be populated later // Note: No need to store Global width value here, it's available in t.config.Stream.Widths.Global @@ -220,21 +221,34 @@ func (t *Table) streamAppendRow(row interface{}) error { } if err := t.ensureStreamWidthsCalculated(rawCellsSlice, t.config.Row); err != nil { - return err + return fmt.Errorf("failed to establish stream column count/widths: %w", 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 { @@ -451,26 +465,26 @@ func (t *Table) streamBuildCellContexts( // The paddingConfig should be the CellPadding config relevant to the sample data (Header/Row/Footer). // Returns the determined number of columns. // This function should only be called when t.streamWidths is currently empty. -func (t *Table) streamCalculateWidths(sampleDataLines []string, sectionConfigForSampleData tw.CellConfig) int { +func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) int { if t.streamWidths != nil && t.streamWidths.Len() > 0 { t.logger.Debug("streamCalculateWidths: Called when streaming widths are already set (%d columns). Reusing existing.", t.streamNumCols) return t.streamNumCols } - t.logger.Debug("streamCalculateWidths: Calculating streaming widths. Sample data cells: %d. Using section config: %+v", len(sampleDataLines), sectionConfigForSampleData.Formatting) + t.logger.Debug("streamCalculateWidths: Calculating streaming widths. Sample data cells: %d. Using section config: %+v", len(sampling), config.Formatting) determinedNumCols := 0 - if t.config.Stream.Widths.PerColumn != nil && t.config.Stream.Widths.PerColumn.Len() > 0 { + if t.config.Widths.PerColumn != nil && t.config.Widths.PerColumn.Len() > 0 { maxColIdx := -1 - t.config.Stream.Widths.PerColumn.Each(func(col int, width int) { + t.config.Widths.PerColumn.Each(func(col int, width int) { if col > maxColIdx { maxColIdx = col } }) determinedNumCols = maxColIdx + 1 t.logger.Debug("streamCalculateWidths: Determined numCols (%d) from StreamConfig.Widths.PerColumn", determinedNumCols) - } else if len(sampleDataLines) > 0 { - determinedNumCols = len(sampleDataLines) + } else if len(sampling) > 0 { + determinedNumCols = len(sampling) t.logger.Debug("streamCalculateWidths: Determined numCols (%d) from sample data length", determinedNumCols) } else { t.logger.Debug("streamCalculateWidths: Cannot determine numCols (no PerColumn config, no sample data)") @@ -482,14 +496,14 @@ func (t *Table) streamCalculateWidths(sampleDataLines []string, sectionConfigFor t.streamNumCols = determinedNumCols t.streamWidths = tw.NewMapper[int, int]() - // Use padding and autowrap from the provided sectionConfigForSampleData - paddingForWidthCalc := sectionConfigForSampleData.Padding - autoWrapForWidthCalc := sectionConfigForSampleData.Formatting.AutoWrap + // Use padding and autowrap from the provided config + paddingForWidthCalc := config.Padding + autoWrapForWidthCalc := config.Formatting.AutoWrap - if t.config.Stream.Widths.PerColumn != nil && t.config.Stream.Widths.PerColumn.Len() > 0 { + if t.config.Widths.PerColumn != nil && t.config.Widths.PerColumn.Len() > 0 { t.logger.Debug("streamCalculateWidths: Using widths from StreamConfig.Widths.PerColumn") for i := 0; i < t.streamNumCols; i++ { - width, ok := t.config.Stream.Widths.PerColumn.OK(i) + width, ok := t.config.Widths.PerColumn.OK(i) if !ok { width = 0 } @@ -501,17 +515,17 @@ func (t *Table) streamCalculateWidths(sampleDataLines []string, sectionConfigFor t.streamWidths.Set(i, width) } } else { - // No PerColumn config, derive from sampleDataLines intelligently + // No PerColumn config, derive from sampling intelligently t.logger.Debug("streamCalculateWidths: Intelligently deriving widths from sample data content and padding.") tempRequiredWidths := tw.NewMapper[int, int]() // Widths from updateWidths (content + padding) - if len(sampleDataLines) > 0 { + if len(sampling) > 0 { // updateWidths calculates: DisplayWidth(content) + padLeft + padRight - t.updateWidths(sampleDataLines, tempRequiredWidths, paddingForWidthCalc) + t.updateWidths(sampling, tempRequiredWidths, paddingForWidthCalc) } ellipsisWidthBuffer := 0 if autoWrapForWidthCalc == tw.WrapTruncate { - ellipsisWidthBuffer = tw.DisplayWidth(tw.CharEllipsis) + ellipsisWidthBuffer = twwidth.Width(tw.CharEllipsis) } varianceBuffer := 2 // Your suggested variance minTotalColWidth := tw.MinimumColumnWidth @@ -522,17 +536,17 @@ func (t *Table) streamCalculateWidths(sampleDataLines []string, sectionConfigFor // We need to deconstruct it to apply logic to content_width first. sampleContent := "" - if i < len(sampleDataLines) { - sampleContent = t.Trimmer(sampleDataLines[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] != (tw.Padding{}) { + 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 @@ -584,8 +598,8 @@ func (t *Table) streamCalculateWidths(sampleDataLines []string, sectionConfigFor } // Apply Global Constraint (if t.config.Stream.Widths.Global > 0) - if t.config.Stream.Widths.Global > 0 && t.streamNumCols > 0 { - t.logger.Debug("streamCalculateWidths: Applying global stream width constraint %d", t.config.Stream.Widths.Global) + 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) { currentTotalColumnWidthsSum += w @@ -595,7 +609,7 @@ func (t *Table) streamCalculateWidths(sampleDataLines []string, sectionConfigFor 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 @@ -606,11 +620,11 @@ func (t *Table) streamCalculateWidths(sampleDataLines []string, sectionConfigFor totalWidthIncludingSeparators += (t.streamNumCols - 1) * separatorWidth } - if t.config.Stream.Widths.Global < totalWidthIncludingSeparators && totalWidthIncludingSeparators > 0 { // Added check for total > 0 - t.logger.Debug("streamCalculateWidths: Total calculated width (%d incl separators) exceeds global stream width (%d). Shrinking.", totalWidthIncludingSeparators, t.config.Stream.Widths.Global) + if t.config.Widths.Global < totalWidthIncludingSeparators && totalWidthIncludingSeparators > 0 { // Added check for total > 0 + t.logger.Debug("streamCalculateWidths: Total calculated width (%d incl separators) exceeds global stream width (%d). Shrinking.", totalWidthIncludingSeparators, t.config.Widths.Global) // Target sum for column widths only (global limit - total separator width) - targetSumForColumnWidths := t.config.Stream.Widths.Global + targetSumForColumnWidths := t.config.Widths.Global if t.streamNumCols > 1 { targetSumForColumnWidths -= (t.streamNumCols - 1) * separatorWidth } @@ -671,7 +685,7 @@ func (t *Table) streamCalculateWidths(sampleDataLines []string, sectionConfigFor } t.logger.Debug("streamCalculateWidths: Widths after scaling and distribution: %v", t.streamWidths) } else { - t.logger.Debug("streamCalculateWidths: Total calculated width (%d) fits global stream width (%d). No scaling needed.", totalWidthIncludingSeparators, t.config.Stream.Widths.Global) + t.logger.Debug("streamCalculateWidths: Total calculated width (%d) fits global stream width (%d). No scaling needed.", totalWidthIncludingSeparators, t.config.Widths.Global) } } diff --git a/vendor/github.com/olekukonko/tablewriter/tablewriter.go b/vendor/github.com/olekukonko/tablewriter/tablewriter.go index 16961d89..4aed1a64 100644 --- a/vendor/github.com/olekukonko/tablewriter/tablewriter.go +++ b/vendor/github.com/olekukonko/tablewriter/tablewriter.go @@ -7,11 +7,14 @@ import ( "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" + "os" "reflect" + "runtime" "strings" "sync" ) @@ -133,6 +136,16 @@ func NewTable(w io.Writer, opts ...Option) *Table { return t } +// NewWriter creates a new table with default settings for backward compatibility. +// It logs the creation if debugging is enabled. +func NewWriter(w io.Writer) *Table { + t := NewTable(w) + if t.logger != nil { + t.logger.Debug("NewWriter created buffered Table") + } + return t +} + // Caption sets the table caption (legacy method). // Defaults to BottomCenter alignment, wrapping to table width. // Use SetCaptionOptions for more control. @@ -397,6 +410,15 @@ func (t *Table) Options(opts ...Option) *Table { t.logger.Suspend() } + // 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 t.renderer.Logger(t.logger) @@ -508,19 +530,40 @@ func (t *Table) appendSingle(row interface{}) error { // Parameter config provides alignment settings for the section. // Returns a map of column indices to alignment settings. func (t *Table) buildAligns(config tw.CellConfig) map[int]tw.Align { - t.logger.Debugf("buildAligns INPUT: config.Formatting.Align=%s, config.ColumnAligns=%v", config.Formatting.Alignment, config.ColumnAligns) + // Start with global alignment, preferring deprecated Formatting.Alignment + effectiveGlobalAlign := config.Formatting.Alignment + if effectiveGlobalAlign == tw.Empty || effectiveGlobalAlign == tw.Skip { + effectiveGlobalAlign = config.Alignment.Global + if config.Formatting.Alignment != tw.Empty && config.Formatting.Alignment != tw.Skip { + t.logger.Warnf("Using deprecated CellFormatting.Alignment (%s). Migrate to CellConfig.Alignment.Global.", config.Formatting.Alignment) + } + } + + // Use per-column alignments, preferring deprecated ColumnAligns + effectivePerColumn := config.ColumnAligns + if len(effectivePerColumn) == 0 && len(config.Alignment.PerColumn) > 0 { + effectivePerColumn = make([]tw.Align, len(config.Alignment.PerColumn)) + copy(effectivePerColumn, config.Alignment.PerColumn) + if len(config.ColumnAligns) > 0 { + t.logger.Warnf("Using deprecated CellConfig.ColumnAligns (%v). Migrate to CellConfig.Alignment.PerColumn.", config.ColumnAligns) + } + } + + // Log input for debugging + t.logger.Debugf("buildAligns INPUT: deprecated Formatting.Alignment=%s, deprecated ColumnAligns=%v, config.Alignment.Global=%s, config.Alignment.PerColumn=%v", + config.Formatting.Alignment, config.ColumnAligns, config.Alignment.Global, config.Alignment.PerColumn) + numColsToUse := t.getNumColsToUse() colAlignsResult := make(map[int]tw.Align) for i := 0; i < numColsToUse; i++ { - currentAlign := config.Formatting.Alignment - if i < len(config.ColumnAligns) { - colSpecificAlign := config.ColumnAligns[i] - if colSpecificAlign != tw.Empty && colSpecificAlign != tw.Skip { - currentAlign = colSpecificAlign - } + currentAlign := effectiveGlobalAlign + if i < len(effectivePerColumn) && effectivePerColumn[i] != tw.Empty && effectivePerColumn[i] != tw.Skip { + currentAlign = effectivePerColumn[i] } + // Skip validation here; rely on rendering to handle invalid alignments colAlignsResult[i] = currentAlign } + t.logger.Debugf("Aligns built: %v (length %d)", colAlignsResult, len(colAlignsResult)) return colAlignsResult } @@ -532,7 +575,7 @@ func (t *Table) buildPadding(padding tw.CellPadding) map[int]tw.Padding { numColsToUse := t.getNumColsToUse() colPadding := make(map[int]tw.Padding) for i := 0; i < numColsToUse; i++ { - if i < len(padding.PerColumn) && padding.PerColumn[i] != (tw.Padding{}) { + if i < len(padding.PerColumn) && padding.PerColumn[i].Paddable() { colPadding[i] = padding.PerColumn[i] } else { colPadding[i] = padding.Global @@ -599,7 +642,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 } @@ -699,8 +742,12 @@ func (t *Table) maxColumns() int { return m } +// printTopBottomCaption prints the table's caption at the specified top or bottom position. +// It wraps the caption text to fit the table width or a user-defined width, aligns it according +// to the specified alignment, and writes it to the provided writer. If the caption text is empty +// or the spot is invalid, it logs the issue and returns without printing. The function handles +// wrapping errors by falling back to splitting on newlines or using the original text. func (t *Table) printTopBottomCaption(w io.Writer, actualTableWidth int) { - // Log the state of t.caption t.logger.Debugf("[printCaption Entry] Text=%q, Spot=%v (type %T), Align=%q, UserWidth=%d, ActualTableWidth=%d", t.caption.Text, t.caption.Spot, t.caption.Spot, t.caption.Align, t.caption.Width, actualTableWidth) @@ -711,27 +758,24 @@ func (t *Table) printTopBottomCaption(w io.Writer, actualTableWidth int) { return } - // Determine captionWrapWidth var captionWrapWidth int if t.caption.Width > 0 { captionWrapWidth = t.caption.Width t.logger.Debugf("[printCaption] Using user-defined caption.Width %d for wrapping.", captionWrapWidth) - } else if actualTableWidth <= 4 { // Empty or minimal table - captionWrapWidth = tw.DisplayWidth(t.caption.Text) + } else if actualTableWidth <= 4 { + 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 t.logger.Debugf("[printCaption] Non-empty table, no user caption.Width: Using actualTableWidth %d for wrapping.", actualTableWidth) } - // Ensure captionWrapWidth is positive if captionWrapWidth <= 0 { - captionWrapWidth = 10 // Minimum sensible width + captionWrapWidth = 10 t.logger.Warnf("[printCaption] captionWrapWidth was %d (<=0). Setting to minimum %d.", captionWrapWidth, 10) } t.logger.Debugf("[printCaption] Final captionWrapWidth to be used by twwarp: %d", captionWrapWidth) - // Wrap the caption text wrappedCaptionLines, count := twwarp.WrapString(t.caption.Text, captionWrapWidth) if count == 0 { t.logger.Errorf("[printCaption] Error from twwarp.WrapString (width %d): %v. Text: %q", captionWrapWidth, count, t.caption.Text) @@ -754,7 +798,6 @@ func (t *Table) printTopBottomCaption(w io.Writer, actualTableWidth int) { t.logger.Debugf("[printCaption] Wrapped caption into %d lines: %v", len(wrappedCaptionLines), wrappedCaptionLines) } - // Determine padding target width paddingTargetWidth := actualTableWidth if t.caption.Width > 0 { paddingTargetWidth = t.caption.Width @@ -763,7 +806,6 @@ func (t *Table) printTopBottomCaption(w io.Writer, actualTableWidth int) { } t.logger.Debugf("[printCaption] Final paddingTargetWidth for tw.Pad: %d", paddingTargetWidth) - // Print each wrapped line for i, line := range wrappedCaptionLines { align := t.caption.Align if align == "" || align == tw.AlignDefault || align == tw.AlignNone { @@ -791,22 +833,11 @@ func (t *Table) printTopBottomCaption(w io.Writer, actualTableWidth int) { // Parameters include cells to process and config for formatting rules. // Returns a slice of string slices representing processed lines. func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string { - // force max width - isStreaming := t.config.Stream.Enable && t.hasPrinted t.logger.Debugf("prepareContent: Processing cells=%v (streaming: %v)", cells, isStreaming) initialInputCellCount := len(cells) result := make([][]string, 0) - // ll.Dbg(t.config.MaxWidth) - // force max width - if t.config.MaxWidth > 0 { - // it has headers - if len(cells) > 0 { - config.ColMaxWidths.Global = int(math.Floor(float64(t.config.MaxWidth) / float64(len(cells)))) - } - } - effectiveNumCols := initialInputCellCount if isStreaming { if t.streamNumCols > 0 { @@ -830,6 +861,16 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string } } + if t.config.MaxWidth > 0 && !t.config.Widths.Constrained() { + if effectiveNumCols > 0 { + derivedSectionGlobalMaxWidth := int(math.Floor(float64(t.config.MaxWidth) / float64(effectiveNumCols))) + config.ColMaxWidths.Global = derivedSectionGlobalMaxWidth + t.logger.Debugf("prepareContent: Table MaxWidth %d active and t.config.Widths not constrained. "+ + "Derived section ColMaxWidths.Global: %d for %d columns. This will be used by calculateContentMaxWidth if no higher priority constraints exist.", + t.config.MaxWidth, config.ColMaxWidths.Global, effectiveNumCols) + } + } + for i := 0; i < effectiveNumCols; i++ { cellContent := "" if i < len(cells) { @@ -841,12 +882,12 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string cellContent = t.Trimmer(cellContent) colPad := config.Padding.Global - if i < len(config.Padding.PerColumn) && config.Padding.PerColumn[i] != (tw.Padding{}) { + if i < len(config.Padding.PerColumn) && config.Padding.PerColumn[i].Paddable() { 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) @@ -868,12 +909,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) @@ -881,56 +922,51 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string case tw.WrapBreak: wrapped := make([]string, 0) currentLine := line - for tw.DisplayWidth(currentLine) > effectiveContentMaxWidth { - breakPoint := tw.BreakPoint(currentLine, effectiveContentMaxWidth) - if breakPoint <= 0 { - t.logger.Warnf("prepareContent: WrapBreak - BreakPoint <= 0 for line '%s' at width %d. Attempting manual break.", currentLine, effectiveContentMaxWidth) - runes := []rune(currentLine) + breakCharWidth := twwidth.Width(tw.CharBreak) + for twwidth.Width(currentLine) > effectiveContentMaxWidth { + targetWidth := effectiveContentMaxWidth - breakCharWidth + if targetWidth < 0 { + targetWidth = 0 + } + breakPoint := tw.BreakPoint(currentLine, targetWidth) + runes := []rune(currentLine) + if breakPoint <= 0 || breakPoint > len(runes) { + t.logger.Warnf("prepareContent: WrapBreak - Invalid BreakPoint %d for line '%s' at width %d. Attempting manual break.", breakPoint, currentLine, targetWidth) actualBreakRuneCount := 0 tempWidth := 0 - for charIdx, r := range currentLine { + for charIdx, r := range runes { runeStr := string(r) - rw := tw.DisplayWidth(runeStr) - if tempWidth+rw > effectiveContentMaxWidth && charIdx > 0 { + rw := twwidth.Width(runeStr) + if tempWidth+rw > targetWidth && charIdx > 0 { break } tempWidth += rw actualBreakRuneCount = charIdx + 1 - if tempWidth >= effectiveContentMaxWidth && charIdx == 0 { + if tempWidth >= targetWidth && charIdx == 0 { break } } if actualBreakRuneCount == 0 && len(runes) > 0 { actualBreakRuneCount = 1 } - if actualBreakRuneCount > 0 && actualBreakRuneCount <= len(runes) { wrapped = append(wrapped, string(runes[:actualBreakRuneCount])+tw.CharBreak) currentLine = string(runes[actualBreakRuneCount:]) } else { - if tw.DisplayWidth(currentLine) > 0 { - wrapped = append(wrapped, currentLine) - currentLine = "" - } - break - } - } else { - runes := []rune(currentLine) - if breakPoint <= len(runes) { - wrapped = append(wrapped, string(runes[:breakPoint])+tw.CharBreak) - currentLine = string(runes[breakPoint:]) - } else { - t.logger.Warnf("prepareContent: WrapBreak - BreakPoint (%d) out of bounds for line runes (%d). Adding full line.", breakPoint, len(runes)) + t.logger.Warnf("prepareContent: WrapBreak - Cannot break line '%s'. Adding as is.", currentLine) wrapped = append(wrapped, currentLine) currentLine = "" break } + } else { + wrapped = append(wrapped, string(runes[:breakPoint])+tw.CharBreak) + 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...) @@ -959,7 +995,8 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string if i < len(result[j]) { result[j][i] = cellLineContent } else { - t.logger.Warnf("prepareContent: Column index %d out of bounds (%d) during result matrix population.", i, len(result[j])) + t.logger.Warnf("prepareContent: Column index %d out of bounds (%d) during result matrix population. EffectiveNumCols: %d. This indicates a logic error.", + i, len(result[j]), effectiveNumCols) } } } @@ -1304,6 +1341,7 @@ 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() if t.config.Stream.Enable { @@ -1339,9 +1377,8 @@ func (t *Table) render() error { t.logger.Debugf("No caption detected. Rendering table core directly to writer.") } - //Render Table Core --- - t.writer = targetWriter // Set writer only when necessary - + //Render Table Core + t.writer = targetWriter ctx, mctx, err := t.prepareContexts() if err != nil { t.writer = originalWriter @@ -1387,7 +1424,6 @@ func (t *Table) render() error { if renderError { return firstRenderErr // Return error from core rendering if any } - //End Render Table Core --- //Caption Handling & Final Output --- if isTopOrBottomCaption { @@ -1412,7 +1448,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 } @@ -1684,7 +1720,7 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error { if j == 0 || representativePadChar == " " { representativePadChar = padChar } - padWidth := tw.DisplayWidth(padChar) + padWidth := twwidth.Width(padChar) if padWidth < 1 { padWidth = 1 } @@ -1699,12 +1735,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 = "" @@ -1877,17 +1913,54 @@ func (t *Table) renderHeader(ctx *renderContext, mctx *mergeContext) error { if cfg.Settings.Lines.ShowHeaderLine.Enabled() && (len(ctx.rowLines) > 0 || len(ctx.footerLines) > 0) { ctx.logger.Debug("Rendering header separator line") - hctx.rowIdx = 0 - hctx.lineIdx = len(ctx.headerLines) - 1 - hctx.line = padLine(ctx.headerLines[hctx.lineIdx], ctx.numCols) - hctx.location = tw.LocationMiddle resp := t.buildCellContexts(ctx, mctx, hctx, colAligns, colPadding) + + var nextSectionCells map[int]tw.CellContext + var nextSectionWidths tw.Mapper[int, int] + + if len(ctx.rowLines) > 0 { + nextSectionWidths = ctx.widths[tw.Row] + rowColAligns := t.buildAligns(t.config.Row) + rowColPadding := t.buildPadding(t.config.Row.Padding) + firstRowHctx := &helperContext{ + position: tw.Row, + rowIdx: 0, + lineIdx: 0, + } + if len(ctx.rowLines[0]) > 0 { + firstRowHctx.line = padLine(ctx.rowLines[0][0], ctx.numCols) + } else { + firstRowHctx.line = make([]string, ctx.numCols) + } + firstRowResp := t.buildCellContexts(ctx, mctx, firstRowHctx, rowColAligns, rowColPadding) + nextSectionCells = firstRowResp.cells + } else if len(ctx.footerLines) > 0 { + nextSectionWidths = ctx.widths[tw.Row] + footerColAligns := t.buildAligns(t.config.Footer) + footerColPadding := t.buildPadding(t.config.Footer.Padding) + firstFooterHctx := &helperContext{ + position: tw.Footer, + rowIdx: 0, + lineIdx: 0, + } + if len(ctx.footerLines) > 0 { + firstFooterHctx.line = padLine(ctx.footerLines[0], ctx.numCols) + } else { + firstFooterHctx.line = make([]string, ctx.numCols) + } + firstFooterResp := t.buildCellContexts(ctx, mctx, firstFooterHctx, footerColAligns, footerColPadding) + nextSectionCells = firstFooterResp.cells + } else { + nextSectionWidths = ctx.widths[tw.Header] + nextSectionCells = nil + } + f.Line(tw.Formatting{ Row: tw.RowContext{ - Widths: ctx.widths[tw.Header], + Widths: nextSectionWidths, Current: resp.cells, Previous: resp.prevCells, - Next: resp.nextCells, + Next: nextSectionCells, Position: tw.Header, Location: tw.LocationMiddle, }, diff --git a/vendor/github.com/olekukonko/tablewriter/tw/cell.go b/vendor/github.com/olekukonko/tablewriter/tw/cell.go index de2fa005..0547f01a 100644 --- a/vendor/github.com/olekukonko/tablewriter/tw/cell.go +++ b/vendor/github.com/olekukonko/tablewriter/tw/cell.go @@ -2,13 +2,17 @@ package tw // CellFormatting holds formatting options for table cells. type CellFormatting struct { - Alignment Align // Text alignment within the cell (e.g., Left, Right, Center) - AutoWrap int // Wrapping behavior (e.g., WrapTruncate, WrapNormal) - MergeMode int // Bitmask for merge behavior (e.g., MergeHorizontal, MergeVertical) + AutoWrap int // Wrapping behavior (e.g., WrapTruncate, WrapNormal) + MergeMode int // Bitmask for merge behavior (e.g., MergeHorizontal, MergeVertical) // Changed form bool to State // See https://github.com/olekukonko/tablewriter/issues/261 AutoFormat State // Enables automatic formatting (e.g., title case for headers) + + // 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. @@ -30,17 +34,31 @@ type CellCallbacks struct { PerColumn []func() // Column-specific callbacks } +// CellAlignment defines alignment settings for table cells. +type CellAlignment struct { + Global Align // Default alignment applied to all cells + PerColumn []Align // Column-specific alignment overrides +} + // CellConfig combines formatting, padding, and callback settings for a table section. type CellConfig struct { Formatting CellFormatting // Cell formatting options Padding CellPadding // Padding configuration Callbacks CellCallbacks // Callback functions (unused) Filter CellFilter // Function to filter cell content (renamed from Filter Filter) - ColumnAligns []Align // Per-column alignment overrides + Alignment CellAlignment // Alignment configuration for cells ColMaxWidths CellWidth // Per-column maximum width overrides + + // Deprecated: use Alignment.PerColumn instead. Will be removed in a future version. + // will be removed soon + ColumnAligns []Align // Per-column alignment overrides } type CellWidth struct { Global int PerColumn Mapper[int, int] } + +func (c CellWidth) Constrained() bool { + return c.Global > 0 || c.PerColumn.Len() > 0 +} diff --git a/vendor/github.com/olekukonko/tablewriter/tw/fn.go b/vendor/github.com/olekukonko/tablewriter/tw/fn.go index 8183e3ea..d962ff8d 100644 --- a/vendor/github.com/olekukonko/tablewriter/tw/fn.go +++ b/vendor/github.com/olekukonko/tablewriter/tw/fn.go @@ -3,146 +3,14 @@ package tw import ( - "bytes" // For buffering string output - "github.com/mattn/go-runewidth" // For calculating display width of Unicode characters - "math" // For mathematical operations like ceiling - "regexp" // For regular expression handling of ANSI codes - "strconv" // For string-to-number conversions - "strings" // For string manipulation utilities - "unicode" // For Unicode character classification - "unicode/utf8" // For UTF-8 rune handling + "github.com/olekukonko/tablewriter/pkg/twwidth" + "math" // For mathematical operations like ceiling + "strconv" // For string-to-number conversions + "strings" // For string manipulation utilities + "unicode" // For Unicode character classification + "unicode/utf8" // For UTF-8 rune handling ) -// ansi is a compiled regex pattern used to strip ANSI escape codes. -// These codes are used in terminal output for styling and are invisible in rendered text. -var ansi = CompileANSIFilter() - -// CompileANSIFilter constructs and compiles a regex for matching ANSI sequences. -// It supports both control sequences (CSI) and operating system commands (OSC) like hyperlinks. -func CompileANSIFilter() *regexp.Regexp { - var regESC = "\x1b" // ASCII escape character - var regBEL = "\x07" // ASCII bell character - - // ANSI string terminator: either ESC+\ or BEL - var regST = "(" + regexp.QuoteMeta(regESC+"\\") + "|" + regexp.QuoteMeta(regBEL) + ")" - // Control Sequence Introducer (CSI): ESC[ followed by parameters and a final byte - var regCSI = regexp.QuoteMeta(regESC+"[") + "[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]" - // Operating System Command (OSC): ESC] followed by arbitrary content until a terminator - var regOSC = regexp.QuoteMeta(regESC+"]") + ".*?" + regST - - // Combine CSI and OSC patterns into a single regex - return regexp.MustCompile("(" + regCSI + "|" + regOSC + ")") -} - -// DisplayWidth calculates the visual width of a string, excluding ANSI escape sequences. -// It uses go-runewidth to handle Unicode characters correctly. -func DisplayWidth(str string) int { - // Strip ANSI codes before calculating width to avoid counting invisible characters - return runewidth.StringWidth(ansi.ReplaceAllLiteralString(str, "")) -} - -// TruncateString shortens a string to a specified maximum display width while preserving ANSI color codes. -// An optional suffix (e.g., "...") is appended if truncation occurs. -func TruncateString(s string, maxWidth int, suffix ...string) string { - // If maxWidth is 0 or negative, return an empty string - if maxWidth <= 0 { - return "" - } - - // Join suffix slices into a single string and calculate its display width - suffixStr := strings.Join(suffix, " ") - suffixDisplayWidth := 0 - if len(suffixStr) > 0 { - // Strip ANSI from suffix for accurate width calculation - suffixDisplayWidth = runewidth.StringWidth(ansi.ReplaceAllLiteralString(suffixStr, "")) - } - - // Check if the string (without ANSI) plus suffix fits within maxWidth - strippedS := ansi.ReplaceAllLiteralString(s, "") - if runewidth.StringWidth(strippedS)+suffixDisplayWidth <= maxWidth { - // If it fits, return the original string (with ANSI) plus suffix - return s + suffixStr - } - - // Handle edge case: maxWidth is too small for even the suffix - if maxWidth < suffixDisplayWidth { - // Try truncating the string without suffix - return TruncateString(s, maxWidth) // Recursive call without suffix - } - // Handle edge case: maxWidth exactly equals suffix width - if maxWidth == suffixDisplayWidth { - if runewidth.StringWidth(strippedS) > 0 { - // If there's content, it's fully truncated; return suffix - return suffixStr - } - return "" // No content and no space for content; return empty string - } - - // Calculate the maximum width available for the content (excluding suffix) - targetContentDisplayWidth := maxWidth - suffixDisplayWidth - - var contentBuf bytes.Buffer // Buffer for building truncated content - var currentContentDisplayWidth int // Tracks display width of content - var ansiSeqBuf bytes.Buffer // Buffer for collecting ANSI sequences - inAnsiSequence := false // Tracks if we're inside an ANSI sequence - - // Iterate over runes to build content while respecting maxWidth - for _, r := range s { - if r == '\x1b' { // Start of ANSI escape sequence - if inAnsiSequence { - // Unexpected new ESC; flush existing sequence - contentBuf.Write(ansiSeqBuf.Bytes()) - ansiSeqBuf.Reset() - } - inAnsiSequence = true - ansiSeqBuf.WriteRune(r) - } else if inAnsiSequence { - ansiSeqBuf.WriteRune(r) - // Detect end of common ANSI sequences (e.g., SGR 'm' or CSI terminators) - if r == 'm' || (ansiSeqBuf.Len() > 2 && ansiSeqBuf.Bytes()[1] == '[' && r >= '@' && r <= '~') { - inAnsiSequence = false - contentBuf.Write(ansiSeqBuf.Bytes()) // Append completed sequence - ansiSeqBuf.Reset() - } else if ansiSeqBuf.Len() > 128 { // Prevent buffer overflow for malformed sequences - inAnsiSequence = false - contentBuf.Write(ansiSeqBuf.Bytes()) - ansiSeqBuf.Reset() - } - } else { - // Handle displayable characters - runeDisplayWidth := runewidth.RuneWidth(r) - if currentContentDisplayWidth+runeDisplayWidth > targetContentDisplayWidth { - // Adding this rune would exceed the content width; stop here - break - } - contentBuf.WriteRune(r) - currentContentDisplayWidth += runeDisplayWidth - } - } - - // Append any unterminated ANSI sequence - if ansiSeqBuf.Len() > 0 { - contentBuf.Write(ansiSeqBuf.Bytes()) - } - - finalContent := contentBuf.String() - - // Append suffix if content was truncated or if suffix is provided and content exists - if runewidth.StringWidth(ansi.ReplaceAllLiteralString(finalContent, "")) < runewidth.StringWidth(strippedS) { - // Content was truncated; append suffix - return finalContent + suffixStr - } else if len(suffixStr) > 0 && len(finalContent) > 0 { - // No truncation but suffix exists; append it - return finalContent + suffixStr - } else if len(suffixStr) > 0 && len(strippedS) == 0 { - // Original string was empty; return suffix - return suffixStr - } - - // Return content as is (with preserved ANSI codes) - return finalContent -} - // Title normalizes and uppercases a label string for use in headers. // It replaces underscores and certain dots with spaces and trims whitespace. func Title(name string) string { @@ -172,7 +40,7 @@ func Title(name string) string { // PadCenter centers a string within a specified width using a padding character. // Extra padding is split between left and right, with slight preference to left if uneven. func PadCenter(s, pad string, width int) string { - gap := width - DisplayWidth(s) + gap := width - twwidth.Width(s) if gap > 0 { // Calculate left and right padding; ceil ensures left gets extra if gap is odd gapLeft := int(math.Ceil(float64(gap) / 2)) @@ -185,7 +53,7 @@ func PadCenter(s, pad string, width int) string { // PadRight left-aligns a string within a specified width, filling remaining space on the right with padding. func PadRight(s, pad string, width int) string { - gap := width - DisplayWidth(s) + gap := width - twwidth.Width(s) if gap > 0 { // Append padding to the right return s + strings.Repeat(pad, gap) @@ -196,7 +64,7 @@ func PadRight(s, pad string, width int) string { // PadLeft right-aligns a string within a specified width, filling remaining space on the left with padding. func PadLeft(s, pad string, width int) string { - gap := width - DisplayWidth(s) + gap := width - twwidth.Width(s) if gap > 0 { // Prepend padding to the left return strings.Repeat(pad, gap) + s @@ -208,9 +76,9 @@ func PadLeft(s, pad string, width int) string { // Pad aligns a string within a specified width using a padding character. // It truncates if the string is wider than the target width. func Pad(s string, padChar string, totalWidth int, alignment Align) string { - sDisplayWidth := DisplayWidth(s) + sDisplayWidth := twwidth.Width(s) if sDisplayWidth > totalWidth { - return TruncateString(s, totalWidth) // Only truncate if necessary + return twwidth.Truncate(s, totalWidth) // Only truncate if necessary } switch alignment { case AlignLeft: @@ -245,7 +113,7 @@ func IsNumeric(s string) bool { return err == nil } -// SplitCamelCase splits a camelCase or PascalCase string into separate words. +// SplitCamelCase splits a camelCase or PascalCase or snake_case string into separate words. // It detects transitions between uppercase, lowercase, digits, and other characters. func SplitCamelCase(src string) (entries []string) { // Validate UTF-8 input; return as single entry if invalid @@ -284,10 +152,11 @@ func SplitCamelCase(src string) (entries []string) { runes[i] = runes[i][:len(runes[i])-1] } } - // Convert rune groups to strings, excluding empty or whitespace-only groups + // Convert rune groups to strings, excluding empty, underscore or whitespace-only groups for _, s := range runes { - if len(s) > 0 && strings.TrimSpace(string(s)) != "" { - entries = append(entries, string(s)) + str := string(s) + if len(s) > 0 && strings.TrimSpace(str) != "" && str != "_" { + entries = append(entries, str) } } return @@ -333,7 +202,7 @@ func BreakPoint(s string, limit int) int { runeCount := 0 // Iterate over runes, accumulating display width for _, r := range s { - runeWidth := DisplayWidth(string(r)) // Calculate width of individual rune + runeWidth := twwidth.Width(string(r)) // Calculate width of individual rune if currentWidth+runeWidth > limit { // Adding this rune would exceed the limit; breakpoint is before this rune if currentWidth == 0 { diff --git a/vendor/github.com/olekukonko/tablewriter/tw/preset.go b/vendor/github.com/olekukonko/tablewriter/tw/preset.go new file mode 100644 index 00000000..acadc25c --- /dev/null +++ b/vendor/github.com/olekukonko/tablewriter/tw/preset.go @@ -0,0 +1,18 @@ +package tw + +// BorderNone defines a border configuration with all sides disabled. +var ( + // PaddingNone represents explicitly empty padding (no spacing on any side) + // Equivalent to Padding{Overwrite: true} + PaddingNone = Padding{Left: Empty, Right: Empty, Top: Empty, Bottom: Empty, Overwrite: true} + BorderNone = Border{Left: Off, Right: Off, Top: Off, Bottom: Off} + LinesNone = Lines{ShowTop: Off, ShowBottom: Off, ShowHeaderLine: Off, ShowFooterLine: Off} + SeparatorsNone = Separators{ShowHeader: Off, ShowFooter: Off, BetweenRows: Off, BetweenColumns: Off} +) + +var ( + + // PaddingDefault represents standard single-space padding on left/right + // Equivalent to Padding{Left: " ", Right: " ", Overwrite: true} + PaddingDefault = Padding{Left: " ", Right: " ", Overwrite: true} +) diff --git a/vendor/github.com/olekukonko/tablewriter/tw/renderer.go b/vendor/github.com/olekukonko/tablewriter/tw/renderer.go index 7078a991..cf2779b2 100644 --- a/vendor/github.com/olekukonko/tablewriter/tw/renderer.go +++ b/vendor/github.com/olekukonko/tablewriter/tw/renderer.go @@ -108,21 +108,25 @@ type Settings struct { // Border defines the visibility states of table borders. type Border struct { - Left State // Left border visibility - Right State // Right border visibility - Top State // Top border visibility - Bottom State // Bottom border visibility + Left State // Left border visibility + Right State // Right border visibility + Top State // Top border visibility + Bottom State // Bottom border visibility + Overwrite bool } -// BorderNone defines a border configuration with all sides disabled. -var ( - PaddingNone = Padding{Left: Empty, Right: Empty, Top: Empty, Bottom: Empty} - BorderNone = Border{Left: Off, Right: Off, Top: Off, Bottom: Off} - LinesNone = Lines{ShowTop: Off, ShowBottom: Off, ShowHeaderLine: Off, ShowFooterLine: Off} - SeparatorsNone = Separators{ShowHeader: Off, ShowFooter: Off, BetweenRows: Off, BetweenColumns: Off} -) - type StreamConfig struct { Enable bool - Widths CellWidth // Cell/column widths + + // StrictColumns, if true, causes Append() to return an error + // in streaming mode if the number of cells in an appended row + // does not match the established number of columns for the stream. + // If false (default), rows with mismatched column counts will be + // padded or truncated with a warning log. + StrictColumns bool + + // Deprecated: Use top-level Config.Widths for streaming width control. + // This field will be removed in a future version. It will be respected if + // Config.Widths is not set and this field is. + Widths CellWidth } diff --git a/vendor/github.com/olekukonko/tablewriter/tw/tw.go b/vendor/github.com/olekukonko/tablewriter/tw/tw.go index afdfbe3a..f1cbb9e5 100644 --- a/vendor/github.com/olekukonko/tablewriter/tw/tw.go +++ b/vendor/github.com/olekukonko/tablewriter/tw/tw.go @@ -15,6 +15,8 @@ const ( Skip = "" Space = " " NewLine = "\n" + Column = ":" + Dash = "-" ) // Feature State Constants @@ -54,7 +56,7 @@ const ( ) const ( - SectionHeader = "heder" + SectionHeader = "header" SectionRow = "row" SectionFooter = "footer" ) diff --git a/vendor/github.com/olekukonko/tablewriter/tw/types.go b/vendor/github.com/olekukonko/tablewriter/tw/types.go index 1551c674..29a1862c 100644 --- a/vendor/github.com/olekukonko/tablewriter/tw/types.go +++ b/vendor/github.com/olekukonko/tablewriter/tw/types.go @@ -132,22 +132,68 @@ func (c Caption) WithWidth(width int) Caption { return c } -// Padding defines custom padding characters for a cell +type Control struct { + Hide State +} + +// Compact configures compact width optimization for merged cells. +type Compact struct { + Merge State // Merge enables compact width calculation during cell merging, optimizing space allocation. +} + +// Behavior defines settings that control table rendering behaviors, such as column visibility and content formatting. +type Behavior struct { + AutoHide State // AutoHide determines whether empty columns are hidden. Ignored in streaming mode. + TrimSpace State // TrimSpace enables trimming of leading and trailing spaces from cell content. + + Header Control // Header specifies control settings for the table header. + Footer Control // Footer specifies control settings for the table footer. + + // Compact enables optimized width calculation for merged cells, such as in horizontal merges, + // by systematically determining the most efficient width instead of scaling by the number of columns. + Compact Compact +} + +// Padding defines the spacing characters around cell content in all four directions. +// A zero-value Padding struct will use the table's default padding unless Overwrite is true. type Padding struct { Left string Right string Top string Bottom string + + // Overwrite forces tablewriter to use this padding configuration exactly as specified, + // even when empty. When false (default), empty Padding fields will inherit defaults. + // + // For explicit no-padding, use the PaddingNone constant instead of setting Overwrite. + Overwrite bool } -type Control struct { - Hide State +// Common padding configurations for convenience + +// Equals reports whether two Padding configurations are identical in all fields. +// This includes comparing the Overwrite flag as part of the equality check. +func (p Padding) Equals(padding Padding) bool { + return p.Left == padding.Left && + p.Right == padding.Right && + p.Top == padding.Top && + p.Bottom == padding.Bottom && + p.Overwrite == padding.Overwrite } -// Behavior defines table behavior settings that control features like auto-hiding columns and trimming spaces. -type Behavior struct { - AutoHide State // Controls whether empty columns are automatically hidden (ignored in streaming mode) - TrimSpace State // Controls whether leading/trailing spaces are trimmed from cell content - Header Control - Footer Control +// Empty reports whether all padding strings are empty (all fields == ""). +// Note that an Empty padding may still take effect if Overwrite is true. +func (p Padding) Empty() bool { + return p.Left == "" && p.Right == "" && p.Top == "" && p.Bottom == "" +} + +// Paddable reports whether this Padding configuration should override existing padding. +// Returns true if either: +// - Any padding string is non-empty (!p.Empty()) +// - Overwrite flag is true (even with all strings empty) +// +// This is used internally during configuration merging to determine whether to +// apply the padding settings. +func (p Padding) Paddable() bool { + return !p.Empty() || p.Overwrite } diff --git a/vendor/github.com/olekukonko/tablewriter/zoo.go b/vendor/github.com/olekukonko/tablewriter/zoo.go index a7f63d58..b24f230c 100644 --- a/vendor/github.com/olekukonko/tablewriter/zoo.go +++ b/vendor/github.com/olekukonko/tablewriter/zoo.go @@ -4,8 +4,10 @@ import ( "database/sql" "fmt" "github.com/olekukonko/errors" + "github.com/olekukonko/tablewriter/pkg/twwidth" "github.com/olekukonko/tablewriter/tw" "io" + "math" "reflect" "strconv" "strings" @@ -164,7 +166,7 @@ func (t *Table) applyHorizontalMergeWidths(position tw.Position, ctx *renderCont 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()) } } @@ -540,7 +542,7 @@ func (t *Table) buildCoreCellContexts(line []string, merges map[int]tw.MergeStat // It generates a []string where each element is the padding content for a column, using the specified padChar. func (t *Table) buildPaddingLineContents(padChar string, widths tw.Mapper[int, int], numCols int, merges map[int]tw.MergeState) []string { line := make([]string, numCols) - padWidth := tw.DisplayWidth(padChar) + padWidth := twwidth.Width(padChar) if padWidth < 1 { padWidth = 1 } @@ -576,26 +578,23 @@ func (t *Table) buildPaddingLineContents(padChar string, widths tw.Mapper[int, i // Parameter ctx holds rendering state with width maps. // Returns an error if width calculation fails. func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error { - ctx.logger.Debugf("calculateAndNormalizeWidths: Computing and normalizing widths for %d columns", ctx.numCols) + ctx.logger.Debugf("calculateAndNormalizeWidths: Computing and normalizing widths for %d columns. Compact: %v", + ctx.numCols, t.config.Behavior.Compact.Merge.Enabled()) // Initialize width maps - t.headerWidths = tw.NewMapper[int, int]() - t.rowWidths = tw.NewMapper[int, int]() - t.footerWidths = tw.NewMapper[int, int]() + //t.headerWidths = tw.NewMapper[int, int]() + //t.rowWidths = tw.NewMapper[int, int]() + //t.footerWidths = tw.NewMapper[int, int]() - // Compute header widths + // Compute content-based widths for each section for _, lines := range ctx.headerLines { t.updateWidths(lines, t.headerWidths, t.config.Header.Padding) } - ctx.logger.Debugf("Initial Header widths: %v", t.headerWidths) - - // Cache row widths to avoid re-iteration rowWidthCache := make([]tw.Mapper[int, int], len(ctx.rowLines)) for i, row := range ctx.rowLines { rowWidthCache[i] = tw.NewMapper[int, int]() for _, line := range row { t.updateWidths(line, rowWidthCache[i], t.config.Row.Padding) - // Aggregate into t.rowWidths for col, width := range rowWidthCache[i] { currentMax, _ := t.rowWidths.OK(col) if width > currentMax { @@ -604,41 +603,348 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error { } } } - ctx.logger.Debugf("Initial Row widths: %v", t.rowWidths) - - // Compute footer widths for _, lines := range ctx.footerLines { t.updateWidths(lines, t.footerWidths, t.config.Footer.Padding) } - ctx.logger.Debugf("Initial Footer widths: %v", t.footerWidths) - - // Initialize width maps for normalization - ctx.widths[tw.Header] = tw.NewMapper[int, int]() - ctx.widths[tw.Row] = tw.NewMapper[int, int]() - ctx.widths[tw.Footer] = tw.NewMapper[int, int]() + ctx.logger.Debugf("Content-based widths: header=%v, row=%v, footer=%v", t.headerWidths, t.rowWidths, t.footerWidths) + + // Analyze header merges for optimization + var headerMergeSpans map[int]int + if t.config.Header.Formatting.MergeMode&tw.MergeHorizontal != 0 && len(ctx.headerLines) > 0 { + headerMergeSpans = make(map[int]int) + visitedCols := make(map[int]bool) + firstHeaderLine := ctx.headerLines[0] + if len(firstHeaderLine) > 0 { + for i := 0; i < len(firstHeaderLine); { + if visitedCols[i] { + i++ + continue + } + var currentLogicalCellContentBuilder strings.Builder + for _, hLine := range ctx.headerLines { + if i < len(hLine) { + currentLogicalCellContentBuilder.WriteString(hLine[i]) + } + } + currentHeaderCellContent := t.Trimmer(currentLogicalCellContentBuilder.String()) + span := 1 + for j := i + 1; j < len(firstHeaderLine); j++ { + var nextLogicalCellContentBuilder strings.Builder + for _, hLine := range ctx.headerLines { + if j < len(hLine) { + nextLogicalCellContentBuilder.WriteString(hLine[j]) + } + } + nextHeaderCellContent := t.Trimmer(nextLogicalCellContentBuilder.String()) + if currentHeaderCellContent == nextHeaderCellContent && currentHeaderCellContent != "" && currentHeaderCellContent != "-" { + span++ + } else { + break + } + } + if span > 1 { + headerMergeSpans[i] = span + for k := 0; k < span; k++ { + visitedCols[i+k] = true + } + } + i += span + } + } + if len(headerMergeSpans) > 0 { + ctx.logger.Debugf("Header merge spans: %v", headerMergeSpans) + } + } - // Normalize widths by taking the maximum across sections + // Determine natural column widths + naturalColumnWidths := tw.NewMapper[int, int]() for i := 0; i < ctx.numCols; i++ { - maxWidth := 0 - for _, w := range []tw.Mapper[int, int]{t.headerWidths, t.rowWidths, t.footerWidths} { - if wd := w.Get(i); wd > maxWidth { - maxWidth = wd + width := 0 + if colWidth, ok := t.config.Widths.PerColumn.OK(i); ok && colWidth >= 0 { + width = colWidth + ctx.logger.Debugf("Col %d width from Config.Widths.PerColumn: %d", i, width) + } else { + maxRowFooterWidth := tw.Max(t.rowWidths.Get(i), t.footerWidths.Get(i)) + headerCellOriginalWidth := t.headerWidths.Get(i) + if t.config.Behavior.Compact.Merge.Enabled() && + t.config.Header.Formatting.MergeMode&tw.MergeHorizontal != 0 && + headerMergeSpans != nil { + isColInHeaderMerge := false + for startCol, span := range headerMergeSpans { + if i >= startCol && i < startCol+span { + isColInHeaderMerge = true + break + } + } + if isColInHeaderMerge { + width = maxRowFooterWidth + if width == 0 && headerCellOriginalWidth > 0 { + width = headerCellOriginalWidth + } + ctx.logger.Debugf("Col %d (in merge) width: %d (row/footer: %d, header: %d)", i, width, maxRowFooterWidth, headerCellOriginalWidth) + } else { + width = tw.Max(headerCellOriginalWidth, maxRowFooterWidth) + ctx.logger.Debugf("Col %d (not in merge) width: %d", i, width) + } + } else { + width = tw.Max(tw.Max(headerCellOriginalWidth, t.rowWidths.Get(i)), t.footerWidths.Get(i)) + ctx.logger.Debugf("Col %d width (no merge): %d", i, width) + } + if width == 0 && (headerCellOriginalWidth > 0 || t.rowWidths.Get(i) > 0 || t.footerWidths.Get(i) > 0) { + width = tw.Max(tw.Max(headerCellOriginalWidth, t.rowWidths.Get(i)), t.footerWidths.Get(i)) + } + if width == 0 { + width = 1 + } + } + naturalColumnWidths.Set(i, width) + } + ctx.logger.Debugf("Natural column widths: %v", naturalColumnWidths) + + // Expand columns for merged header content if needed + workingWidths := naturalColumnWidths.Clone() + if t.config.Header.Formatting.MergeMode&tw.MergeHorizontal != 0 && headerMergeSpans != nil { + if span, isOneBigMerge := headerMergeSpans[0]; isOneBigMerge && span == ctx.numCols && ctx.numCols > 0 { + var firstHeaderCellLogicalContentBuilder strings.Builder + for _, hLine := range ctx.headerLines { + if 0 < len(hLine) { + firstHeaderCellLogicalContentBuilder.WriteString(hLine[0]) + } + } + mergedContentString := t.Trimmer(firstHeaderCellLogicalContentBuilder.String()) + headerCellPadding := t.config.Header.Padding.Global + if 0 < len(t.config.Header.Padding.PerColumn) && t.config.Header.Padding.PerColumn[0].Paddable() { + headerCellPadding = t.config.Header.Padding.PerColumn[0] + } + actualMergedHeaderContentPhysicalWidth := twwidth.Width(mergedContentString) + + twwidth.Width(headerCellPadding.Left) + + twwidth.Width(headerCellPadding.Right) + currentSumOfColumnWidths := 0 + workingWidths.Each(func(_ int, w int) { currentSumOfColumnWidths += w }) + numSeparatorsInFullSpan := 0 + if ctx.numCols > 1 { + if t.renderer != nil && t.renderer.Config().Settings.Separators.BetweenColumns.Enabled() { + numSeparatorsInFullSpan = (ctx.numCols - 1) * twwidth.Width(t.renderer.Config().Symbols.Column()) + } + } + totalCurrentSpanPhysicalWidth := currentSumOfColumnWidths + numSeparatorsInFullSpan + if actualMergedHeaderContentPhysicalWidth > totalCurrentSpanPhysicalWidth { + ctx.logger.Debugf("Merged header content '%s' (width %d) exceeds total width %d. Expanding.", + mergedContentString, actualMergedHeaderContentPhysicalWidth, totalCurrentSpanPhysicalWidth) + shortfall := actualMergedHeaderContentPhysicalWidth - totalCurrentSpanPhysicalWidth + numNonZeroCols := 0 + workingWidths.Each(func(_ int, w int) { + if w > 0 { + numNonZeroCols++ + } + }) + if numNonZeroCols == 0 && ctx.numCols > 0 { + numNonZeroCols = ctx.numCols + } + if numNonZeroCols > 0 && shortfall > 0 { + extraPerColumn := int(math.Ceil(float64(shortfall) / float64(numNonZeroCols))) + finalSumAfterExpansion := 0 + workingWidths.Each(func(colIdx int, currentW int) { + if currentW > 0 || (numNonZeroCols == ctx.numCols && ctx.numCols > 0) { + newWidth := currentW + extraPerColumn + workingWidths.Set(colIdx, newWidth) + finalSumAfterExpansion += newWidth + ctx.logger.Debugf("Col %d expanded by %d to %d", colIdx, extraPerColumn, newWidth) + } else { + finalSumAfterExpansion += currentW + } + }) + overDistributed := (finalSumAfterExpansion + numSeparatorsInFullSpan) - actualMergedHeaderContentPhysicalWidth + if overDistributed > 0 { + ctx.logger.Debugf("Correcting over-distribution of %d", overDistributed) + // Sort columns for deterministic reduction + sortedCols := workingWidths.SortedKeys() + for i := 0; i < overDistributed; i++ { + // Reduce from highest-indexed column + for j := len(sortedCols) - 1; j >= 0; j-- { + col := sortedCols[j] + if workingWidths.Get(col) > 1 && naturalColumnWidths.Get(col) < workingWidths.Get(col) { + workingWidths.Set(col, workingWidths.Get(col)-1) + ctx.logger.Debugf("Reduced col %d by 1 to %d", col, workingWidths.Get(col)) + break + } + } + } + } + } } } - ctx.widths[tw.Header].Set(i, maxWidth) - ctx.widths[tw.Row].Set(i, maxWidth) - ctx.widths[tw.Footer].Set(i, maxWidth) } - ctx.logger.Debugf("Normalized widths: header=%v, row=%v, footer=%v", ctx.widths[tw.Header], ctx.widths[tw.Row], ctx.widths[tw.Footer]) + ctx.logger.Debugf("Widths after merged header expansion: %v", workingWidths) + + // Apply global width constraint + finalWidths := workingWidths.Clone() + if t.config.Widths.Global > 0 { + ctx.logger.Debugf("Applying global width constraint: %d", t.config.Widths.Global) + currentSumOfFinalColWidths := 0 + finalWidths.Each(func(_ int, w int) { currentSumOfFinalColWidths += w }) + numSeparators := 0 + if ctx.numCols > 1 && t.renderer != nil && t.renderer.Config().Settings.Separators.BetweenColumns.Enabled() { + numSeparators = (ctx.numCols - 1) * twwidth.Width(t.renderer.Config().Symbols.Column()) + } + totalCurrentTablePhysicalWidth := currentSumOfFinalColWidths + numSeparators + if totalCurrentTablePhysicalWidth > t.config.Widths.Global { + ctx.logger.Debugf("Table width %d exceeds global limit %d. Shrinking.", totalCurrentTablePhysicalWidth, t.config.Widths.Global) + targetTotalColumnContentWidth := t.config.Widths.Global - numSeparators + if targetTotalColumnContentWidth < 0 { + targetTotalColumnContentWidth = 0 + } + if ctx.numCols > 0 && targetTotalColumnContentWidth < ctx.numCols { + targetTotalColumnContentWidth = ctx.numCols + } + hardMinimums := tw.NewMapper[int, int]() + sumOfHardMinimums := 0 + isHeaderContentHardToWrap := !(t.config.Header.Formatting.AutoWrap == tw.WrapNormal || t.config.Header.Formatting.AutoWrap == tw.WrapBreak) + for i := 0; i < ctx.numCols; i++ { + minW := 1 + if isHeaderContentHardToWrap && len(ctx.headerLines) > 0 { + headerColNaturalWidthWithPadding := t.headerWidths.Get(i) + if headerColNaturalWidthWithPadding > minW { + minW = headerColNaturalWidthWithPadding + } + } + hardMinimums.Set(i, minW) + sumOfHardMinimums += minW + } + ctx.logger.Debugf("Hard minimums: %v (sum: %d)", hardMinimums, sumOfHardMinimums) + if targetTotalColumnContentWidth < sumOfHardMinimums && sumOfHardMinimums > 0 { + ctx.logger.Warnf("Target width %d below minimums %d. Scaling.", targetTotalColumnContentWidth, sumOfHardMinimums) + scaleFactorMin := float64(targetTotalColumnContentWidth) / float64(sumOfHardMinimums) + if scaleFactorMin < 0 { + scaleFactorMin = 0 + } + tempSum := 0 + scaledHardMinimums := tw.NewMapper[int, int]() + hardMinimums.Each(func(colIdx int, currentMinW int) { + scaledMinW := int(math.Round(float64(currentMinW) * scaleFactorMin)) + if scaledMinW < 1 && targetTotalColumnContentWidth > 0 { + scaledMinW = 1 + } else if scaledMinW < 0 { + scaledMinW = 0 + } + scaledHardMinimums.Set(colIdx, scaledMinW) + tempSum += scaledMinW + }) + errorDiffMin := targetTotalColumnContentWidth - tempSum + if errorDiffMin != 0 && scaledHardMinimums.Len() > 0 { + sortedKeys := scaledHardMinimums.SortedKeys() + for i := 0; i < int(math.Abs(float64(errorDiffMin))); i++ { + keyToAdjust := sortedKeys[i%len(sortedKeys)] + val := scaledHardMinimums.Get(keyToAdjust) + adj := 1 + if errorDiffMin < 0 { + adj = -1 + } + if val+adj >= 1 || (val+adj == 0 && targetTotalColumnContentWidth == 0) { + scaledHardMinimums.Set(keyToAdjust, val+adj) + } else if adj > 0 { + scaledHardMinimums.Set(keyToAdjust, val+adj) + } + } + } + finalWidths = scaledHardMinimums.Clone() + ctx.logger.Debugf("Scaled minimums: %v", finalWidths) + } else { + finalWidths = hardMinimums.Clone() + widthAllocatedByMinimums := sumOfHardMinimums + remainingWidthToDistribute := targetTotalColumnContentWidth - widthAllocatedByMinimums + ctx.logger.Debugf("Target: %d, minimums: %d, remaining: %d", targetTotalColumnContentWidth, widthAllocatedByMinimums, remainingWidthToDistribute) + if remainingWidthToDistribute > 0 { + sumOfFlexiblePotentialBase := 0 + flexibleColsOriginalWidths := tw.NewMapper[int, int]() + for i := 0; i < ctx.numCols; i++ { + naturalW := workingWidths.Get(i) + minW := hardMinimums.Get(i) + if naturalW > minW { + sumOfFlexiblePotentialBase += (naturalW - minW) + flexibleColsOriginalWidths.Set(i, naturalW) + } + } + ctx.logger.Debugf("Flexible potential: %d, flexible widths: %v", sumOfFlexiblePotentialBase, flexibleColsOriginalWidths) + if sumOfFlexiblePotentialBase > 0 { + distributedExtraSum := 0 + sortedFlexKeys := flexibleColsOriginalWidths.SortedKeys() + for _, colIdx := range sortedFlexKeys { + naturalWOfCol := flexibleColsOriginalWidths.Get(colIdx) + hardMinOfCol := hardMinimums.Get(colIdx) + flexiblePartOfCol := naturalWOfCol - hardMinOfCol + proportion := 0.0 + if sumOfFlexiblePotentialBase > 0 { + proportion = float64(flexiblePartOfCol) / float64(sumOfFlexiblePotentialBase) + } else if len(sortedFlexKeys) > 0 { + proportion = 1.0 / float64(len(sortedFlexKeys)) + } + extraForThisCol := int(math.Round(float64(remainingWidthToDistribute) * proportion)) + currentAssignedW := finalWidths.Get(colIdx) + finalWidths.Set(colIdx, currentAssignedW+extraForThisCol) + distributedExtraSum += extraForThisCol + } + errorInDist := remainingWidthToDistribute - distributedExtraSum + ctx.logger.Debugf("Distributed %d, error: %d", distributedExtraSum, errorInDist) + if errorInDist != 0 && len(sortedFlexKeys) > 0 { + for i := 0; i < int(math.Abs(float64(errorInDist))); i++ { + colToAdjust := sortedFlexKeys[i%len(sortedFlexKeys)] + w := finalWidths.Get(colToAdjust) + adj := 1 + if errorInDist < 0 { + adj = -1 + } + if !(adj < 0 && w+adj < hardMinimums.Get(colToAdjust)) { + finalWidths.Set(colToAdjust, w+adj) + } else if adj > 0 { + finalWidths.Set(colToAdjust, w+adj) + } + } + } + } else { + if ctx.numCols > 0 { + extraPerCol := remainingWidthToDistribute / ctx.numCols + rem := remainingWidthToDistribute % ctx.numCols + for i := 0; i < ctx.numCols; i++ { + currentW := finalWidths.Get(i) + add := extraPerCol + if i < rem { + add++ + } + finalWidths.Set(i, currentW+add) + } + } + } + } + } + finalSumCheck := 0 + finalWidths.Each(func(idx int, w int) { + if w < 1 && targetTotalColumnContentWidth > 0 { + finalWidths.Set(idx, 1) + } else if w < 0 { + finalWidths.Set(idx, 0) + } + finalSumCheck += finalWidths.Get(idx) + }) + ctx.logger.Debugf("Final widths after scaling: %v (sum: %d, target: %d)", finalWidths, finalSumCheck, targetTotalColumnContentWidth) + } + } + + // Assign final widths to context + ctx.widths[tw.Header] = finalWidths.Clone() + ctx.widths[tw.Row] = finalWidths.Clone() + ctx.widths[tw.Footer] = finalWidths.Clone() + ctx.logger.Debugf("Final normalized widths: header=%v, row=%v, footer=%v", ctx.widths[tw.Header], ctx.widths[tw.Row], ctx.widths[tw.Footer]) return nil } // calculateContentMaxWidth computes the maximum content width for a column, accounting for padding and mode-specific constraints. // Returns the effective content width (after subtracting padding) for the given column index. func (t *Table) calculateContentMaxWidth(colIdx int, config tw.CellConfig, padLeftWidth, padRightWidth int, isStreaming bool) int { - var effectiveContentMaxWidth int + if isStreaming { + // Existing streaming logic remains unchanged totalColumnWidthFromStream := t.streamWidths.Get(colIdx) if totalColumnWidthFromStream < 0 { totalColumnWidthFromStream = 0 @@ -652,28 +958,57 @@ func (t *Table) calculateContentMaxWidth(colIdx int, config tw.CellConfig, padLe if totalColumnWidthFromStream == 0 { effectiveContentMaxWidth = 0 } - t.logger.Debugf("calculateContentMaxWidth: Streaming col %d, TotalColWd=%d, PadL=%d, PadR=%d -> ContentMaxWd=%d", - colIdx, totalColumnWidthFromStream, padLeftWidth, padRightWidth, effectiveContentMaxWidth) + t.logger.Debugf("calculateContentMaxWidth: Streaming col %d, TotalColWd=%d, PadL=%d, PadR=%d -> ContentMaxWd=%d", colIdx, totalColumnWidthFromStream, padLeftWidth, padRightWidth, effectiveContentMaxWidth) } else { - hasConstraint := false + // New priority-based width constraint checking constraintTotalCellWidth := 0 - if config.ColMaxWidths.PerColumn != nil { + hasConstraint := false + + // 1. Check new Widths.PerColumn (highest priority) + if t.config.Widths.Constrained() { + + if colWidth, ok := t.config.Widths.PerColumn.OK(colIdx); ok && colWidth > 0 { + constraintTotalCellWidth = colWidth + hasConstraint = true + t.logger.Debugf("calculateContentMaxWidth: Using Widths.PerColumn[%d] = %d", + colIdx, constraintTotalCellWidth) + } + + // 2. Check new Widths.Global + if !hasConstraint && t.config.Widths.Global > 0 { + constraintTotalCellWidth = t.config.Widths.Global + hasConstraint = true + t.logger.Debugf("calculateContentMaxWidth: Using Widths.Global = %d", constraintTotalCellWidth) + } + } + + // 3. Fall back to legacy ColMaxWidths.PerColumn (backward compatibility) + if !hasConstraint && config.ColMaxWidths.PerColumn != nil { if colMax, ok := config.ColMaxWidths.PerColumn.OK(colIdx); ok && colMax > 0 { constraintTotalCellWidth = colMax hasConstraint = true - t.logger.Debugf("calculateContentMaxWidth: Batch col %d using config.ColMaxWidths.PerColumn (as total cell width constraint): %d", colIdx, constraintTotalCellWidth) + t.logger.Debugf("calculateContentMaxWidth: Using legacy ColMaxWidths.PerColumn[%d] = %d", + colIdx, constraintTotalCellWidth) } } + + // 4. Fall back to legacy ColMaxWidths.Global if !hasConstraint && config.ColMaxWidths.Global > 0 { constraintTotalCellWidth = config.ColMaxWidths.Global hasConstraint = true - t.logger.Debugf("calculateContentMaxWidth: Batch col %d using config.Formatting.MaxWidth (as total cell width constraint): %d", colIdx, constraintTotalCellWidth) + t.logger.Debugf("calculateContentMaxWidth: Using legacy ColMaxWidths.Global = %d", + constraintTotalCellWidth) } + + // 5. Fall back to table MaxWidth if auto-wrapping if !hasConstraint && t.config.MaxWidth > 0 && config.Formatting.AutoWrap != tw.WrapNone { constraintTotalCellWidth = t.config.MaxWidth hasConstraint = true - t.logger.Debugf("calculateContentMaxWidth: Batch col %d using t.config.MaxWidth (as total cell width constraint, due to AutoWrap != WrapNone): %d", colIdx, constraintTotalCellWidth) + t.logger.Debugf("calculateContentMaxWidth: Using table MaxWidth = %d (AutoWrap enabled)", + constraintTotalCellWidth) } + + // Calculate effective width based on found constraint if hasConstraint { effectiveContentMaxWidth = constraintTotalCellWidth - padLeftWidth - padRightWidth if effectiveContentMaxWidth < 1 && constraintTotalCellWidth > (padLeftWidth+padRightWidth) { @@ -681,13 +1016,14 @@ func (t *Table) calculateContentMaxWidth(colIdx int, config tw.CellConfig, padLe } else if effectiveContentMaxWidth < 0 { effectiveContentMaxWidth = 0 } - t.logger.Debugf("calculateContentMaxWidth: Batch col %d, ConstraintTotalCellWidth=%d, PadL=%d, PadR=%d -> EffectiveContentMaxWidth=%d", - colIdx, constraintTotalCellWidth, padLeftWidth, padRightWidth, effectiveContentMaxWidth) + t.logger.Debugf("calculateContentMaxWidth: ConstraintTotalCellWidth=%d, PadL=%d, PadR=%d -> EffectiveContentMaxWidth=%d", + constraintTotalCellWidth, padLeftWidth, padRightWidth, effectiveContentMaxWidth) } else { effectiveContentMaxWidth = 0 - t.logger.Debugf("calculateContentMaxWidth: Batch col %d, No applicable MaxWidth constraint. EffectiveContentMaxWidth set to 0 (unlimited for this stage).", colIdx) + t.logger.Debugf("calculateContentMaxWidth: No width constraints found for column %d", colIdx) } } + return effectiveContentMaxWidth } @@ -698,6 +1034,8 @@ func (t *Table) convertToStringer(input interface{}) ([]string, error) { return nil, errors.New("internal error: convertToStringer called with nil t.stringer") } + t.logger.Debugf("convertToString attempt %v using %v", input, t.stringer) + inputType := reflect.TypeOf(input) stringerFuncVal := reflect.ValueOf(t.stringer) stringerFuncType := stringerFuncVal.Type() @@ -1310,23 +1648,24 @@ func (t *Table) updateWidths(row []string, widths tw.Mapper[int, int], padding t t.logger.Debugf("Updating widths for row: %v", row) for i, cell := range row { colPad := padding.Global - if i < len(padding.PerColumn) && padding.PerColumn[i] != (tw.Padding{}) { + + if i < len(padding.PerColumn) && padding.PerColumn[i].Paddable() { colPad = padding.PerColumn[i] t.logger.Debugf(" Col %d: Using per-column padding: L:'%s' R:'%s'", i, colPad.Left, colPad.Right) } else { t.logger.Debugf(" Col %d: Using global padding: L:'%s' R:'%s'", i, padding.Global.Left, padding.Global.Right) } - padLeftWidth := tw.DisplayWidth(colPad.Left) - padRightWidth := tw.DisplayWidth(colPad.Right) + padLeftWidth := twwidth.Width(colPad.Left) + padRightWidth := twwidth.Width(colPad.Right) // Split cell into lines and find maximum content width lines := strings.Split(cell, tw.NewLine) contentWidth := 0 for _, line := range lines { - lineWidth := tw.DisplayWidth(line) + lineWidth := twwidth.Width(line) if t.config.Behavior.TrimSpace.Enabled() { - lineWidth = tw.DisplayWidth(t.Trimmer(line)) + lineWidth = twwidth.Width(t.Trimmer(line)) } if lineWidth > contentWidth { contentWidth = lineWidth diff --git a/vendor/go.etcd.io/etcd/api/v3/version/version.go b/vendor/go.etcd.io/etcd/api/v3/version/version.go index d08dd196..2eaf0e58 100644 --- a/vendor/go.etcd.io/etcd/api/v3/version/version.go +++ b/vendor/go.etcd.io/etcd/api/v3/version/version.go @@ -26,7 +26,7 @@ import ( var ( // MinClusterVersion is the min cluster version this etcd binary is compatible with. MinClusterVersion = "3.0.0" - Version = "3.6.0" + Version = "3.6.2" APIVersion = "unknown" // Git SHA Value will be set during build diff --git a/vendor/golang.org/x/net/http2/frame.go b/vendor/golang.org/x/net/http2/frame.go index 97bd8b06..db3264da 100644 --- a/vendor/golang.org/x/net/http2/frame.go +++ b/vendor/golang.org/x/net/http2/frame.go @@ -39,7 +39,7 @@ const ( FrameContinuation FrameType = 0x9 ) -var frameName = map[FrameType]string{ +var frameNames = [...]string{ FrameData: "DATA", FrameHeaders: "HEADERS", FramePriority: "PRIORITY", @@ -53,10 +53,10 @@ var frameName = map[FrameType]string{ } func (t FrameType) String() string { - if s, ok := frameName[t]; ok { - return s + if int(t) < len(frameNames) { + return frameNames[t] } - return fmt.Sprintf("UNKNOWN_FRAME_TYPE_%d", uint8(t)) + return fmt.Sprintf("UNKNOWN_FRAME_TYPE_%d", t) } // Flags is a bitmask of HTTP/2 flags. @@ -124,7 +124,7 @@ var flagName = map[FrameType]map[Flags]string{ // might be 0). type frameParser func(fc *frameCache, fh FrameHeader, countError func(string), payload []byte) (Frame, error) -var frameParsers = map[FrameType]frameParser{ +var frameParsers = [...]frameParser{ FrameData: parseDataFrame, FrameHeaders: parseHeadersFrame, FramePriority: parsePriorityFrame, @@ -138,8 +138,8 @@ var frameParsers = map[FrameType]frameParser{ } func typeFrameParser(t FrameType) frameParser { - if f := frameParsers[t]; f != nil { - return f + if int(t) < len(frameParsers) { + return frameParsers[t] } return parseUnknownFrame } @@ -509,7 +509,7 @@ func (fr *Framer) ReadFrame() (Frame, error) { } if fh.Length > fr.maxReadSize { if fh == invalidHTTP1LookingFrameHeader() { - return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", err) + return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", ErrFrameTooLarge) } return nil, ErrFrameTooLarge } diff --git a/vendor/golang.org/x/net/trace/events.go b/vendor/golang.org/x/net/trace/events.go index c646a695..3aaffdd1 100644 --- a/vendor/golang.org/x/net/trace/events.go +++ b/vendor/golang.org/x/net/trace/events.go @@ -508,7 +508,7 @@ const eventsHTML = ` {{$el.When}} {{$el.ElapsedTime}} - {{$el.Title}} + {{$el.Title}} {{if $.Expanded}} diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index 4f432bfe..9e7a6c5a 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -319,6 +319,7 @@ const ( AUDIT_INTEGRITY_POLICY_RULE = 0x70f AUDIT_INTEGRITY_RULE = 0x70d AUDIT_INTEGRITY_STATUS = 0x70a + AUDIT_INTEGRITY_USERSPACE = 0x710 AUDIT_IPC = 0x517 AUDIT_IPC_SET_PERM = 0x51f AUDIT_IPE_ACCESS = 0x58c @@ -843,9 +844,9 @@ const ( DM_UUID_FLAG = 0x4000 DM_UUID_LEN = 0x81 DM_VERSION = 0xc138fd00 - DM_VERSION_EXTRA = "-ioctl (2023-03-01)" + DM_VERSION_EXTRA = "-ioctl (2025-01-17)" DM_VERSION_MAJOR = 0x4 - DM_VERSION_MINOR = 0x30 + DM_VERSION_MINOR = 0x31 DM_VERSION_PATCHLEVEL = 0x0 DT_BLK = 0x6 DT_CHR = 0x2 @@ -941,6 +942,8 @@ const ( ETHER_FLOW = 0x12 ETHTOOL_BUSINFO_LEN = 0x20 ETHTOOL_EROMVERS_LEN = 0x20 + ETHTOOL_FAMILY_NAME = "ethtool" + ETHTOOL_FAMILY_VERSION = 0x1 ETHTOOL_FEC_AUTO = 0x2 ETHTOOL_FEC_BASER = 0x10 ETHTOOL_FEC_LLRS = 0x20 @@ -1203,6 +1206,9 @@ const ( FAN_DENY = 0x2 FAN_ENABLE_AUDIT = 0x40 FAN_EPIDFD = -0x2 + FAN_ERRNO_BITS = 0x8 + FAN_ERRNO_MASK = 0xff + FAN_ERRNO_SHIFT = 0x18 FAN_EVENT_INFO_TYPE_DFID = 0x3 FAN_EVENT_INFO_TYPE_DFID_NAME = 0x2 FAN_EVENT_INFO_TYPE_ERROR = 0x5 @@ -1210,6 +1216,7 @@ const ( FAN_EVENT_INFO_TYPE_NEW_DFID_NAME = 0xc FAN_EVENT_INFO_TYPE_OLD_DFID_NAME = 0xa FAN_EVENT_INFO_TYPE_PIDFD = 0x4 + FAN_EVENT_INFO_TYPE_RANGE = 0x6 FAN_EVENT_METADATA_LEN = 0x18 FAN_EVENT_ON_CHILD = 0x8000000 FAN_FS_ERROR = 0x8000 @@ -1240,6 +1247,7 @@ const ( FAN_OPEN_EXEC = 0x1000 FAN_OPEN_EXEC_PERM = 0x40000 FAN_OPEN_PERM = 0x10000 + FAN_PRE_ACCESS = 0x100000 FAN_Q_OVERFLOW = 0x4000 FAN_RENAME = 0x10000000 FAN_REPORT_DFID_NAME = 0xc00 @@ -2787,7 +2795,7 @@ const ( RTAX_UNSPEC = 0x0 RTAX_WINDOW = 0x3 RTA_ALIGNTO = 0x4 - RTA_MAX = 0x1e + RTA_MAX = 0x1f RTCF_DIRECTSRC = 0x4000000 RTCF_DOREDIRECT = 0x1000000 RTCF_LOG = 0x2000000 @@ -2864,10 +2872,12 @@ const ( RTM_DELACTION = 0x31 RTM_DELADDR = 0x15 RTM_DELADDRLABEL = 0x49 + RTM_DELANYCAST = 0x3d RTM_DELCHAIN = 0x65 RTM_DELLINK = 0x11 RTM_DELLINKPROP = 0x6d RTM_DELMDB = 0x55 + RTM_DELMULTICAST = 0x39 RTM_DELNEIGH = 0x1d RTM_DELNETCONF = 0x51 RTM_DELNEXTHOP = 0x69 @@ -2917,11 +2927,13 @@ const ( RTM_NEWACTION = 0x30 RTM_NEWADDR = 0x14 RTM_NEWADDRLABEL = 0x48 + RTM_NEWANYCAST = 0x3c RTM_NEWCACHEREPORT = 0x60 RTM_NEWCHAIN = 0x64 RTM_NEWLINK = 0x10 RTM_NEWLINKPROP = 0x6c RTM_NEWMDB = 0x54 + RTM_NEWMULTICAST = 0x38 RTM_NEWNDUSEROPT = 0x44 RTM_NEWNEIGH = 0x1c RTM_NEWNEIGHTBL = 0x40 @@ -2987,11 +2999,12 @@ const ( RUSAGE_THREAD = 0x1 RWF_APPEND = 0x10 RWF_ATOMIC = 0x40 + RWF_DONTCACHE = 0x80 RWF_DSYNC = 0x2 RWF_HIPRI = 0x1 RWF_NOAPPEND = 0x20 RWF_NOWAIT = 0x8 - RWF_SUPPORTED = 0x7f + RWF_SUPPORTED = 0xff RWF_SYNC = 0x4 RWF_WRITE_LIFE_NOT_SET = 0x0 SCHED_BATCH = 0x3 @@ -3271,6 +3284,7 @@ const ( STATX_BTIME = 0x800 STATX_CTIME = 0x80 STATX_DIOALIGN = 0x2000 + STATX_DIO_READ_ALIGN = 0x20000 STATX_GID = 0x10 STATX_INO = 0x100 STATX_MNT_ID = 0x1000 @@ -3322,7 +3336,7 @@ const ( TASKSTATS_GENL_NAME = "TASKSTATS" TASKSTATS_GENL_VERSION = 0x1 TASKSTATS_TYPE_MAX = 0x6 - TASKSTATS_VERSION = 0xe + TASKSTATS_VERSION = 0xf TCIFLUSH = 0x0 TCIOFF = 0x2 TCIOFLUSH = 0x2 @@ -3503,6 +3517,7 @@ const ( TP_STATUS_WRONG_FORMAT = 0x4 TRACEFS_MAGIC = 0x74726163 TS_COMM_LEN = 0x20 + UBI_IOCECNFO = 0xc01c6f06 UDF_SUPER_MAGIC = 0x15013346 UDP_CORK = 0x1 UDP_ENCAP = 0x64 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go index 75207613..a8c421e2 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go @@ -372,6 +372,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x12 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x14 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x14 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go index c68acda5..9a88d181 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go @@ -373,6 +373,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x12 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x14 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x14 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go index a8c607ab..7cb6a867 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go @@ -378,6 +378,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x12 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x14 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x14 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go index 18563dd8..d0ecd2c5 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go @@ -371,6 +371,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x12 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x14 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x14 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go index 22912cda..7a2940ae 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go @@ -365,6 +365,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x12 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x14 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x14 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go index 29344eb3..d14ca8f2 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go @@ -371,6 +371,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x1004 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x1006 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x1006 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go index 20d51fb9..2da1bac1 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go @@ -371,6 +371,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x1004 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x1006 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x1006 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go index 321b6090..28727514 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go @@ -371,6 +371,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x1004 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x1006 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x1006 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go index 9bacdf1e..7f287b54 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go @@ -371,6 +371,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x1004 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x1006 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x1006 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go index c2242726..7e5f9e6a 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go @@ -426,6 +426,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x10 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x12 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x12 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go index 6270c8ee..37c87952 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go @@ -430,6 +430,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x10 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x12 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x12 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go index 9966c194..52201336 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go @@ -430,6 +430,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x10 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x12 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x12 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go index 848e5fcc..4bfe2b5b 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go @@ -362,6 +362,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x12 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x14 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x14 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go index 669b2adb..e3cffb86 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go @@ -434,6 +434,7 @@ const ( SO_RCVBUFFORCE = 0x21 SO_RCVLOWAT = 0x12 SO_RCVMARK = 0x4b + SO_RCVPRIORITY = 0x52 SO_RCVTIMEO = 0x14 SO_RCVTIMEO_NEW = 0x42 SO_RCVTIMEO_OLD = 0x14 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index 4834e575..c219c8db 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -473,6 +473,7 @@ const ( SO_RCVBUFFORCE = 0x100b SO_RCVLOWAT = 0x800 SO_RCVMARK = 0x54 + SO_RCVPRIORITY = 0x5b SO_RCVTIMEO = 0x2000 SO_RCVTIMEO_NEW = 0x44 SO_RCVTIMEO_OLD = 0x2000 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index a46abe64..8bcac283 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -114,7 +114,7 @@ type Statx_t struct { Atomic_write_unit_min uint32 Atomic_write_unit_max uint32 Atomic_write_segments_max uint32 - _ [1]uint32 + Dio_read_offset_align uint32 _ [9]uint64 } @@ -2226,8 +2226,11 @@ const ( NFT_PAYLOAD_LL_HEADER = 0x0 NFT_PAYLOAD_NETWORK_HEADER = 0x1 NFT_PAYLOAD_TRANSPORT_HEADER = 0x2 + NFT_PAYLOAD_INNER_HEADER = 0x3 + NFT_PAYLOAD_TUN_HEADER = 0x4 NFT_PAYLOAD_CSUM_NONE = 0x0 NFT_PAYLOAD_CSUM_INET = 0x1 + NFT_PAYLOAD_CSUM_SCTP = 0x2 NFT_PAYLOAD_L4CSUM_PSEUDOHDR = 0x1 NFTA_PAYLOAD_UNSPEC = 0x0 NFTA_PAYLOAD_DREG = 0x1 @@ -3802,7 +3805,16 @@ const ( ETHTOOL_MSG_PSE_GET = 0x24 ETHTOOL_MSG_PSE_SET = 0x25 ETHTOOL_MSG_RSS_GET = 0x26 - ETHTOOL_MSG_USER_MAX = 0x2d + ETHTOOL_MSG_PLCA_GET_CFG = 0x27 + ETHTOOL_MSG_PLCA_SET_CFG = 0x28 + ETHTOOL_MSG_PLCA_GET_STATUS = 0x29 + ETHTOOL_MSG_MM_GET = 0x2a + ETHTOOL_MSG_MM_SET = 0x2b + ETHTOOL_MSG_MODULE_FW_FLASH_ACT = 0x2c + ETHTOOL_MSG_PHY_GET = 0x2d + ETHTOOL_MSG_TSCONFIG_GET = 0x2e + ETHTOOL_MSG_TSCONFIG_SET = 0x2f + ETHTOOL_MSG_USER_MAX = 0x2f ETHTOOL_MSG_KERNEL_NONE = 0x0 ETHTOOL_MSG_STRSET_GET_REPLY = 0x1 ETHTOOL_MSG_LINKINFO_GET_REPLY = 0x2 @@ -3842,7 +3854,17 @@ const ( ETHTOOL_MSG_MODULE_NTF = 0x24 ETHTOOL_MSG_PSE_GET_REPLY = 0x25 ETHTOOL_MSG_RSS_GET_REPLY = 0x26 - ETHTOOL_MSG_KERNEL_MAX = 0x2e + ETHTOOL_MSG_PLCA_GET_CFG_REPLY = 0x27 + ETHTOOL_MSG_PLCA_GET_STATUS_REPLY = 0x28 + ETHTOOL_MSG_PLCA_NTF = 0x29 + ETHTOOL_MSG_MM_GET_REPLY = 0x2a + ETHTOOL_MSG_MM_NTF = 0x2b + ETHTOOL_MSG_MODULE_FW_FLASH_NTF = 0x2c + ETHTOOL_MSG_PHY_GET_REPLY = 0x2d + ETHTOOL_MSG_PHY_NTF = 0x2e + ETHTOOL_MSG_TSCONFIG_GET_REPLY = 0x2f + ETHTOOL_MSG_TSCONFIG_SET_REPLY = 0x30 + ETHTOOL_MSG_KERNEL_MAX = 0x30 ETHTOOL_FLAG_COMPACT_BITSETS = 0x1 ETHTOOL_FLAG_OMIT_REPLY = 0x2 ETHTOOL_FLAG_STATS = 0x4 @@ -3949,7 +3971,12 @@ const ( ETHTOOL_A_RINGS_TCP_DATA_SPLIT = 0xb ETHTOOL_A_RINGS_CQE_SIZE = 0xc ETHTOOL_A_RINGS_TX_PUSH = 0xd - ETHTOOL_A_RINGS_MAX = 0x10 + ETHTOOL_A_RINGS_RX_PUSH = 0xe + ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN = 0xf + ETHTOOL_A_RINGS_TX_PUSH_BUF_LEN_MAX = 0x10 + ETHTOOL_A_RINGS_HDS_THRESH = 0x11 + ETHTOOL_A_RINGS_HDS_THRESH_MAX = 0x12 + ETHTOOL_A_RINGS_MAX = 0x12 ETHTOOL_A_CHANNELS_UNSPEC = 0x0 ETHTOOL_A_CHANNELS_HEADER = 0x1 ETHTOOL_A_CHANNELS_RX_MAX = 0x2 @@ -4015,7 +4042,9 @@ const ( ETHTOOL_A_TSINFO_TX_TYPES = 0x3 ETHTOOL_A_TSINFO_RX_FILTERS = 0x4 ETHTOOL_A_TSINFO_PHC_INDEX = 0x5 - ETHTOOL_A_TSINFO_MAX = 0x6 + ETHTOOL_A_TSINFO_STATS = 0x6 + ETHTOOL_A_TSINFO_HWTSTAMP_PROVIDER = 0x7 + ETHTOOL_A_TSINFO_MAX = 0x7 ETHTOOL_A_CABLE_TEST_UNSPEC = 0x0 ETHTOOL_A_CABLE_TEST_HEADER = 0x1 ETHTOOL_A_CABLE_TEST_MAX = 0x1 @@ -4613,6 +4642,7 @@ const ( NL80211_ATTR_AKM_SUITES = 0x4c NL80211_ATTR_AP_ISOLATE = 0x60 NL80211_ATTR_AP_SETTINGS_FLAGS = 0x135 + NL80211_ATTR_ASSOC_SPP_AMSDU = 0x14a NL80211_ATTR_AUTH_DATA = 0x9c NL80211_ATTR_AUTH_TYPE = 0x35 NL80211_ATTR_BANDS = 0xef @@ -4623,6 +4653,7 @@ const ( NL80211_ATTR_BSS_BASIC_RATES = 0x24 NL80211_ATTR_BSS = 0x2f NL80211_ATTR_BSS_CTS_PROT = 0x1c + NL80211_ATTR_BSS_DUMP_INCLUDE_USE_DATA = 0x147 NL80211_ATTR_BSS_HT_OPMODE = 0x6d NL80211_ATTR_BSSID = 0xf5 NL80211_ATTR_BSS_SELECT = 0xe3 @@ -4682,6 +4713,7 @@ const ( NL80211_ATTR_DTIM_PERIOD = 0xd NL80211_ATTR_DURATION = 0x57 NL80211_ATTR_EHT_CAPABILITY = 0x136 + NL80211_ATTR_EMA_RNR_ELEMS = 0x145 NL80211_ATTR_EML_CAPABILITY = 0x13d NL80211_ATTR_EXT_CAPA = 0xa9 NL80211_ATTR_EXT_CAPA_MASK = 0xaa @@ -4717,6 +4749,7 @@ const ( NL80211_ATTR_HIDDEN_SSID = 0x7e NL80211_ATTR_HT_CAPABILITY = 0x1f NL80211_ATTR_HT_CAPABILITY_MASK = 0x94 + NL80211_ATTR_HW_TIMESTAMP_ENABLED = 0x144 NL80211_ATTR_IE_ASSOC_RESP = 0x80 NL80211_ATTR_IE = 0x2a NL80211_ATTR_IE_PROBE_RESP = 0x7f @@ -4747,9 +4780,10 @@ const ( NL80211_ATTR_MAC_HINT = 0xc8 NL80211_ATTR_MAC_MASK = 0xd7 NL80211_ATTR_MAX_AP_ASSOC_STA = 0xca - NL80211_ATTR_MAX = 0x14d + NL80211_ATTR_MAX = 0x150 NL80211_ATTR_MAX_CRIT_PROT_DURATION = 0xb4 NL80211_ATTR_MAX_CSA_COUNTERS = 0xce + NL80211_ATTR_MAX_HW_TIMESTAMP_PEERS = 0x143 NL80211_ATTR_MAX_MATCH_SETS = 0x85 NL80211_ATTR_MAX_NUM_AKM_SUITES = 0x13c NL80211_ATTR_MAX_NUM_PMKIDS = 0x56 @@ -4774,9 +4808,12 @@ const ( NL80211_ATTR_MGMT_SUBTYPE = 0x29 NL80211_ATTR_MLD_ADDR = 0x13a NL80211_ATTR_MLD_CAPA_AND_OPS = 0x13e + NL80211_ATTR_MLO_LINK_DISABLED = 0x146 NL80211_ATTR_MLO_LINK_ID = 0x139 NL80211_ATTR_MLO_LINKS = 0x138 NL80211_ATTR_MLO_SUPPORT = 0x13b + NL80211_ATTR_MLO_TTLM_DLINK = 0x148 + NL80211_ATTR_MLO_TTLM_ULINK = 0x149 NL80211_ATTR_MNTR_FLAGS = 0x17 NL80211_ATTR_MPATH_INFO = 0x1b NL80211_ATTR_MPATH_NEXT_HOP = 0x1a @@ -4809,12 +4846,14 @@ const ( NL80211_ATTR_PORT_AUTHORIZED = 0x103 NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN = 0x5 NL80211_ATTR_POWER_RULE_MAX_EIRP = 0x6 + NL80211_ATTR_POWER_RULE_PSD = 0x8 NL80211_ATTR_PREV_BSSID = 0x4f NL80211_ATTR_PRIVACY = 0x46 NL80211_ATTR_PROBE_RESP = 0x91 NL80211_ATTR_PROBE_RESP_OFFLOAD = 0x90 NL80211_ATTR_PROTOCOL_FEATURES = 0xad NL80211_ATTR_PS_STATE = 0x5d + NL80211_ATTR_PUNCT_BITMAP = 0x142 NL80211_ATTR_QOS_MAP = 0xc7 NL80211_ATTR_RADAR_BACKGROUND = 0x134 NL80211_ATTR_RADAR_EVENT = 0xa8 @@ -4943,7 +4982,9 @@ const ( NL80211_ATTR_WIPHY_FREQ = 0x26 NL80211_ATTR_WIPHY_FREQ_HINT = 0xc9 NL80211_ATTR_WIPHY_FREQ_OFFSET = 0x122 + NL80211_ATTR_WIPHY_INTERFACE_COMBINATIONS = 0x14c NL80211_ATTR_WIPHY_NAME = 0x2 + NL80211_ATTR_WIPHY_RADIOS = 0x14b NL80211_ATTR_WIPHY_RETRY_LONG = 0x3e NL80211_ATTR_WIPHY_RETRY_SHORT = 0x3d NL80211_ATTR_WIPHY_RTS_THRESHOLD = 0x40 @@ -4978,6 +5019,8 @@ const ( NL80211_BAND_ATTR_IFTYPE_DATA = 0x9 NL80211_BAND_ATTR_MAX = 0xd NL80211_BAND_ATTR_RATES = 0x2 + NL80211_BAND_ATTR_S1G_CAPA = 0xd + NL80211_BAND_ATTR_S1G_MCS_NSS_SET = 0xc NL80211_BAND_ATTR_VHT_CAPA = 0x8 NL80211_BAND_ATTR_VHT_MCS_SET = 0x7 NL80211_BAND_IFTYPE_ATTR_EHT_CAP_MAC = 0x8 @@ -5001,6 +5044,10 @@ const ( NL80211_BSS_BEACON_INTERVAL = 0x4 NL80211_BSS_BEACON_TSF = 0xd NL80211_BSS_BSSID = 0x1 + NL80211_BSS_CANNOT_USE_6GHZ_PWR_MISMATCH = 0x2 + NL80211_BSS_CANNOT_USE_NSTR_NONPRIMARY = 0x1 + NL80211_BSS_CANNOT_USE_REASONS = 0x18 + NL80211_BSS_CANNOT_USE_UHB_PWR_MISMATCH = 0x2 NL80211_BSS_CAPABILITY = 0x5 NL80211_BSS_CHAIN_SIGNAL = 0x13 NL80211_BSS_CHAN_WIDTH_10 = 0x1 @@ -5032,6 +5079,9 @@ const ( NL80211_BSS_STATUS = 0x9 NL80211_BSS_STATUS_IBSS_JOINED = 0x2 NL80211_BSS_TSF = 0x3 + NL80211_BSS_USE_FOR = 0x17 + NL80211_BSS_USE_FOR_MLD_LINK = 0x2 + NL80211_BSS_USE_FOR_NORMAL = 0x1 NL80211_CHAN_HT20 = 0x1 NL80211_CHAN_HT40MINUS = 0x2 NL80211_CHAN_HT40PLUS = 0x3 @@ -5117,7 +5167,8 @@ const ( NL80211_CMD_LEAVE_IBSS = 0x2c NL80211_CMD_LEAVE_MESH = 0x45 NL80211_CMD_LEAVE_OCB = 0x6d - NL80211_CMD_MAX = 0x9b + NL80211_CMD_LINKS_REMOVED = 0x9a + NL80211_CMD_MAX = 0x9d NL80211_CMD_MICHAEL_MIC_FAILURE = 0x29 NL80211_CMD_MODIFY_LINK_STA = 0x97 NL80211_CMD_NAN_MATCH = 0x78 @@ -5161,6 +5212,7 @@ const ( NL80211_CMD_SET_COALESCE = 0x65 NL80211_CMD_SET_CQM = 0x3f NL80211_CMD_SET_FILS_AAD = 0x92 + NL80211_CMD_SET_HW_TIMESTAMP = 0x99 NL80211_CMD_SET_INTERFACE = 0x6 NL80211_CMD_SET_KEY = 0xa NL80211_CMD_SET_MAC_ACL = 0x5d @@ -5180,6 +5232,7 @@ const ( NL80211_CMD_SET_SAR_SPECS = 0x8c NL80211_CMD_SET_STATION = 0x12 NL80211_CMD_SET_TID_CONFIG = 0x89 + NL80211_CMD_SET_TID_TO_LINK_MAPPING = 0x9b NL80211_CMD_SET_TX_BITRATE_MASK = 0x39 NL80211_CMD_SET_WDS_PEER = 0x42 NL80211_CMD_SET_WIPHY = 0x2 @@ -5247,6 +5300,7 @@ const ( NL80211_EXT_FEATURE_AIRTIME_FAIRNESS = 0x21 NL80211_EXT_FEATURE_AP_PMKSA_CACHING = 0x22 NL80211_EXT_FEATURE_AQL = 0x28 + NL80211_EXT_FEATURE_AUTH_AND_DEAUTH_RANDOM_TA = 0x40 NL80211_EXT_FEATURE_BEACON_PROTECTION_CLIENT = 0x2e NL80211_EXT_FEATURE_BEACON_PROTECTION = 0x29 NL80211_EXT_FEATURE_BEACON_RATE_HE = 0x36 @@ -5262,6 +5316,7 @@ const ( NL80211_EXT_FEATURE_CQM_RSSI_LIST = 0xd NL80211_EXT_FEATURE_DATA_ACK_SIGNAL_SUPPORT = 0x1b NL80211_EXT_FEATURE_DEL_IBSS_STA = 0x2c + NL80211_EXT_FEATURE_DFS_CONCURRENT = 0x43 NL80211_EXT_FEATURE_DFS_OFFLOAD = 0x19 NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER = 0x20 NL80211_EXT_FEATURE_EXT_KEY_ID = 0x24 @@ -5281,9 +5336,12 @@ const ( NL80211_EXT_FEATURE_OCE_PROBE_REQ_DEFERRAL_SUPPRESSION = 0x14 NL80211_EXT_FEATURE_OCE_PROBE_REQ_HIGH_TX_RATE = 0x13 NL80211_EXT_FEATURE_OPERATING_CHANNEL_VALIDATION = 0x31 + NL80211_EXT_FEATURE_OWE_OFFLOAD_AP = 0x42 + NL80211_EXT_FEATURE_OWE_OFFLOAD = 0x41 NL80211_EXT_FEATURE_POWERED_ADDR_CHANGE = 0x3d NL80211_EXT_FEATURE_PROTECTED_TWT = 0x2b NL80211_EXT_FEATURE_PROT_RANGE_NEGO_AND_MEASURE = 0x39 + NL80211_EXT_FEATURE_PUNCT = 0x3e NL80211_EXT_FEATURE_RADAR_BACKGROUND = 0x3c NL80211_EXT_FEATURE_RRM = 0x1 NL80211_EXT_FEATURE_SAE_OFFLOAD_AP = 0x33 @@ -5295,8 +5353,10 @@ const ( NL80211_EXT_FEATURE_SCHED_SCAN_BAND_SPECIFIC_RSSI_THOLD = 0x23 NL80211_EXT_FEATURE_SCHED_SCAN_RELATIVE_RSSI = 0xc NL80211_EXT_FEATURE_SECURE_LTF = 0x37 + NL80211_EXT_FEATURE_SECURE_NAN = 0x3f NL80211_EXT_FEATURE_SECURE_RTT = 0x38 NL80211_EXT_FEATURE_SET_SCAN_DWELL = 0x5 + NL80211_EXT_FEATURE_SPP_AMSDU_SUPPORT = 0x44 NL80211_EXT_FEATURE_STA_TX_PWR = 0x25 NL80211_EXT_FEATURE_TXQS = 0x1c NL80211_EXT_FEATURE_UNSOL_BCAST_PROBE_RESP = 0x35 @@ -5343,7 +5403,10 @@ const ( NL80211_FREQUENCY_ATTR_2MHZ = 0x16 NL80211_FREQUENCY_ATTR_4MHZ = 0x17 NL80211_FREQUENCY_ATTR_8MHZ = 0x18 + NL80211_FREQUENCY_ATTR_ALLOW_6GHZ_VLP_AP = 0x21 + NL80211_FREQUENCY_ATTR_CAN_MONITOR = 0x20 NL80211_FREQUENCY_ATTR_DFS_CAC_TIME = 0xd + NL80211_FREQUENCY_ATTR_DFS_CONCURRENT = 0x1d NL80211_FREQUENCY_ATTR_DFS_STATE = 0x7 NL80211_FREQUENCY_ATTR_DFS_TIME = 0x8 NL80211_FREQUENCY_ATTR_DISABLED = 0x2 @@ -5357,6 +5420,8 @@ const ( NL80211_FREQUENCY_ATTR_NO_160MHZ = 0xc NL80211_FREQUENCY_ATTR_NO_20MHZ = 0x10 NL80211_FREQUENCY_ATTR_NO_320MHZ = 0x1a + NL80211_FREQUENCY_ATTR_NO_6GHZ_AFC_CLIENT = 0x1f + NL80211_FREQUENCY_ATTR_NO_6GHZ_VLP_CLIENT = 0x1e NL80211_FREQUENCY_ATTR_NO_80MHZ = 0xb NL80211_FREQUENCY_ATTR_NO_EHT = 0x1b NL80211_FREQUENCY_ATTR_NO_HE = 0x13 @@ -5364,8 +5429,11 @@ const ( NL80211_FREQUENCY_ATTR_NO_HT40_PLUS = 0xa NL80211_FREQUENCY_ATTR_NO_IBSS = 0x3 NL80211_FREQUENCY_ATTR_NO_IR = 0x3 + NL80211_FREQUENCY_ATTR_NO_UHB_AFC_CLIENT = 0x1f + NL80211_FREQUENCY_ATTR_NO_UHB_VLP_CLIENT = 0x1e NL80211_FREQUENCY_ATTR_OFFSET = 0x14 NL80211_FREQUENCY_ATTR_PASSIVE_SCAN = 0x3 + NL80211_FREQUENCY_ATTR_PSD = 0x1c NL80211_FREQUENCY_ATTR_RADAR = 0x5 NL80211_FREQUENCY_ATTR_WMM = 0x12 NL80211_FTM_RESP_ATTR_CIVICLOC = 0x3 @@ -5430,6 +5498,7 @@ const ( NL80211_IFTYPE_STATION = 0x2 NL80211_IFTYPE_UNSPECIFIED = 0x0 NL80211_IFTYPE_WDS = 0x5 + NL80211_KCK_EXT_LEN_32 = 0x20 NL80211_KCK_EXT_LEN = 0x18 NL80211_KCK_LEN = 0x10 NL80211_KEK_EXT_LEN = 0x20 @@ -5458,6 +5527,7 @@ const ( NL80211_MAX_SUPP_HT_RATES = 0x4d NL80211_MAX_SUPP_RATES = 0x20 NL80211_MAX_SUPP_REG_RULES = 0x80 + NL80211_MAX_SUPP_SELECTORS = 0x80 NL80211_MBSSID_CONFIG_ATTR_EMA = 0x5 NL80211_MBSSID_CONFIG_ATTR_INDEX = 0x3 NL80211_MBSSID_CONFIG_ATTR_MAX = 0x5 @@ -5703,11 +5773,16 @@ const ( NL80211_RADAR_PRE_CAC_EXPIRED = 0x4 NL80211_RATE_INFO_10_MHZ_WIDTH = 0xb NL80211_RATE_INFO_160_MHZ_WIDTH = 0xa + NL80211_RATE_INFO_16_MHZ_WIDTH = 0x1d + NL80211_RATE_INFO_1_MHZ_WIDTH = 0x19 + NL80211_RATE_INFO_2_MHZ_WIDTH = 0x1a NL80211_RATE_INFO_320_MHZ_WIDTH = 0x12 NL80211_RATE_INFO_40_MHZ_WIDTH = 0x3 + NL80211_RATE_INFO_4_MHZ_WIDTH = 0x1b NL80211_RATE_INFO_5_MHZ_WIDTH = 0xc NL80211_RATE_INFO_80_MHZ_WIDTH = 0x8 NL80211_RATE_INFO_80P80_MHZ_WIDTH = 0x9 + NL80211_RATE_INFO_8_MHZ_WIDTH = 0x1c NL80211_RATE_INFO_BITRATE32 = 0x5 NL80211_RATE_INFO_BITRATE = 0x1 NL80211_RATE_INFO_EHT_GI_0_8 = 0x0 @@ -5753,6 +5828,8 @@ const ( NL80211_RATE_INFO_HE_RU_ALLOC = 0x11 NL80211_RATE_INFO_MAX = 0x1d NL80211_RATE_INFO_MCS = 0x2 + NL80211_RATE_INFO_S1G_MCS = 0x17 + NL80211_RATE_INFO_S1G_NSS = 0x18 NL80211_RATE_INFO_SHORT_GI = 0x4 NL80211_RATE_INFO_VHT_MCS = 0x6 NL80211_RATE_INFO_VHT_NSS = 0x7 @@ -5770,14 +5847,19 @@ const ( NL80211_REKEY_DATA_KEK = 0x1 NL80211_REKEY_DATA_REPLAY_CTR = 0x3 NL80211_REPLAY_CTR_LEN = 0x8 + NL80211_RRF_ALLOW_6GHZ_VLP_AP = 0x1000000 NL80211_RRF_AUTO_BW = 0x800 NL80211_RRF_DFS = 0x10 + NL80211_RRF_DFS_CONCURRENT = 0x200000 NL80211_RRF_GO_CONCURRENT = 0x1000 NL80211_RRF_IR_CONCURRENT = 0x1000 NL80211_RRF_NO_160MHZ = 0x10000 NL80211_RRF_NO_320MHZ = 0x40000 + NL80211_RRF_NO_6GHZ_AFC_CLIENT = 0x800000 + NL80211_RRF_NO_6GHZ_VLP_CLIENT = 0x400000 NL80211_RRF_NO_80MHZ = 0x8000 NL80211_RRF_NO_CCK = 0x2 + NL80211_RRF_NO_EHT = 0x80000 NL80211_RRF_NO_HE = 0x20000 NL80211_RRF_NO_HT40 = 0x6000 NL80211_RRF_NO_HT40MINUS = 0x2000 @@ -5788,7 +5870,10 @@ const ( NL80211_RRF_NO_IR = 0x80 NL80211_RRF_NO_OFDM = 0x1 NL80211_RRF_NO_OUTDOOR = 0x8 + NL80211_RRF_NO_UHB_AFC_CLIENT = 0x800000 + NL80211_RRF_NO_UHB_VLP_CLIENT = 0x400000 NL80211_RRF_PASSIVE_SCAN = 0x80 + NL80211_RRF_PSD = 0x100000 NL80211_RRF_PTMP_ONLY = 0x40 NL80211_RRF_PTP_ONLY = 0x20 NL80211_RXMGMT_FLAG_ANSWERED = 0x1 @@ -5849,6 +5934,7 @@ const ( NL80211_STA_FLAG_MAX_OLD_API = 0x6 NL80211_STA_FLAG_MFP = 0x4 NL80211_STA_FLAG_SHORT_PREAMBLE = 0x2 + NL80211_STA_FLAG_SPP_AMSDU = 0x8 NL80211_STA_FLAG_TDLS_PEER = 0x6 NL80211_STA_FLAG_WME = 0x3 NL80211_STA_INFO_ACK_SIGNAL_AVG = 0x23 @@ -6007,6 +6093,13 @@ const ( NL80211_VHT_CAPABILITY_LEN = 0xc NL80211_VHT_NSS_MAX = 0x8 NL80211_WIPHY_NAME_MAXLEN = 0x40 + NL80211_WIPHY_RADIO_ATTR_FREQ_RANGE = 0x2 + NL80211_WIPHY_RADIO_ATTR_INDEX = 0x1 + NL80211_WIPHY_RADIO_ATTR_INTERFACE_COMBINATION = 0x3 + NL80211_WIPHY_RADIO_ATTR_MAX = 0x4 + NL80211_WIPHY_RADIO_FREQ_ATTR_END = 0x2 + NL80211_WIPHY_RADIO_FREQ_ATTR_MAX = 0x2 + NL80211_WIPHY_RADIO_FREQ_ATTR_START = 0x1 NL80211_WMMR_AIFSN = 0x3 NL80211_WMMR_CW_MAX = 0x2 NL80211_WMMR_CW_MIN = 0x1 @@ -6038,6 +6131,7 @@ const ( NL80211_WOWLAN_TRIG_PKT_PATTERN = 0x4 NL80211_WOWLAN_TRIG_RFKILL_RELEASE = 0x9 NL80211_WOWLAN_TRIG_TCP_CONNECTION = 0xe + NL80211_WOWLAN_TRIG_UNPROTECTED_DEAUTH_DISASSOC = 0x14 NL80211_WOWLAN_TRIG_WAKEUP_PKT_80211 = 0xa NL80211_WOWLAN_TRIG_WAKEUP_PKT_80211_LEN = 0xb NL80211_WOWLAN_TRIG_WAKEUP_PKT_8023 = 0xc diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go index fd402da4..62db85f6 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_386.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_386.go @@ -285,10 +285,16 @@ type Taskstats struct { _ [4]byte Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]int8 @@ -324,11 +330,17 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 _ [4]byte Ac_tgetime uint64 @@ -336,8 +348,12 @@ type Taskstats struct { Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint32 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go index eb7a5e18..7d89d648 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go @@ -300,10 +300,16 @@ type Taskstats struct { Ac_nice uint8 Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]int8 @@ -338,19 +344,29 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 Ac_tgetime uint64 Ac_exe_dev uint64 Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint64 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go index d78ac108..9c0b39ee 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm.go @@ -276,10 +276,16 @@ type Taskstats struct { _ [4]byte Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]uint8 @@ -315,11 +321,17 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 _ [4]byte Ac_tgetime uint64 @@ -327,8 +339,12 @@ type Taskstats struct { Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint32 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go index cd06d47f..de9c7ff3 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go @@ -279,10 +279,16 @@ type Taskstats struct { Ac_nice uint8 Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]int8 @@ -317,19 +323,29 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 Ac_tgetime uint64 Ac_exe_dev uint64 Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint64 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go index 2f28fe26..2336bd2b 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go @@ -280,10 +280,16 @@ type Taskstats struct { Ac_nice uint8 Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]int8 @@ -318,19 +324,29 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 Ac_tgetime uint64 Ac_exe_dev uint64 Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint64 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go index 71d6cac2..4711f0be 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips.go @@ -281,10 +281,16 @@ type Taskstats struct { _ [4]byte Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]int8 @@ -320,11 +326,17 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 _ [4]byte Ac_tgetime uint64 @@ -332,8 +344,12 @@ type Taskstats struct { Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint32 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go index 8596d453..ab99a34b 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go @@ -282,10 +282,16 @@ type Taskstats struct { Ac_nice uint8 Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]int8 @@ -320,19 +326,29 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 Ac_tgetime uint64 Ac_exe_dev uint64 Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint64 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go index cd60ea18..04c9866e 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go @@ -282,10 +282,16 @@ type Taskstats struct { Ac_nice uint8 Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]int8 @@ -320,19 +326,29 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 Ac_tgetime uint64 Ac_exe_dev uint64 Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint64 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go index b0ae420c..60aa69f6 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go @@ -281,10 +281,16 @@ type Taskstats struct { _ [4]byte Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]int8 @@ -320,11 +326,17 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 _ [4]byte Ac_tgetime uint64 @@ -332,8 +344,12 @@ type Taskstats struct { Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint32 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go index 83597287..cb4fad78 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go @@ -288,10 +288,16 @@ type Taskstats struct { _ [4]byte Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]uint8 @@ -327,11 +333,17 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 _ [4]byte Ac_tgetime uint64 @@ -339,8 +351,12 @@ type Taskstats struct { Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint32 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go index 69eb6a5c..60272cfc 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go @@ -289,10 +289,16 @@ type Taskstats struct { Ac_nice uint8 Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]uint8 @@ -327,19 +333,29 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 Ac_tgetime uint64 Ac_exe_dev uint64 Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint64 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go index 5f583cb6..3f5b91bc 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go @@ -289,10 +289,16 @@ type Taskstats struct { Ac_nice uint8 Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]uint8 @@ -327,19 +333,29 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 Ac_tgetime uint64 Ac_exe_dev uint64 Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint64 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go index ad05b51a..51550f15 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go @@ -307,10 +307,16 @@ type Taskstats struct { Ac_nice uint8 Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]uint8 @@ -345,19 +351,29 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 Ac_tgetime uint64 Ac_exe_dev uint64 Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint64 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go index cf3ce900..3239e50e 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go @@ -302,10 +302,16 @@ type Taskstats struct { Ac_nice uint8 Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]int8 @@ -340,19 +346,29 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 Ac_tgetime uint64 Ac_exe_dev uint64 Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint64 diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go b/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go index 590b5673..faf20027 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go @@ -284,10 +284,16 @@ type Taskstats struct { Ac_nice uint8 Cpu_count uint64 Cpu_delay_total uint64 + Cpu_delay_max uint64 + Cpu_delay_min uint64 Blkio_count uint64 Blkio_delay_total uint64 + Blkio_delay_max uint64 + Blkio_delay_min uint64 Swapin_count uint64 Swapin_delay_total uint64 + Swapin_delay_max uint64 + Swapin_delay_min uint64 Cpu_run_real_total uint64 Cpu_run_virtual_total uint64 Ac_comm [32]int8 @@ -322,19 +328,29 @@ type Taskstats struct { Cpu_scaled_run_real_total uint64 Freepages_count uint64 Freepages_delay_total uint64 + Freepages_delay_max uint64 + Freepages_delay_min uint64 Thrashing_count uint64 Thrashing_delay_total uint64 + Thrashing_delay_max uint64 + Thrashing_delay_min uint64 Ac_btime64 uint64 Compact_count uint64 Compact_delay_total uint64 + Compact_delay_max uint64 + Compact_delay_min uint64 Ac_tgid uint32 Ac_tgetime uint64 Ac_exe_dev uint64 Ac_exe_inode uint64 Wpcopy_count uint64 Wpcopy_delay_total uint64 + Wpcopy_delay_max uint64 + Wpcopy_delay_min uint64 Irq_count uint64 Irq_delay_total uint64 + Irq_delay_max uint64 + Irq_delay_min uint64 } type cpuMask uint64 diff --git a/vendor/google.golang.org/grpc/CONTRIBUTING.md b/vendor/google.golang.org/grpc/CONTRIBUTING.md index d9bfa6e1..1de0ce66 100644 --- a/vendor/google.golang.org/grpc/CONTRIBUTING.md +++ b/vendor/google.golang.org/grpc/CONTRIBUTING.md @@ -1,73 +1,102 @@ # How to contribute -We definitely welcome your patches and contributions to gRPC! Please read the gRPC -organization's [governance rules](https://github.com/grpc/grpc-community/blob/master/governance.md) -and [contribution guidelines](https://github.com/grpc/grpc-community/blob/master/CONTRIBUTING.md) before proceeding. +We welcome your patches and contributions to gRPC! Please read the gRPC +organization's [governance +rules](https://github.com/grpc/grpc-community/blob/master/governance.md) before +proceeding. If you are new to GitHub, please start by reading [Pull Request howto](https://help.github.com/articles/about-pull-requests/) ## Legal requirements In order to protect both you and ourselves, you will need to sign the -[Contributor License Agreement](https://identity.linuxfoundation.org/projects/cncf). +[Contributor License +Agreement](https://identity.linuxfoundation.org/projects/cncf). When you create +your first PR, a link will be added as a comment that contains the steps needed +to complete this process. -## Guidelines for Pull Requests -How to get your contributions merged smoothly and quickly. +## Getting Started -- Create **small PRs** that are narrowly focused on **addressing a single - concern**. We often times receive PRs that are trying to fix several things at - a time, but only one fix is considered acceptable, nothing gets merged and - both author's & review's time is wasted. Create more PRs to address different - concerns and everyone will be happy. +A great way to start is by searching through our open issues. [Unassigned issues +labeled as "help +wanted"](https://github.com/grpc/grpc-go/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20label%3A%22Status%3A%20Help%20Wanted%22%20no%3Aassignee) +are especially nice for first-time contributors, as they should be well-defined +problems that already have agreed-upon solutions. -- If you are searching for features to work on, issues labeled [Status: Help - Wanted](https://github.com/grpc/grpc-go/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22Status%3A+Help+Wanted%22) - is a great place to start. These issues are well-documented and usually can be - resolved with a single pull request. +## Code Style -- If you are adding a new file, make sure it has the copyright message template - at the top as a comment. You can copy over the message from an existing file - and update the year. +We follow [Google's published Go style +guide](https://google.github.io/styleguide/go/). Note that there are three +primary documents that make up this style guide; please follow them as closely +as possible. If a reviewer recommends something that contradicts those +guidelines, there may be valid reasons to do so, but it should be rare. -- The grpc package should only depend on standard Go packages and a small number - of exceptions. If your contribution introduces new dependencies which are NOT - in the [list](https://godoc.org/google.golang.org/grpc?imports), you need a - discussion with gRPC-Go authors and consultants. +## Guidelines for Pull Requests -- For speculative changes, consider opening an issue and discussing it first. If - you are suggesting a behavioral or API change, consider starting with a [gRFC - proposal](https://github.com/grpc/proposal). +How to get your contributions merged smoothly and quickly: + +- Create **small PRs** that are narrowly focused on **addressing a single + concern**. We often receive PRs that attempt to fix several things at the same + time, and if one part of the PR has a problem, that will hold up the entire + PR. + +- For **speculative changes**, consider opening an issue and discussing it + first. If you are suggesting a behavioral or API change, consider starting + with a [gRFC proposal](https://github.com/grpc/proposal). Many new features + that are not bug fixes will require cross-language agreement. + +- If you want to fix **formatting or style**, consider whether your changes are + an obvious improvement or might be considered a personal preference. If a + style change is based on preference, it likely will not be accepted. If it + corrects widely agreed-upon anti-patterns, then please do create a PR and + explain the benefits of the change. + +- For correcting **misspellings**, please be aware that we use some terms that + are sometimes flagged by spell checkers. As an example, "if an only if" is + often written as "iff". Please do not make spelling correction changes unless + you are certain they are misspellings. - Provide a good **PR description** as a record of **what** change is being made and **why** it was made. Link to a GitHub issue if it exists. -- If you want to fix formatting or style, consider whether your changes are an - obvious improvement or might be considered a personal preference. If a style - change is based on preference, it likely will not be accepted. If it corrects - widely agreed-upon anti-patterns, then please do create a PR and explain the - benefits of the change. +- Maintain a **clean commit history** and use **meaningful commit messages**. + PRs with messy commit histories are difficult to review and won't be merged. + Before sending your PR, ensure your changes are based on top of the latest + `upstream/master` commits, and avoid rebasing in the middle of a code review. + You should **never use `git push -f`** unless absolutely necessary during a + review, as it can interfere with GitHub's tracking of comments. -- Unless your PR is trivial, you should expect there will be reviewer comments - that you'll need to address before merging. We'll mark it as `Status: Requires - Reporter Clarification` if we expect you to respond to these comments in a - timely manner. If the PR remains inactive for 6 days, it will be marked as - `stale` and automatically close 7 days after that if we don't hear back from - you. +- **All tests need to be passing** before your change can be merged. We + recommend you run tests locally before creating your PR to catch breakages + early on: -- Maintain **clean commit history** and use **meaningful commit messages**. PRs - with messy commit history are difficult to review and won't be merged. Use - `rebase -i upstream/master` to curate your commit history and/or to bring in - latest changes from master (but avoid rebasing in the middle of a code - review). + - `./scripts/vet.sh` to catch vet errors. + - `go test -cpu 1,4 -timeout 7m ./...` to run the tests. + - `go test -race -cpu 1,4 -timeout 7m ./...` to run tests in race mode. -- Keep your PR up to date with upstream/master (if there are merge conflicts, we - can't really merge your change). + Note that we have a multi-module repo, so `go test` commands may need to be + run from the root of each module in order to cause all tests to run. -- **All tests need to be passing** before your change can be merged. We - recommend you **run tests locally** before creating your PR to catch breakages - early on. - - `./scripts/vet.sh` to catch vet errors - - `go test -cpu 1,4 -timeout 7m ./...` to run the tests - - `go test -race -cpu 1,4 -timeout 7m ./...` to run tests in race mode + *Alternatively*, you may find it easier to push your changes to your fork on + GitHub, which will trigger a GitHub Actions run that you can use to verify + everything is passing. + +- If you are adding a new file, make sure it has the **copyright message** + template at the top as a comment. You can copy the message from an existing + file and update the year. + +- The grpc package should only depend on standard Go packages and a small number + of exceptions. **If your contribution introduces new dependencies**, you will + need a discussion with gRPC-Go maintainers. A GitHub action check will run on + every PR, and will flag any transitive dependency changes from any public + package. + +- Unless your PR is trivial, you should **expect reviewer comments** that you + will need to address before merging. We'll label the PR as `Status: Requires + Reporter Clarification` if we expect you to respond to these comments in a + timely manner. If the PR remains inactive for 6 days, it will be marked as + `stale`, and we will automatically close it after 7 days if we don't hear back + from you. Please feel free to ping issues or bugs if you do not get a response + within a week. -- Exceptions to the rules can be made if there's a compelling reason for doing so. +- Exceptions to the rules can be made if there's a compelling reason to do so. diff --git a/vendor/google.golang.org/grpc/README.md b/vendor/google.golang.org/grpc/README.md index b572707c..f9a88d59 100644 --- a/vendor/google.golang.org/grpc/README.md +++ b/vendor/google.golang.org/grpc/README.md @@ -32,6 +32,7 @@ import "google.golang.org/grpc" - [Low-level technical docs](Documentation) from this repository - [Performance benchmark][] - [Examples](examples) +- [Contribution guidelines](CONTRIBUTING.md) ## FAQ diff --git a/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go b/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go index 825c3179..b1364a03 100644 --- a/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go +++ b/vendor/google.golang.org/grpc/binarylog/grpc_binarylog_v1/binarylog.pb.go @@ -18,7 +18,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.5 +// protoc-gen-go v1.36.6 // protoc v5.27.1 // source: grpc/binlog/v1/binarylog.proto @@ -858,133 +858,68 @@ func (x *Address) GetIpPort() uint32 { var File_grpc_binlog_v1_binarylog_proto protoreflect.FileDescriptor -var file_grpc_binlog_v1_binarylog_proto_rawDesc = string([]byte{ - 0x0a, 0x1e, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x62, 0x69, 0x6e, 0x6c, 0x6f, 0x67, 0x2f, 0x76, 0x31, - 0x2f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x11, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, - 0x2e, 0x76, 0x31, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbb, 0x07, 0x0a, 0x0c, 0x47, 0x72, 0x70, 0x63, 0x4c, 0x6f, 0x67, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, - 0x17, 0x0a, 0x07, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x17, 0x73, 0x65, 0x71, 0x75, - 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x5f, 0x63, - 0x61, 0x6c, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x73, 0x65, 0x71, 0x75, 0x65, - 0x6e, 0x63, 0x65, 0x49, 0x64, 0x57, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x43, 0x61, 0x6c, 0x6c, 0x12, - 0x3d, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x72, 0x70, 0x63, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x3e, - 0x0a, 0x06, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x72, 0x70, 0x63, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x2e, - 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x52, 0x06, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x12, 0x46, - 0x0a, 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x62, 0x69, 0x6e, - 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x46, 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x00, - 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x36, - 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, - 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x48, 0x00, 0x52, 0x07, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x65, - 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x62, - 0x69, 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x61, 0x69, - 0x6c, 0x65, 0x72, 0x48, 0x00, 0x52, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x12, 0x2b, - 0x0a, 0x11, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, - 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x70, 0x61, 0x79, 0x6c, 0x6f, - 0x61, 0x64, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x70, - 0x65, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, - 0x2e, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x04, 0x70, 0x65, 0x65, 0x72, 0x22, 0xf5, 0x01, 0x0a, 0x09, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x45, 0x56, 0x45, - 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, - 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, - 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, 0x5f, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x10, 0x01, 0x12, - 0x1c, 0x0a, 0x18, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x45, - 0x52, 0x56, 0x45, 0x52, 0x5f, 0x48, 0x45, 0x41, 0x44, 0x45, 0x52, 0x10, 0x02, 0x12, 0x1d, 0x0a, - 0x19, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4c, 0x49, 0x45, - 0x4e, 0x54, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x03, 0x12, 0x1d, 0x0a, 0x19, - 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, - 0x52, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x04, 0x12, 0x20, 0x0a, 0x1c, 0x45, - 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4c, 0x49, 0x45, 0x4e, 0x54, - 0x5f, 0x48, 0x41, 0x4c, 0x46, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x05, 0x12, 0x1d, 0x0a, - 0x19, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x45, 0x52, 0x56, - 0x45, 0x52, 0x5f, 0x54, 0x52, 0x41, 0x49, 0x4c, 0x45, 0x52, 0x10, 0x06, 0x12, 0x15, 0x0a, 0x11, - 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, - 0x4c, 0x10, 0x07, 0x22, 0x42, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x12, 0x12, 0x0a, - 0x0e, 0x4c, 0x4f, 0x47, 0x47, 0x45, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, - 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x4c, 0x4f, 0x47, 0x47, 0x45, 0x52, 0x5f, 0x43, 0x4c, 0x49, 0x45, - 0x4e, 0x54, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x4c, 0x4f, 0x47, 0x47, 0x45, 0x52, 0x5f, 0x53, - 0x45, 0x52, 0x56, 0x45, 0x52, 0x10, 0x02, 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, - 0x61, 0x64, 0x22, 0xbb, 0x01, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x12, 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x62, 0x69, 0x6e, - 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1f, 0x0a, 0x0b, - 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0a, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, - 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x33, 0x0a, 0x07, 0x74, - 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, - 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x22, 0x47, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x12, 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, - 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, - 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xb1, 0x01, 0x0a, 0x07, 0x54, 0x72, - 0x61, 0x69, 0x6c, 0x65, 0x72, 0x12, 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x62, - 0x69, 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1f, - 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x35, 0x0a, - 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67, - 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, - 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, - 0x64, 0x61, 0x74, 0x61, 0x22, 0x42, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x12, 0x36, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x20, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, - 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x37, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x22, 0xb8, 0x01, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x33, 0x0a, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x17, 0x0a, 0x07, - 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x69, - 0x70, 0x50, 0x6f, 0x72, 0x74, 0x22, 0x45, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, - 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, - 0x0d, 0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x50, 0x56, 0x34, 0x10, 0x01, 0x12, 0x0d, - 0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x50, 0x56, 0x36, 0x10, 0x02, 0x12, 0x0d, 0x0a, - 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x49, 0x58, 0x10, 0x03, 0x42, 0x5c, 0x0a, 0x14, - 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, - 0x67, 0x2e, 0x76, 0x31, 0x42, 0x0e, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x4c, 0x6f, 0x67, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x32, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67, - 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x62, - 0x69, 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x62, 0x69, - 0x6e, 0x61, 0x72, 0x79, 0x6c, 0x6f, 0x67, 0x5f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, -}) +const file_grpc_binlog_v1_binarylog_proto_rawDesc = "" + + "\n" + + "\x1egrpc/binlog/v1/binarylog.proto\x12\x11grpc.binarylog.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xbb\a\n" + + "\fGrpcLogEntry\x128\n" + + "\ttimestamp\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x12\x17\n" + + "\acall_id\x18\x02 \x01(\x04R\x06callId\x125\n" + + "\x17sequence_id_within_call\x18\x03 \x01(\x04R\x14sequenceIdWithinCall\x12=\n" + + "\x04type\x18\x04 \x01(\x0e2).grpc.binarylog.v1.GrpcLogEntry.EventTypeR\x04type\x12>\n" + + "\x06logger\x18\x05 \x01(\x0e2&.grpc.binarylog.v1.GrpcLogEntry.LoggerR\x06logger\x12F\n" + + "\rclient_header\x18\x06 \x01(\v2\x1f.grpc.binarylog.v1.ClientHeaderH\x00R\fclientHeader\x12F\n" + + "\rserver_header\x18\a \x01(\v2\x1f.grpc.binarylog.v1.ServerHeaderH\x00R\fserverHeader\x126\n" + + "\amessage\x18\b \x01(\v2\x1a.grpc.binarylog.v1.MessageH\x00R\amessage\x126\n" + + "\atrailer\x18\t \x01(\v2\x1a.grpc.binarylog.v1.TrailerH\x00R\atrailer\x12+\n" + + "\x11payload_truncated\x18\n" + + " \x01(\bR\x10payloadTruncated\x12.\n" + + "\x04peer\x18\v \x01(\v2\x1a.grpc.binarylog.v1.AddressR\x04peer\"\xf5\x01\n" + + "\tEventType\x12\x16\n" + + "\x12EVENT_TYPE_UNKNOWN\x10\x00\x12\x1c\n" + + "\x18EVENT_TYPE_CLIENT_HEADER\x10\x01\x12\x1c\n" + + "\x18EVENT_TYPE_SERVER_HEADER\x10\x02\x12\x1d\n" + + "\x19EVENT_TYPE_CLIENT_MESSAGE\x10\x03\x12\x1d\n" + + "\x19EVENT_TYPE_SERVER_MESSAGE\x10\x04\x12 \n" + + "\x1cEVENT_TYPE_CLIENT_HALF_CLOSE\x10\x05\x12\x1d\n" + + "\x19EVENT_TYPE_SERVER_TRAILER\x10\x06\x12\x15\n" + + "\x11EVENT_TYPE_CANCEL\x10\a\"B\n" + + "\x06Logger\x12\x12\n" + + "\x0eLOGGER_UNKNOWN\x10\x00\x12\x11\n" + + "\rLOGGER_CLIENT\x10\x01\x12\x11\n" + + "\rLOGGER_SERVER\x10\x02B\t\n" + + "\apayload\"\xbb\x01\n" + + "\fClientHeader\x127\n" + + "\bmetadata\x18\x01 \x01(\v2\x1b.grpc.binarylog.v1.MetadataR\bmetadata\x12\x1f\n" + + "\vmethod_name\x18\x02 \x01(\tR\n" + + "methodName\x12\x1c\n" + + "\tauthority\x18\x03 \x01(\tR\tauthority\x123\n" + + "\atimeout\x18\x04 \x01(\v2\x19.google.protobuf.DurationR\atimeout\"G\n" + + "\fServerHeader\x127\n" + + "\bmetadata\x18\x01 \x01(\v2\x1b.grpc.binarylog.v1.MetadataR\bmetadata\"\xb1\x01\n" + + "\aTrailer\x127\n" + + "\bmetadata\x18\x01 \x01(\v2\x1b.grpc.binarylog.v1.MetadataR\bmetadata\x12\x1f\n" + + "\vstatus_code\x18\x02 \x01(\rR\n" + + "statusCode\x12%\n" + + "\x0estatus_message\x18\x03 \x01(\tR\rstatusMessage\x12%\n" + + "\x0estatus_details\x18\x04 \x01(\fR\rstatusDetails\"5\n" + + "\aMessage\x12\x16\n" + + "\x06length\x18\x01 \x01(\rR\x06length\x12\x12\n" + + "\x04data\x18\x02 \x01(\fR\x04data\"B\n" + + "\bMetadata\x126\n" + + "\x05entry\x18\x01 \x03(\v2 .grpc.binarylog.v1.MetadataEntryR\x05entry\"7\n" + + "\rMetadataEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\fR\x05value\"\xb8\x01\n" + + "\aAddress\x123\n" + + "\x04type\x18\x01 \x01(\x0e2\x1f.grpc.binarylog.v1.Address.TypeR\x04type\x12\x18\n" + + "\aaddress\x18\x02 \x01(\tR\aaddress\x12\x17\n" + + "\aip_port\x18\x03 \x01(\rR\x06ipPort\"E\n" + + "\x04Type\x12\x10\n" + + "\fTYPE_UNKNOWN\x10\x00\x12\r\n" + + "\tTYPE_IPV4\x10\x01\x12\r\n" + + "\tTYPE_IPV6\x10\x02\x12\r\n" + + "\tTYPE_UNIX\x10\x03B\\\n" + + "\x14io.grpc.binarylog.v1B\x0eBinaryLogProtoP\x01Z2google.golang.org/grpc/binarylog/grpc_binarylog_v1b\x06proto3" var ( file_grpc_binlog_v1_binarylog_proto_rawDescOnce sync.Once diff --git a/vendor/google.golang.org/grpc/clientconn.go b/vendor/google.golang.org/grpc/clientconn.go index 4f350ca5..cd3eaf8d 100644 --- a/vendor/google.golang.org/grpc/clientconn.go +++ b/vendor/google.golang.org/grpc/clientconn.go @@ -689,22 +689,31 @@ func (cc *ClientConn) Connect() { cc.mu.Unlock() } -// waitForResolvedAddrs blocks until the resolver has provided addresses or the -// context expires. Returns nil unless the context expires first; otherwise -// returns a status error based on the context. -func (cc *ClientConn) waitForResolvedAddrs(ctx context.Context) error { +// waitForResolvedAddrs blocks until the resolver provides addresses or the +// context expires, whichever happens first. +// +// Error is nil unless the context expires first; otherwise returns a status +// error based on the context. +// +// The returned boolean indicates whether it did block or not. If the +// resolution has already happened once before, it returns false without +// blocking. Otherwise, it wait for the resolution and return true if +// resolution has succeeded or return false along with error if resolution has +// failed. +func (cc *ClientConn) waitForResolvedAddrs(ctx context.Context) (bool, error) { // This is on the RPC path, so we use a fast path to avoid the // more-expensive "select" below after the resolver has returned once. if cc.firstResolveEvent.HasFired() { - return nil + return false, nil } + internal.NewStreamWaitingForResolver() select { case <-cc.firstResolveEvent.Done(): - return nil + return true, nil case <-ctx.Done(): - return status.FromContextError(ctx.Err()).Err() + return false, status.FromContextError(ctx.Err()).Err() case <-cc.ctx.Done(): - return ErrClientConnClosing + return false, ErrClientConnClosing } } diff --git a/vendor/google.golang.org/grpc/credentials/credentials.go b/vendor/google.golang.org/grpc/credentials/credentials.go index 665e790b..a63ab606 100644 --- a/vendor/google.golang.org/grpc/credentials/credentials.go +++ b/vendor/google.golang.org/grpc/credentials/credentials.go @@ -120,6 +120,20 @@ type AuthInfo interface { AuthType() string } +// AuthorityValidator validates the authority used to override the `:authority` +// header. This is an optional interface that implementations of AuthInfo can +// implement if they support per-RPC authority overrides. It is invoked when the +// application attempts to override the HTTP/2 `:authority` header using the +// CallAuthority call option. +type AuthorityValidator interface { + // ValidateAuthority checks the authority value used to override the + // `:authority` header. The authority parameter is the override value + // provided by the application via the CallAuthority option. This value + // typically corresponds to the server hostname or endpoint the RPC is + // targeting. It returns non-nil error if the validation fails. + ValidateAuthority(authority string) error +} + // ErrConnDispatched indicates that rawConn has been dispatched out of gRPC // and the caller should not close rawConn. var ErrConnDispatched = errors.New("credentials: rawConn is dispatched out of gRPC") @@ -207,14 +221,32 @@ type RequestInfo struct { AuthInfo AuthInfo } +// requestInfoKey is a struct to be used as the key to store RequestInfo in a +// context. +type requestInfoKey struct{} + // RequestInfoFromContext extracts the RequestInfo from the context if it exists. // // This API is experimental. func RequestInfoFromContext(ctx context.Context) (ri RequestInfo, ok bool) { - ri, ok = icredentials.RequestInfoFromContext(ctx).(RequestInfo) + ri, ok = ctx.Value(requestInfoKey{}).(RequestInfo) return ri, ok } +// NewContextWithRequestInfo creates a new context from ctx and attaches ri to it. +// +// This RequestInfo will be accessible via RequestInfoFromContext. +// +// Intended to be used from tests for PerRPCCredentials implementations (that +// often need to check connection's SecurityLevel). Should not be used from +// non-test code: the gRPC client already prepares a context with the correct +// RequestInfo attached when calling PerRPCCredentials.GetRequestMetadata. +// +// This API is experimental. +func NewContextWithRequestInfo(ctx context.Context, ri RequestInfo) context.Context { + return context.WithValue(ctx, requestInfoKey{}, ri) +} + // ClientHandshakeInfo holds data to be passed to ClientHandshake. This makes // it possible to pass arbitrary data to the handshaker from gRPC, resolver, // balancer etc. Individual credential implementations control the actual diff --git a/vendor/google.golang.org/grpc/credentials/insecure/insecure.go b/vendor/google.golang.org/grpc/credentials/insecure/insecure.go index 4c805c64..93156c0f 100644 --- a/vendor/google.golang.org/grpc/credentials/insecure/insecure.go +++ b/vendor/google.golang.org/grpc/credentials/insecure/insecure.go @@ -30,7 +30,7 @@ import ( // NewCredentials returns a credentials which disables transport security. // // Note that using this credentials with per-RPC credentials which require -// transport security is incompatible and will cause grpc.Dial() to fail. +// transport security is incompatible and will cause RPCs to fail. func NewCredentials() credentials.TransportCredentials { return insecureTC{} } @@ -71,6 +71,12 @@ func (info) AuthType() string { return "insecure" } +// ValidateAuthority allows any value to be overridden for the :authority +// header. +func (info) ValidateAuthority(string) error { + return nil +} + // insecureBundle implements an insecure bundle. // An insecure bundle provides a thin wrapper around insecureTC to support // the credentials.Bundle interface. diff --git a/vendor/google.golang.org/grpc/credentials/tls.go b/vendor/google.golang.org/grpc/credentials/tls.go index bd5fe22b..20f65f7b 100644 --- a/vendor/google.golang.org/grpc/credentials/tls.go +++ b/vendor/google.golang.org/grpc/credentials/tls.go @@ -22,6 +22,7 @@ import ( "context" "crypto/tls" "crypto/x509" + "errors" "fmt" "net" "net/url" @@ -50,6 +51,21 @@ func (t TLSInfo) AuthType() string { return "tls" } +// ValidateAuthority validates the provided authority being used to override the +// :authority header by verifying it against the peer certificates. It returns a +// non-nil error if the validation fails. +func (t TLSInfo) ValidateAuthority(authority string) error { + var errs []error + for _, cert := range t.State.PeerCertificates { + var err error + if err = cert.VerifyHostname(authority); err == nil { + return nil + } + errs = append(errs, err) + } + return fmt.Errorf("credentials: invalid authority %q: %v", authority, errors.Join(errs...)) +} + // cipherSuiteLookup returns the string version of a TLS cipher suite ID. func cipherSuiteLookup(cipherSuiteID uint16) string { for _, s := range tls.CipherSuites() { diff --git a/vendor/google.golang.org/grpc/dialoptions.go b/vendor/google.golang.org/grpc/dialoptions.go index 405a2ffe..050ba0f1 100644 --- a/vendor/google.golang.org/grpc/dialoptions.go +++ b/vendor/google.golang.org/grpc/dialoptions.go @@ -360,7 +360,7 @@ func WithReturnConnectionError() DialOption { // // Note that using this DialOption with per-RPC credentials (through // WithCredentialsBundle or WithPerRPCCredentials) which require transport -// security is incompatible and will cause grpc.Dial() to fail. +// security is incompatible and will cause RPCs to fail. // // Deprecated: use WithTransportCredentials and insecure.NewCredentials() // instead. Will be supported throughout 1.x. diff --git a/vendor/google.golang.org/grpc/internal/credentials/credentials.go b/vendor/google.golang.org/grpc/internal/credentials/credentials.go index 9deee7f6..48b22d9c 100644 --- a/vendor/google.golang.org/grpc/internal/credentials/credentials.go +++ b/vendor/google.golang.org/grpc/internal/credentials/credentials.go @@ -20,20 +20,6 @@ import ( "context" ) -// requestInfoKey is a struct to be used as the key to store RequestInfo in a -// context. -type requestInfoKey struct{} - -// NewRequestInfoContext creates a context with ri. -func NewRequestInfoContext(ctx context.Context, ri any) context.Context { - return context.WithValue(ctx, requestInfoKey{}, ri) -} - -// RequestInfoFromContext extracts the RequestInfo from ctx. -func RequestInfoFromContext(ctx context.Context) any { - return ctx.Value(requestInfoKey{}) -} - // clientHandshakeInfoKey is a struct used as the key to store // ClientHandshakeInfo in a context. type clientHandshakeInfoKey struct{} diff --git a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go index cc5713fd..f5f2bdeb 100644 --- a/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go +++ b/vendor/google.golang.org/grpc/internal/envconfig/envconfig.go @@ -36,7 +36,7 @@ var ( // LeastRequestLB is set if we should support the least_request_experimental // LB policy, which can be enabled by setting the environment variable // "GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST" to "true". - LeastRequestLB = boolFromEnv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST", false) + LeastRequestLB = boolFromEnv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST", true) // ALTSMaxConcurrentHandshakes is the maximum number of concurrent ALTS // handshakes that can be performed. ALTSMaxConcurrentHandshakes = uint64FromEnv("GRPC_ALTS_MAX_CONCURRENT_HANDSHAKES", 100, 1, 100) @@ -69,6 +69,10 @@ var ( // to gRFC A76. It can be enabled by setting the environment variable // "GRPC_EXPERIMENTAL_RING_HASH_SET_REQUEST_HASH_KEY" to "true". RingHashSetRequestHashKey = boolFromEnv("GRPC_EXPERIMENTAL_RING_HASH_SET_REQUEST_HASH_KEY", false) + + // ALTSHandshakerKeepaliveParams is set if we should add the + // KeepaliveParams when dial the ALTS handshaker service. + ALTSHandshakerKeepaliveParams = boolFromEnv("GRPC_EXPERIMENTAL_ALTS_HANDSHAKER_KEEPALIVE_PARAMS", false) ) func boolFromEnv(envVar string, def bool) bool { diff --git a/vendor/google.golang.org/grpc/internal/envconfig/xds.go b/vendor/google.golang.org/grpc/internal/envconfig/xds.go index 2eb97f83..e8755155 100644 --- a/vendor/google.golang.org/grpc/internal/envconfig/xds.go +++ b/vendor/google.golang.org/grpc/internal/envconfig/xds.go @@ -63,4 +63,9 @@ var ( // For more details, see: // https://github.com/grpc/proposal/blob/master/A82-xds-system-root-certs.md. XDSSystemRootCertsEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_SYSTEM_ROOT_CERTS", false) + + // XDSSPIFFEEnabled controls if SPIFFE Bundle Maps can be used as roots of + // trust. For more details, see: + // https://github.com/grpc/proposal/blob/master/A87-mtls-spiffe-support.md + XDSSPIFFEEnabled = boolFromEnv("GRPC_EXPERIMENTAL_XDS_MTLS_SPIFFE", false) ) diff --git a/vendor/google.golang.org/grpc/internal/grpcsync/event.go b/vendor/google.golang.org/grpc/internal/grpcsync/event.go index fbe697c3..d788c249 100644 --- a/vendor/google.golang.org/grpc/internal/grpcsync/event.go +++ b/vendor/google.golang.org/grpc/internal/grpcsync/event.go @@ -21,28 +21,25 @@ package grpcsync import ( - "sync" "sync/atomic" ) // Event represents a one-time event that may occur in the future. type Event struct { - fired int32 + fired atomic.Bool c chan struct{} - o sync.Once } // Fire causes e to complete. It is safe to call multiple times, and // concurrently. It returns true iff this call to Fire caused the signaling -// channel returned by Done to close. +// channel returned by Done to close. If Fire returns false, it is possible +// the Done channel has not been closed yet. func (e *Event) Fire() bool { - ret := false - e.o.Do(func() { - atomic.StoreInt32(&e.fired, 1) + if e.fired.CompareAndSwap(false, true) { close(e.c) - ret = true - }) - return ret + return true + } + return false } // Done returns a channel that will be closed when Fire is called. @@ -52,7 +49,7 @@ func (e *Event) Done() <-chan struct{} { // HasFired returns true if Fire has been called. func (e *Event) HasFired() bool { - return atomic.LoadInt32(&e.fired) == 1 + return e.fired.Load() } // NewEvent returns a new, ready-to-use Event. diff --git a/vendor/google.golang.org/grpc/internal/internal.go b/vendor/google.golang.org/grpc/internal/internal.go index 2ce012cd..3ac798e8 100644 --- a/vendor/google.golang.org/grpc/internal/internal.go +++ b/vendor/google.golang.org/grpc/internal/internal.go @@ -266,6 +266,13 @@ var ( TimeAfterFunc = func(d time.Duration, f func()) Timer { return time.AfterFunc(d, f) } + + // NewStreamWaitingForResolver is a test hook that is triggered when a + // new stream blocks while waiting for name resolution. This can be + // used in tests to synchronize resolver updates and avoid race conditions. + // When set, the function will be called before the stream enters + // the blocking state. + NewStreamWaitingForResolver = func() {} ) // HealthChecker defines the signature of the client-side LB channel health diff --git a/vendor/google.golang.org/grpc/internal/resolver/delegatingresolver/delegatingresolver.go b/vendor/google.golang.org/grpc/internal/resolver/delegatingresolver/delegatingresolver.go index c0e22757..20b8fb09 100644 --- a/vendor/google.golang.org/grpc/internal/resolver/delegatingresolver/delegatingresolver.go +++ b/vendor/google.golang.org/grpc/internal/resolver/delegatingresolver/delegatingresolver.go @@ -186,23 +186,15 @@ func (r *delegatingResolver) Close() { r.proxyResolver = nil } -func networkTypeFromAddr(addr resolver.Address) string { - networkType, ok := networktype.Get(addr) - if !ok { - networkType, _ = transport.ParseDialTarget(addr.Addr) - } - return networkType -} - -func isTCPAddressPresent(state *resolver.State) bool { +func needsProxyResolver(state *resolver.State) bool { for _, addr := range state.Addresses { - if networkType := networkTypeFromAddr(addr); networkType == "tcp" { + if !skipProxy(addr) { return true } } for _, endpoint := range state.Endpoints { for _, addr := range endpoint.Addresses { - if networktype := networkTypeFromAddr(addr); networktype == "tcp" { + if !skipProxy(addr) { return true } } @@ -210,6 +202,29 @@ func isTCPAddressPresent(state *resolver.State) bool { return false } +func skipProxy(address resolver.Address) bool { + // Avoid proxy when network is not tcp. + networkType, ok := networktype.Get(address) + if !ok { + networkType, _ = transport.ParseDialTarget(address.Addr) + } + if networkType != "tcp" { + return true + } + + req := &http.Request{URL: &url.URL{ + Scheme: "https", + Host: address.Addr, + }} + // Avoid proxy when address included in `NO_PROXY` environment variable or + // fails to get the proxy address. + url, err := HTTPSProxyFromEnvironment(req) + if err != nil || url == nil { + return true + } + return false +} + // updateClientConnStateLocked constructs a combined list of addresses by // pairing each proxy address with every target address of type TCP. For each // pair, it creates a new [resolver.Address] using the proxy address and @@ -240,8 +255,7 @@ func (r *delegatingResolver) updateClientConnStateLocked() error { } var addresses []resolver.Address for _, targetAddr := range (*r.targetResolverState).Addresses { - // Avoid proxy when network is not tcp. - if networkType := networkTypeFromAddr(targetAddr); networkType != "tcp" { + if skipProxy(targetAddr) { addresses = append(addresses, targetAddr) continue } @@ -259,7 +273,7 @@ func (r *delegatingResolver) updateClientConnStateLocked() error { var addrs []resolver.Address for _, targetAddr := range endpt.Addresses { // Avoid proxy when network is not tcp. - if networkType := networkTypeFromAddr(targetAddr); networkType != "tcp" { + if skipProxy(targetAddr) { addrs = append(addrs, targetAddr) continue } @@ -340,9 +354,10 @@ func (r *delegatingResolver) updateTargetResolverState(state resolver.State) err logger.Infof("Addresses received from target resolver: %v", state.Addresses) } r.targetResolverState = &state - // If no addresses returned by resolver have network type as tcp , do not - // wait for proxy update. - if !isTCPAddressPresent(r.targetResolverState) { + // If all addresses returned by the target resolver have a non-TCP network + // type, or are listed in the `NO_PROXY` environment variable, do not wait + // for proxy update. + if !needsProxyResolver(r.targetResolverState) { return r.cc.UpdateState(*r.targetResolverState) } diff --git a/vendor/google.golang.org/grpc/internal/status/status.go b/vendor/google.golang.org/grpc/internal/status/status.go index 1186f1e9..aad171cd 100644 --- a/vendor/google.golang.org/grpc/internal/status/status.go +++ b/vendor/google.golang.org/grpc/internal/status/status.go @@ -236,3 +236,11 @@ func IsRestrictedControlPlaneCode(s *Status) bool { } return false } + +// RawStatusProto returns the internal protobuf message for use by gRPC itself. +func RawStatusProto(s *Status) *spb.Status { + if s == nil { + return nil + } + return s.s +} diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_client.go b/vendor/google.golang.org/grpc/internal/transport/http2_client.go index 171e690a..ef56592b 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_client.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_client.go @@ -545,7 +545,7 @@ func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr) Method: callHdr.Method, AuthInfo: t.authInfo, } - ctxWithRequestInfo := icredentials.NewRequestInfoContext(ctx, ri) + ctxWithRequestInfo := credentials.NewContextWithRequestInfo(ctx, ri) authData, err := t.getTrAuthData(ctxWithRequestInfo, aud) if err != nil { return nil, err @@ -592,6 +592,9 @@ func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr) // Send out timeout regardless its value. The server can detect timeout context by itself. // TODO(mmukhi): Perhaps this field should be updated when actually writing out to the wire. timeout := time.Until(dl) + if timeout <= 0 { + return nil, status.Error(codes.DeadlineExceeded, context.DeadlineExceeded.Error()) + } headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-timeout", Value: grpcutil.EncodeDuration(timeout)}) } for k, v := range authData { @@ -749,6 +752,25 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (*ClientS callHdr = &newCallHdr } + // The authority specified via the `CallAuthority` CallOption takes the + // highest precedence when determining the `:authority` header. It overrides + // any value present in the Host field of CallHdr. Before applying this + // override, the authority string is validated. If the credentials do not + // implement the AuthorityValidator interface, or if validation fails, the + // RPC is failed with a status code of `UNAVAILABLE`. + if callHdr.Authority != "" { + auth, ok := t.authInfo.(credentials.AuthorityValidator) + if !ok { + return nil, &NewStreamError{Err: status.Errorf(codes.Unavailable, "credentials type %q does not implement the AuthorityValidator interface, but authority override specified with CallAuthority call option", t.authInfo.AuthType())} + } + if err := auth.ValidateAuthority(callHdr.Authority); err != nil { + return nil, &NewStreamError{Err: status.Errorf(codes.Unavailable, "failed to validate authority %q : %v", callHdr.Authority, err)} + } + newCallHdr := *callHdr + newCallHdr.Host = callHdr.Authority + callHdr = &newCallHdr + } + headerFields, err := t.createHeaderFields(ctx, callHdr) if err != nil { return nil, &NewStreamError{Err: err, AllowTransparentRetry: false} diff --git a/vendor/google.golang.org/grpc/internal/transport/http2_server.go b/vendor/google.golang.org/grpc/internal/transport/http2_server.go index 7e53eb17..e4c3731b 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http2_server.go +++ b/vendor/google.golang.org/grpc/internal/transport/http2_server.go @@ -39,6 +39,7 @@ import ( "google.golang.org/grpc/internal/grpclog" "google.golang.org/grpc/internal/grpcutil" "google.golang.org/grpc/internal/pretty" + istatus "google.golang.org/grpc/internal/status" "google.golang.org/grpc/internal/syscall" "google.golang.org/grpc/mem" "google.golang.org/protobuf/proto" @@ -1055,7 +1056,7 @@ func (t *http2Server) writeHeaderLocked(s *ServerStream) error { return nil } -// WriteStatus sends stream status to the client and terminates the stream. +// writeStatus sends stream status to the client and terminates the stream. // There is no further I/O operations being able to perform on this stream. // TODO(zhaoq): Now it indicates the end of entire stream. Revisit if early // OK is adopted. @@ -1083,7 +1084,7 @@ func (t *http2Server) writeStatus(s *ServerStream, st *status.Status) error { headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-status", Value: strconv.Itoa(int(st.Code()))}) headerFields = append(headerFields, hpack.HeaderField{Name: "grpc-message", Value: encodeGrpcMessage(st.Message())}) - if p := st.Proto(); p != nil && len(p.Details) > 0 { + if p := istatus.RawStatusProto(st); len(p.GetDetails()) > 0 { // Do not use the user's grpc-status-details-bin (if present) if we are // even attempting to set our own. delete(s.trailer, grpcStatusDetailsBinHeader) diff --git a/vendor/google.golang.org/grpc/internal/transport/http_util.go b/vendor/google.golang.org/grpc/internal/transport/http_util.go index f997f9fd..607d2c4c 100644 --- a/vendor/google.golang.org/grpc/internal/transport/http_util.go +++ b/vendor/google.golang.org/grpc/internal/transport/http_util.go @@ -196,11 +196,14 @@ func decodeTimeout(s string) (time.Duration, error) { if !ok { return 0, fmt.Errorf("transport: timeout unit is not recognized: %q", s) } - t, err := strconv.ParseInt(s[:size-1], 10, 64) + t, err := strconv.ParseUint(s[:size-1], 10, 64) if err != nil { return 0, err } - const maxHours = math.MaxInt64 / int64(time.Hour) + if t == 0 { + return 0, fmt.Errorf("transport: timeout must be positive: %q", s) + } + const maxHours = math.MaxInt64 / uint64(time.Hour) if d == time.Hour && t > maxHours { // This timeout would overflow math.MaxInt64; clamp it. return time.Duration(math.MaxInt64), nil diff --git a/vendor/google.golang.org/grpc/internal/transport/transport.go b/vendor/google.golang.org/grpc/internal/transport/transport.go index af4a4aea..1730a639 100644 --- a/vendor/google.golang.org/grpc/internal/transport/transport.go +++ b/vendor/google.golang.org/grpc/internal/transport/transport.go @@ -540,6 +540,11 @@ type CallHdr struct { PreviousAttempts int // value of grpc-previous-rpc-attempts header to set DoneFunc func() // called when the stream is finished + + // Authority is used to explicitly override the `:authority` header. If set, + // this value takes precedence over the Host field and will be used as the + // value for the `:authority` header. + Authority string } // ClientTransport is the common interface for all gRPC client-side transport diff --git a/vendor/google.golang.org/grpc/rpc_util.go b/vendor/google.golang.org/grpc/rpc_util.go index ad20e9df..47ea09f5 100644 --- a/vendor/google.golang.org/grpc/rpc_util.go +++ b/vendor/google.golang.org/grpc/rpc_util.go @@ -160,6 +160,7 @@ type callInfo struct { codec baseCodec maxRetryRPCBufferSize int onFinish []func(err error) + authority string } func defaultCallInfo() *callInfo { @@ -365,6 +366,36 @@ func (o MaxRecvMsgSizeCallOption) before(c *callInfo) error { } func (o MaxRecvMsgSizeCallOption) after(*callInfo, *csAttempt) {} +// CallAuthority returns a CallOption that sets the HTTP/2 :authority header of +// an RPC to the specified value. When using CallAuthority, the credentials in +// use must implement the AuthorityValidator interface. +// +// # Experimental +// +// Notice: This API is EXPERIMENTAL and may be changed or removed in a later +// release. +func CallAuthority(authority string) CallOption { + return AuthorityOverrideCallOption{Authority: authority} +} + +// AuthorityOverrideCallOption is a CallOption that indicates the HTTP/2 +// :authority header value to use for the call. +// +// # Experimental +// +// Notice: This type is EXPERIMENTAL and may be changed or removed in a later +// release. +type AuthorityOverrideCallOption struct { + Authority string +} + +func (o AuthorityOverrideCallOption) before(c *callInfo) error { + c.authority = o.Authority + return nil +} + +func (o AuthorityOverrideCallOption) after(*callInfo, *csAttempt) {} + // MaxCallSendMsgSize returns a CallOption which sets the maximum message size // in bytes the client can send. If this is not set, gRPC uses the default // `math.MaxInt32`. diff --git a/vendor/google.golang.org/grpc/stats/handlers.go b/vendor/google.golang.org/grpc/stats/handlers.go index dc03731e..67194a59 100644 --- a/vendor/google.golang.org/grpc/stats/handlers.go +++ b/vendor/google.golang.org/grpc/stats/handlers.go @@ -38,6 +38,15 @@ type RPCTagInfo struct { // FailFast indicates if this RPC is failfast. // This field is only valid on client side, it's always false on server side. FailFast bool + // NameResolutionDelay indicates if the RPC needed to wait for the + // initial name resolver update before it could begin. This should only + // happen if the channel is IDLE when the RPC is started. Note that + // all retry or hedging attempts for an RPC that experienced a delay + // will have it set. + // + // This field is only valid on the client side; it is always false on + // the server side. + NameResolutionDelay bool } // Handler defines the interface for the related stats handling (e.g., RPCs, connections). diff --git a/vendor/google.golang.org/grpc/stream.go b/vendor/google.golang.org/grpc/stream.go index 12163150..d58bb647 100644 --- a/vendor/google.golang.org/grpc/stream.go +++ b/vendor/google.golang.org/grpc/stream.go @@ -101,9 +101,9 @@ type ClientStream interface { // It must only be called after stream.CloseAndRecv has returned, or // stream.Recv has returned a non-nil error (including io.EOF). Trailer() metadata.MD - // CloseSend closes the send direction of the stream. It closes the stream - // when non-nil error is met. It is also not safe to call CloseSend - // concurrently with SendMsg. + // CloseSend closes the send direction of the stream. This method always + // returns a nil error. The status of the stream may be discovered using + // RecvMsg. It is also not safe to call CloseSend concurrently with SendMsg. CloseSend() error // Context returns the context for this stream. // @@ -212,14 +212,15 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth } // Provide an opportunity for the first RPC to see the first service config // provided by the resolver. - if err := cc.waitForResolvedAddrs(ctx); err != nil { + nameResolutionDelayed, err := cc.waitForResolvedAddrs(ctx) + if err != nil { return nil, err } var mc serviceconfig.MethodConfig var onCommit func() newStream := func(ctx context.Context, done func()) (iresolver.ClientStream, error) { - return newClientStreamWithParams(ctx, desc, cc, method, mc, onCommit, done, opts...) + return newClientStreamWithParams(ctx, desc, cc, method, mc, onCommit, done, nameResolutionDelayed, opts...) } rpcInfo := iresolver.RPCInfo{Context: ctx, Method: method} @@ -257,7 +258,7 @@ func newClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth return newStream(ctx, func() {}) } -func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, mc serviceconfig.MethodConfig, onCommit, doneFunc func(), opts ...CallOption) (_ iresolver.ClientStream, err error) { +func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, mc serviceconfig.MethodConfig, onCommit, doneFunc func(), nameResolutionDelayed bool, opts ...CallOption) (_ iresolver.ClientStream, err error) { callInfo := defaultCallInfo() if mc.WaitForReady != nil { callInfo.failFast = !*mc.WaitForReady @@ -296,6 +297,7 @@ func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *Client Method: method, ContentSubtype: callInfo.contentSubtype, DoneFunc: doneFunc, + Authority: callInfo.authority, } // Set our outgoing compression according to the UseCompressor CallOption, if @@ -321,19 +323,20 @@ func newClientStreamWithParams(ctx context.Context, desc *StreamDesc, cc *Client } cs := &clientStream{ - callHdr: callHdr, - ctx: ctx, - methodConfig: &mc, - opts: opts, - callInfo: callInfo, - cc: cc, - desc: desc, - codec: callInfo.codec, - compressorV0: compressorV0, - compressorV1: compressorV1, - cancel: cancel, - firstAttempt: true, - onCommit: onCommit, + callHdr: callHdr, + ctx: ctx, + methodConfig: &mc, + opts: opts, + callInfo: callInfo, + cc: cc, + desc: desc, + codec: callInfo.codec, + compressorV0: compressorV0, + compressorV1: compressorV1, + cancel: cancel, + firstAttempt: true, + onCommit: onCommit, + nameResolutionDelay: nameResolutionDelayed, } if !cc.dopts.disableRetry { cs.retryThrottler = cc.retryThrottler.Load().(*retryThrottler) @@ -417,7 +420,7 @@ func (cs *clientStream) newAttemptLocked(isTransparent bool) (*csAttempt, error) var beginTime time.Time shs := cs.cc.dopts.copts.StatsHandlers for _, sh := range shs { - ctx = sh.TagRPC(ctx, &stats.RPCTagInfo{FullMethodName: method, FailFast: cs.callInfo.failFast}) + ctx = sh.TagRPC(ctx, &stats.RPCTagInfo{FullMethodName: method, FailFast: cs.callInfo.failFast, NameResolutionDelay: cs.nameResolutionDelay}) beginTime = time.Now() begin := &stats.Begin{ Client: true, @@ -573,6 +576,9 @@ type clientStream struct { onCommit func() replayBuffer []replayOp // operations to replay on retry replayBufferSize int // current size of replayBuffer + // nameResolutionDelay indicates if there was a delay in the name resolution. + // This field is only valid on client side, it's always false on server side. + nameResolutionDelay bool } type replayOp struct { @@ -987,7 +993,7 @@ func (cs *clientStream) RecvMsg(m any) error { func (cs *clientStream) CloseSend() error { if cs.sentLast { - // TODO: return an error and finish the stream instead, due to API misuse? + // Return a nil error on repeated calls to this method. return nil } cs.sentLast = true @@ -1008,7 +1014,10 @@ func (cs *clientStream) CloseSend() error { binlog.Log(cs.ctx, chc) } } - // We never returned an error here for reasons. + // We don't return an error here as we expect users to read all messages + // from the stream and get the RPC status from RecvMsg(). Note that + // SendMsg() must return an error when one occurs so the application + // knows to stop sending messages, but that does not apply here. return nil } @@ -1372,7 +1381,7 @@ func (as *addrConnStream) Trailer() metadata.MD { func (as *addrConnStream) CloseSend() error { if as.sentLast { - // TODO: return an error and finish the stream instead, due to API misuse? + // Return a nil error on repeated calls to this method. return nil } as.sentLast = true diff --git a/vendor/google.golang.org/grpc/version.go b/vendor/google.golang.org/grpc/version.go index 51da8ed5..bd82673d 100644 --- a/vendor/google.golang.org/grpc/version.go +++ b/vendor/google.golang.org/grpc/version.go @@ -19,4 +19,4 @@ package grpc // Version is the current grpc version. -const Version = "1.72.1" +const Version = "1.73.0" diff --git a/vendor/modules.txt b/vendor/modules.txt index 3dceb2ec..2f34cf78 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -11,7 +11,7 @@ github.com/coreos/go-systemd/v22/journal # github.com/creack/pty v1.1.24 ## explicit; go 1.18 github.com/creack/pty -# github.com/ebitengine/purego v0.8.3 +# github.com/ebitengine/purego v0.8.4 ## explicit; go 1.18 github.com/ebitengine/purego github.com/ebitengine/purego/internal/cgo @@ -28,7 +28,7 @@ github.com/gogo/protobuf/protoc-gen-gogo/descriptor # github.com/golang/protobuf v1.5.4 ## explicit; go 1.17 github.com/golang/protobuf/proto -# github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 +# github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 ## explicit; go 1.23.0 github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options # github.com/iskylite/nodeset v1.0.1 @@ -50,15 +50,16 @@ github.com/moby/term/windows # github.com/olekukonko/errors v1.1.0 ## explicit; go 1.21 github.com/olekukonko/errors -# github.com/olekukonko/ll v0.0.8 +# github.com/olekukonko/ll v0.0.9 ## explicit; go 1.21 github.com/olekukonko/ll github.com/olekukonko/ll/lh github.com/olekukonko/ll/lx -# github.com/olekukonko/tablewriter v1.0.6 +# github.com/olekukonko/tablewriter v1.0.8 ## explicit; go 1.21 github.com/olekukonko/tablewriter github.com/olekukonko/tablewriter/pkg/twwarp +github.com/olekukonko/tablewriter/pkg/twwidth github.com/olekukonko/tablewriter/renderer github.com/olekukonko/tablewriter/tw # github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 @@ -73,7 +74,7 @@ github.com/segmentio/fasthash/fnv1a # github.com/willf/bitset v1.1.11 ## explicit; go 1.14 github.com/willf/bitset -# go.etcd.io/etcd/api/v3 v3.6.0 +# go.etcd.io/etcd/api/v3 v3.6.2 ## explicit; go 1.23.0 go.etcd.io/etcd/api/v3/authpb go.etcd.io/etcd/api/v3/etcdserverpb @@ -82,7 +83,7 @@ go.etcd.io/etcd/api/v3/mvccpb go.etcd.io/etcd/api/v3/v3rpc/rpctypes go.etcd.io/etcd/api/v3/version go.etcd.io/etcd/api/v3/versionpb -# go.etcd.io/etcd/client/pkg/v3 v3.6.0 +# go.etcd.io/etcd/client/pkg/v3 v3.6.2 ## explicit; go 1.23.0 go.etcd.io/etcd/client/pkg/v3/fileutil go.etcd.io/etcd/client/pkg/v3/logutil @@ -91,7 +92,7 @@ go.etcd.io/etcd/client/pkg/v3/tlsutil go.etcd.io/etcd/client/pkg/v3/transport go.etcd.io/etcd/client/pkg/v3/types go.etcd.io/etcd/client/pkg/v3/verify -# go.etcd.io/etcd/client/v3 v3.6.0 +# go.etcd.io/etcd/client/v3 v3.6.2 ## explicit; go 1.23.0 go.etcd.io/etcd/client/v3 go.etcd.io/etcd/client/v3/credentials @@ -112,7 +113,7 @@ go.uber.org/zap/internal/pool go.uber.org/zap/internal/stacktrace go.uber.org/zap/zapcore go.uber.org/zap/zapgrpc -# golang.org/x/net v0.40.0 +# golang.org/x/net v0.42.0 ## explicit; go 1.23.0 golang.org/x/net/http/httpguts golang.org/x/net/http2 @@ -121,25 +122,25 @@ golang.org/x/net/idna golang.org/x/net/internal/httpcommon golang.org/x/net/internal/timeseries golang.org/x/net/trace -# golang.org/x/sys v0.33.0 +# golang.org/x/sys v0.34.0 ## explicit; go 1.23.0 golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/text v0.25.0 +# golang.org/x/text v0.27.0 ## explicit; go 1.23.0 golang.org/x/text/secure/bidirule golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm -# google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 +# google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 ## explicit; go 1.23.0 google.golang.org/genproto/googleapis/api google.golang.org/genproto/googleapis/api/annotations -# google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 +# google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 ## explicit; go 1.23.0 google.golang.org/genproto/googleapis/rpc/status -# google.golang.org/grpc v1.72.1 -## explicit; go 1.23 +# google.golang.org/grpc v1.73.0 +## explicit; go 1.23.0 google.golang.org/grpc google.golang.org/grpc/attributes google.golang.org/grpc/backoff