Migrate table formatting to go-pretty for proper UTF-8 and ANSI handling
Replace manual string-based table formatting with jedib0t/go-pretty/v6/table library to fix alignment issues with Norwegian characters (æøå) and ANSI color codes. Changes: - Migrate FormatTaskList() to use go-pretty table - Migrate FormatTaskDetail() to use clean table format - Migrate FormatProjects() and FormatTagCounts() for consistency - Remove truncate() function (no longer needed) - Configure StyleLight with Unicode box-drawing characters - Set proper column widths and alignment Fixes: - Priority column now center-aligned under 'Pri' - Norwegian characters (æøå) display and align correctly - Tags column properly aligned - Description field truncates at 40 chars with proper UTF-8 handling - All ANSI color codes handled automatically - Consistent formatting across all table views All existing color functions work unchanged. UTF-8 and ANSI codes are handled automatically by go-pretty's width calculation.
This commit is contained in:
+11
-6
@@ -2,26 +2,31 @@ module git.jnss.me/joakim/opal
|
|||||||
|
|
||||||
go 1.25.5
|
go 1.25.5
|
||||||
|
|
||||||
require github.com/google/uuid v1.6.0
|
require (
|
||||||
|
github.com/fatih/color v1.18.0
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/jedib0t/go-pretty/v6 v6.7.8
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.33
|
||||||
|
github.com/spf13/cobra v1.10.2
|
||||||
|
github.com/spf13/viper v1.21.0
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.33 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
github.com/spf13/afero v1.15.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/spf13/cast v1.10.0 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
github.com/spf13/cobra v1.10.2 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/spf13/viper v1.21.0 // indirect
|
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.28.0 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
+29
-2
@@ -1,23 +1,44 @@
|
|||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
|
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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
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/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
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/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/jedib0t/go-pretty/v6 v6.7.8 h1:BVYrDy5DPBA3Qn9ICT+PokP9cvCv1KaHv2i+Hc8sr5o=
|
||||||
|
github.com/jedib0t/go-pretty/v6 v6.7.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
|
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
|
||||||
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
@@ -34,14 +55,20 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
|||||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package engine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
|
"github.com/jedib0t/go-pretty/v6/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FormatTaskList formats a list of tasks for display
|
// FormatTaskList formats a list of tasks for display
|
||||||
@@ -14,14 +15,29 @@ func FormatTaskList(tasks []*Task, ws *WorkingSet) string {
|
|||||||
return "No tasks found."
|
return "No tasks found."
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb strings.Builder
|
t := table.NewWriter()
|
||||||
|
|
||||||
// Header
|
// Configure style
|
||||||
sb.WriteString(fmt.Sprintf("%-3s %-8s %-3s %-12s %-40s %-12s %s\n",
|
t.SetStyle(table.StyleLight)
|
||||||
"ID", "Status", "Pri", "Project", "Description", "Due", "Tags"))
|
t.Style().Options.SeparateRows = false
|
||||||
sb.WriteString(strings.Repeat("-", 100) + "\n")
|
t.Style().Options.DrawBorder = false
|
||||||
|
|
||||||
// Tasks
|
// Configure columns with proper widths
|
||||||
|
t.SetColumnConfigs([]table.ColumnConfig{
|
||||||
|
{Number: 1, WidthMin: 3, WidthMax: 3, Align: text.AlignRight}, // ID
|
||||||
|
{Number: 2, WidthMin: 8, WidthMax: 8, Align: text.AlignLeft}, // Status
|
||||||
|
{Number: 3, WidthMin: 3, WidthMax: 3, Align: text.AlignCenter}, // Pri
|
||||||
|
{Number: 4, WidthMin: 12, WidthMax: 12, Align: text.AlignLeft}, // Project
|
||||||
|
{Number: 5, WidthMin: 40, WidthMax: 40, Align: text.AlignLeft, // Description
|
||||||
|
WidthMaxEnforcer: text.Trim},
|
||||||
|
{Number: 6, WidthMin: 12, WidthMax: 12, Align: text.AlignLeft}, // Due
|
||||||
|
{Number: 7, Align: text.AlignLeft}, // Tags (variable)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add header
|
||||||
|
t.AppendHeader(table.Row{"ID", "Status", "Pri", "Project", "Description", "Due", "Tags"})
|
||||||
|
|
||||||
|
// Add rows
|
||||||
for i, task := range tasks {
|
for i, task := range tasks {
|
||||||
displayID := i + 1
|
displayID := i + 1
|
||||||
if ws != nil {
|
if ws != nil {
|
||||||
@@ -34,73 +50,90 @@ func FormatTaskList(tasks []*Task, ws *WorkingSet) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
status := formatStatus(task.Status)
|
t.AppendRow(table.Row{
|
||||||
priority := formatPriority(task.Priority)
|
displayID,
|
||||||
project := formatProject(task.Project)
|
formatStatus(task.Status),
|
||||||
description := truncate(task.Description, 40)
|
formatPriority(task.Priority),
|
||||||
due := formatDue(task.Due)
|
formatProject(task.Project),
|
||||||
tags := formatTags(task.Tags)
|
task.Description,
|
||||||
|
formatDue(task.Due),
|
||||||
sb.WriteString(fmt.Sprintf("%-3d %-8s %-3s %-12s %-40s %-12s %s\n",
|
formatTags(task.Tags),
|
||||||
displayID, status, priority, project, description, due, tags))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.String()
|
return t.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatTaskDetail formats detailed task information
|
// FormatTaskDetail formats detailed task information
|
||||||
func FormatTaskDetail(task *Task) string {
|
func FormatTaskDetail(task *Task) string {
|
||||||
var sb strings.Builder
|
t := table.NewWriter()
|
||||||
|
|
||||||
sb.WriteString(color.New(color.Bold).Sprint("Task Details") + "\n")
|
// Configure style
|
||||||
sb.WriteString(strings.Repeat("=", 50) + "\n")
|
t.SetStyle(table.StyleLight)
|
||||||
|
t.Style().Options.SeparateRows = false
|
||||||
|
t.Style().Options.DrawBorder = false
|
||||||
|
t.Style().Options.SeparateHeader = true
|
||||||
|
|
||||||
sb.WriteString(fmt.Sprintf("UUID: %s\n", task.UUID))
|
// Configure columns - field name and value
|
||||||
sb.WriteString(fmt.Sprintf("Status: %s\n", formatStatus(task.Status)))
|
t.SetColumnConfigs([]table.ColumnConfig{
|
||||||
sb.WriteString(fmt.Sprintf("Description: %s\n", task.Description))
|
{Number: 1, WidthMin: 12, WidthMax: 12, Align: text.AlignLeft},
|
||||||
sb.WriteString(fmt.Sprintf("Priority: %s\n", formatPriority(task.Priority)))
|
{Number: 2, Align: text.AlignLeft},
|
||||||
sb.WriteString(fmt.Sprintf("Project: %s\n", formatProject(task.Project)))
|
})
|
||||||
|
|
||||||
sb.WriteString(fmt.Sprintf("\nCreated: %s\n", formatTime(task.Created)))
|
// Add title as header
|
||||||
sb.WriteString(fmt.Sprintf("Modified: %s\n", formatTime(task.Modified)))
|
t.SetTitle(color.New(color.Bold).Sprint("Task Details"))
|
||||||
|
|
||||||
|
// Core fields
|
||||||
|
t.AppendRow(table.Row{"UUID", task.UUID})
|
||||||
|
t.AppendRow(table.Row{"Status", formatStatus(task.Status)})
|
||||||
|
t.AppendRow(table.Row{"Description", task.Description})
|
||||||
|
t.AppendRow(table.Row{"Priority", formatPriority(task.Priority)})
|
||||||
|
t.AppendRow(table.Row{"Project", formatProject(task.Project)})
|
||||||
|
|
||||||
|
t.AppendSeparator()
|
||||||
|
|
||||||
|
// Timestamps
|
||||||
|
t.AppendRow(table.Row{"Created", formatTime(task.Created)})
|
||||||
|
t.AppendRow(table.Row{"Modified", formatTime(task.Modified)})
|
||||||
|
|
||||||
if task.Start != nil {
|
if task.Start != nil {
|
||||||
sb.WriteString(fmt.Sprintf("Started: %s\n", formatTime(*task.Start)))
|
t.AppendRow(table.Row{"Started", formatTime(*task.Start)})
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.End != nil {
|
if task.End != nil {
|
||||||
sb.WriteString(fmt.Sprintf("Ended: %s\n", formatTime(*task.End)))
|
t.AppendRow(table.Row{"Ended", formatTime(*task.End)})
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.Due != nil {
|
if task.Due != nil {
|
||||||
sb.WriteString(fmt.Sprintf("Due: %s\n", formatTimeWithColor(*task.Due)))
|
t.AppendRow(table.Row{"Due", formatTimeWithColor(*task.Due)})
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.Scheduled != nil {
|
if task.Scheduled != nil {
|
||||||
sb.WriteString(fmt.Sprintf("Scheduled: %s\n", formatTime(*task.Scheduled)))
|
t.AppendRow(table.Row{"Scheduled", formatTime(*task.Scheduled)})
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.Wait != nil {
|
if task.Wait != nil {
|
||||||
sb.WriteString(fmt.Sprintf("Wait: %s\n", formatTime(*task.Wait)))
|
t.AppendRow(table.Row{"Wait", formatTime(*task.Wait)})
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.Until != nil {
|
if task.Until != nil {
|
||||||
sb.WriteString(fmt.Sprintf("Until: %s\n", formatTime(*task.Until)))
|
t.AppendRow(table.Row{"Until", formatTime(*task.Until)})
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.RecurrenceDuration != nil {
|
if task.RecurrenceDuration != nil {
|
||||||
sb.WriteString(fmt.Sprintf("Recurrence: %s\n", FormatRecurrenceDuration(*task.RecurrenceDuration)))
|
t.AppendRow(table.Row{"Recurrence", FormatRecurrenceDuration(*task.RecurrenceDuration)})
|
||||||
}
|
}
|
||||||
|
|
||||||
if task.ParentUUID != nil {
|
if task.ParentUUID != nil {
|
||||||
sb.WriteString(fmt.Sprintf("Parent: %s (recurring instance)\n", *task.ParentUUID))
|
t.AppendRow(table.Row{"Parent", fmt.Sprintf("%s (recurring instance)", *task.ParentUUID)})
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(task.Tags) > 0 {
|
if len(task.Tags) > 0 {
|
||||||
sb.WriteString(fmt.Sprintf("\nTags: %s\n", formatTags(task.Tags)))
|
t.AppendSeparator()
|
||||||
|
t.AppendRow(table.Row{"Tags", formatTags(task.Tags)})
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.String()
|
return t.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatProjects formats project list with counts
|
// FormatProjects formats project list with counts
|
||||||
@@ -109,32 +142,58 @@ func FormatProjects(projectCounts map[string]int) string {
|
|||||||
return "No projects found."
|
return "No projects found."
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb strings.Builder
|
t := table.NewWriter()
|
||||||
sb.WriteString(fmt.Sprintf("%-20s %s\n", "Project", "Count"))
|
|
||||||
sb.WriteString(strings.Repeat("-", 30) + "\n")
|
|
||||||
|
|
||||||
|
// Configure style
|
||||||
|
t.SetStyle(table.StyleLight)
|
||||||
|
t.Style().Options.SeparateRows = false
|
||||||
|
t.Style().Options.DrawBorder = false
|
||||||
|
|
||||||
|
// Configure columns
|
||||||
|
t.SetColumnConfigs([]table.ColumnConfig{
|
||||||
|
{Number: 1, WidthMin: 20, WidthMax: 20, Align: text.AlignLeft},
|
||||||
|
{Number: 2, Align: text.AlignRight},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add header
|
||||||
|
t.AppendHeader(table.Row{"Project", "Count"})
|
||||||
|
|
||||||
|
// Add rows
|
||||||
for project, count := range projectCounts {
|
for project, count := range projectCounts {
|
||||||
sb.WriteString(fmt.Sprintf("%-20s %d\n", project, count))
|
t.AppendRow(table.Row{project, count})
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.String()
|
return t.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatTags formats tag list with counts
|
// FormatTagCounts formats tag list with counts
|
||||||
func FormatTagCounts(tagCounts map[string]int) string {
|
func FormatTagCounts(tagCounts map[string]int) string {
|
||||||
if len(tagCounts) == 0 {
|
if len(tagCounts) == 0 {
|
||||||
return "No tags found."
|
return "No tags found."
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb strings.Builder
|
t := table.NewWriter()
|
||||||
sb.WriteString(fmt.Sprintf("%-20s %s\n", "Tag", "Count"))
|
|
||||||
sb.WriteString(strings.Repeat("-", 30) + "\n")
|
|
||||||
|
|
||||||
|
// Configure style
|
||||||
|
t.SetStyle(table.StyleLight)
|
||||||
|
t.Style().Options.SeparateRows = false
|
||||||
|
t.Style().Options.DrawBorder = false
|
||||||
|
|
||||||
|
// Configure columns
|
||||||
|
t.SetColumnConfigs([]table.ColumnConfig{
|
||||||
|
{Number: 1, WidthMin: 20, WidthMax: 20, Align: text.AlignLeft},
|
||||||
|
{Number: 2, Align: text.AlignRight},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add header
|
||||||
|
t.AppendHeader(table.Row{"Tag", "Count"})
|
||||||
|
|
||||||
|
// Add rows
|
||||||
for tag, count := range tagCounts {
|
for tag, count := range tagCounts {
|
||||||
sb.WriteString(fmt.Sprintf("%-20s %d\n", tag, count))
|
t.AppendRow(table.Row{tag, count})
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.String()
|
return t.Render()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
@@ -214,14 +273,13 @@ func formatTags(tags []string) string {
|
|||||||
for i, tag := range tags {
|
for i, tag := range tags {
|
||||||
formatted[i] = color.CyanString("+" + tag)
|
formatted[i] = color.CyanString("+" + tag)
|
||||||
}
|
}
|
||||||
return strings.Join(formatted, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func truncate(s string, maxLen int) string {
|
// Join tags with spaces
|
||||||
if len(s) <= maxLen {
|
result := formatted[0]
|
||||||
return s
|
for i := 1; i < len(formatted); i++ {
|
||||||
|
result += " " + formatted[i]
|
||||||
}
|
}
|
||||||
return s[:maxLen-3] + "..."
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetProjectCounts returns a map of project names to task counts
|
// GetProjectCounts returns a map of project names to task counts
|
||||||
|
|||||||
Reference in New Issue
Block a user