List note tags (#85)

pull/88/head
Mickaël Menu 3 years ago committed by GitHub
parent 977dc20bcb
commit d74cf52d1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -6,6 +6,8 @@ All notable changes to this project will be documented in this file.
### Added
* List the tags found in your notebook with `zk tag list`.
* Many options are available to customize the output, including JSON serialization. See `zk tag list --help`.
* Support for LSP references to browse the backlinks of the current note, if the caret is not over a link.
* New template variables are available when [generating custom Markdown links with `link-format`](docs/note-format.md).
* `filename`, `path`, `abs-path` and `rel-path` for many path flavors.
@ -23,13 +25,13 @@ All notable changes to this project will be documented in this file.
* A new `{{substring s index length}}` template helper extracts a portion of a given string, e.g.:
* `{{substring 'A full quote' 2 4}}` outputs `full`
* `{{substring 'A full quote' -5 5}` outputs `quote`
* Allow setting the `--working-dir` and `--notebook-dir` flags before the `zk` subcommand when using aliases, e.g. `zk -W ~/notes my-alias`.
### Fixed
* UTF-8 handling in the LSP server.
* [#78](https://github.com/mickael-menu/zk/issues/78) Do not exclude notes containing broken links from the index.
* Allow setting the `--working-dir` and `--notebook-dir` flags before the `zk` subcommand when using aliases, e.g. `zk -W ~/notes my-alias`.
## 0.6.0

@ -12,3 +12,16 @@ You can filter your notes by their tags using the `--tags` option, as demonstrat
```sh
$ zk list --tag "inbox OR todo, NOT done"
```
## Listing tags
You can list all the tags found in your notebook using `zk tag list`.
The following variables are available in the templates used when formatting tags, for example with `zk tag list --format <template>`.
| Variable | Type | Description |
|--------------|--------|------------------------------------------------|
| `id` | int | Unique ID of this tag in the Notebook database |
| `name` | string | Name of the tag |
| `note-count` | int | Number of notes attached to this tag |

@ -5,31 +5,34 @@ go 1.15
replace github.com/tliron/glsp => github.com/mickael-menu/glsp v0.1.1
require (
github.com/AlecAivazis/survey/v2 v2.2.12
github.com/alecthomas/kong v0.2.18-0.20210616130643-2d879d20374b
github.com/AlecAivazis/survey/v2 v2.3.2
github.com/alecthomas/kong v0.2.18-0.20210927063154-5c7b038540ab
github.com/aymerick/raymond v2.0.2+incompatible
github.com/fatih/color v1.12.0
github.com/fatih/color v1.13.0
github.com/go-testfixtures/testfixtures/v3 v3.4.1
github.com/google/go-cmp v0.5.6
github.com/gosimple/slug v1.9.0
github.com/gosimple/slug v1.10.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/lestrrat-go/strftime v1.0.4
github.com/mattn/go-isatty v0.0.13
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mattn/go-sqlite3 v1.14.7
github.com/lestrrat-go/strftime v1.0.5
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.14
github.com/mattn/go-sqlite3 v1.14.8
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mickael-menu/pretty v0.2.3
github.com/mvdan/xurls v1.1.0
github.com/pelletier/go-toml v1.9.3
github.com/pelletier/go-toml v1.9.4
github.com/relvacode/iso8601 v1.1.0
github.com/rogpeppe/go-internal v1.6.2 // indirect
github.com/rvflash/elapsed v0.2.0
github.com/schollz/progressbar/v3 v3.8.1
github.com/schollz/progressbar/v3 v3.8.3
github.com/tj/go-naturaldate v1.3.0
github.com/tliron/glsp v0.0.0-20210423182918-98d1a22f3ee6
github.com/tliron/kutil v0.1.48
github.com/yuin/goldmark v1.3.8
github.com/tliron/glsp v0.0.0-20210824162824-d103e5701036
github.com/tliron/kutil v0.1.49
github.com/yuin/goldmark v1.4.1
github.com/yuin/goldmark-meta v1.0.0
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
gopkg.in/djherbis/times.v1 v1.2.0
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/sys v0.0.0-20211002104244-808efd93c36d // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/djherbis/times.v1 v1.3.0
)

@ -39,8 +39,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AlecAivazis/survey/v2 v2.2.12 h1:5a07y93zA6SZ09gOa9wLVLznF5zTJMQ+pJ3cZK4IuO8=
github.com/AlecAivazis/survey/v2 v2.2.12/go.mod h1:6d4saEvBsfSHXeN1a5OA5m2+HJ2LuVokllnC77pAIKI=
github.com/AlecAivazis/survey/v2 v2.3.2 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8=
github.com/AlecAivazis/survey/v2 v2.3.2/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg=
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
@ -89,8 +89,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/alecthomas/kong v0.2.18-0.20210616130643-2d879d20374b h1:u2cQGzIEefu+7cLR4wJifBB/DFhZzTQpQ+nBJXcJHY8=
github.com/alecthomas/kong v0.2.18-0.20210616130643-2d879d20374b/go.mod h1:ka3VZ8GZNPXv9Ov+j4YNLkI8mTuhXyr/0ktSlqIydQQ=
github.com/alecthomas/kong v0.2.18-0.20210927063154-5c7b038540ab h1:YDA7vD+nEeaQMbZcKRmQOgp4Ba2N7+nuJnZsr27WY7I=
github.com/alecthomas/kong v0.2.18-0.20210927063154-5c7b038540ab/go.mod h1:ka3VZ8GZNPXv9Ov+j4YNLkI8mTuhXyr/0ktSlqIydQQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -305,8 +305,9 @@ github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
@ -464,8 +465,10 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs=
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
github.com/gosimple/slug v1.10.0 h1:3XbiQua1IpCdrvuntWvGBxVm+K99wCSxJjlxkP49GGQ=
github.com/gosimple/slug v1.10.0/go.mod h1:MICb3w495l9KNdZm+Xn5b6T2Hn831f9DMxiJ1r+bAjw=
github.com/gosimple/unidecode v1.0.0 h1:kPdvM+qy0tnk4/BrnkrbdJ82xe88xn7c9hcaipDz4dQ=
github.com/gosimple/unidecode v1.0.0/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@ -602,8 +605,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8=
github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g=
github.com/lestrrat-go/strftime v1.0.5 h1:A7H3tT8DhTz8u65w+JRpiBxM4dINQhUXAZnhBa2xeOE=
github.com/lestrrat-go/strftime v1.0.5/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -621,8 +624,10 @@ github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlW
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@ -630,16 +635,15 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
@ -729,8 +733,9 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -773,11 +778,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
github.com/relvacode/iso8601 v1.1.0 h1:2nV8sp0eOjpoKQ2vD3xSDygsjAx37NHG2UlZiCkDH4I=
github.com/relvacode/iso8601 v1.1.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@ -797,8 +799,8 @@ github.com/rvflash/elapsed v0.2.0/go.mod h1:sgjohdXO66LHVgIEQpO92eQjDWyZ5twX1ow1
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/schollz/progressbar/v3 v3.8.1 h1:maiA95sku3mMHbERvCwzn/Tj6258Fm5NQf0E4L/a+5o=
github.com/schollz/progressbar/v3 v3.8.1/go.mod h1:rS3+CgxcNODZywN7C/z/7XH8gxCBLwuW5UmOUiNpOgs=
github.com/schollz/progressbar/v3 v3.8.3 h1:FnLGl3ewlDUP+YdSwveXBaXs053Mem/du+wr7XSYKl8=
github.com/schollz/progressbar/v3 v3.8.3/go.mod h1:pWnVCjSBZsT2X3nx9HfRdnCDrpbevliMeoEVhStwHko=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
@ -866,8 +868,9 @@ github.com/tj/assert v0.0.0-20190920132354-ee03d75cd160 h1:NSWpaDaurcAJY7PkL8Xt0
github.com/tj/assert v0.0.0-20190920132354-ee03d75cd160/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
github.com/tj/go-naturaldate v1.3.0 h1:OgJIPkR/Jk4bFMBLbxZ8w+QUxwjqSvzd9x+yXocY4RI=
github.com/tj/go-naturaldate v1.3.0/go.mod h1:rpUbjivDKiS1BlfMGc2qUKNZ/yxgthOfmytQs8d8hKk=
github.com/tliron/kutil v0.1.48 h1:9pQvSklcmjIcWPCdpRiCrlHHFWUb8GvoNEzJEc7Xvlk=
github.com/tliron/kutil v0.1.48/go.mod h1:JNExME6WabMoU/TMzjffPPXxQjSdRQuIVdUM8aBxNuw=
github.com/tliron/kutil v0.1.49 h1:C1EixWV5BLm7034cbJZ3bONiTHiUvBe+12BYzU5NpiU=
github.com/tliron/kutil v0.1.49/go.mod h1:JNExME6WabMoU/TMzjffPPXxQjSdRQuIVdUM8aBxNuw=
github.com/tliron/yamlkeys v1.3.5/go.mod h1:8kJ1A/1s3p/I3MQUAbtv72dPEyQGoh0ZkQp0UAkABBo=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@ -897,8 +900,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.3.8 h1:Nw158Q8QN+CPgTmVRByhVwapp8Mm1e2blinhmx4wx5E=
github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark-meta v1.0.0 h1:ScsatUIT2gFS6azqzLGUjgOnELsBOxMXerM3ogdJhAM=
github.com/yuin/goldmark-meta v1.0.0/go.mod h1:zsNNOrZ4nLuyHAJeLQEZcQat8dm70SmB2kHbls092Gc=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
@ -970,9 +973,10 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ=
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -1104,7 +1108,6 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -1176,19 +1179,23 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211002104244-808efd93c36d h1:SABT8Vei3iTiu+Gy8KOzpSNz+W1EQ5YBCRtiEETxF+0=
golang.org/x/sys v0.0.0-20211002104244-808efd93c36d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1196,8 +1203,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1402,8 +1410,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/djherbis/times.v1 v1.2.0 h1:UCvDKl1L/fmBygl2Y7hubXCnY7t4Yj46ZrBFNUipFbM=
gopkg.in/djherbis/times.v1 v1.2.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8=
gopkg.in/djherbis/times.v1 v1.3.0 h1:uxMS4iMtH6Pwsxog094W0FYldiNnfY/xba00vq6C2+o=
gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=

@ -749,7 +749,7 @@ func (s *Server) refreshDiagnosticsOfDocument(doc *document, notify glsp.NotifyF
}
func (s *Server) buildTagCompletionList(notebook *core.Notebook, triggerChar string) ([]protocol.CompletionItem, error) {
tags, err := notebook.FindCollections(core.CollectionKindTag)
tags, err := notebook.FindCollections(core.CollectionKindTag, nil)
if err != nil {
return nil, err
}

@ -3,6 +3,7 @@ package sqlite
import (
"database/sql"
"fmt"
"strings"
"github.com/mickael-menu/zk/internal/core"
"github.com/mickael-menu/zk/internal/util"
@ -17,7 +18,6 @@ type CollectionDAO struct {
// Prepared SQL statements
createCollectionStmt *LazyStmt
findCollectionStmt *LazyStmt
findAllCollectionsStmt *LazyStmt
findAssociationStmt *LazyStmt
createAssociationStmt *LazyStmt
removeAssociationsStmt *LazyStmt
@ -42,16 +42,6 @@ func NewCollectionDAO(tx Transaction, logger util.Logger) *CollectionDAO {
WHERE kind = ? AND name = ?
`),
// Find all collections.
findAllCollectionsStmt: tx.PrepareLazy(`
SELECT c.name, COUNT(nc.id) as count
FROM collections c
INNER JOIN notes_collections nc ON nc.collection_id = c.id
WHERE kind = ?
GROUP BY c.id
ORDER BY c.name
`),
// Returns whether a note and a collection are associated.
findAssociationStmt: tx.PrepareLazy(`
SELECT id FROM notes_collections
@ -87,8 +77,25 @@ func (d *CollectionDAO) FindOrCreate(kind core.CollectionKind, name string) (cor
}
}
func (d *CollectionDAO) FindAll(kind core.CollectionKind) ([]core.Collection, error) {
rows, err := d.findAllCollectionsStmt.Query(kind)
func (d *CollectionDAO) FindAll(kind core.CollectionKind, sorters []core.CollectionSorter) ([]core.Collection, error) {
query := `
SELECT c.name, COUNT(nc.id) as count
FROM collections c
INNER JOIN notes_collections nc ON nc.collection_id = c.id
WHERE kind = ?
GROUP BY c.id
`
orderTerms := []string{}
if sorters != nil {
for _, sorter := range sorters {
orderTerms = append(orderTerms, collectionOrderTerm(sorter))
}
}
orderTerms = append(orderTerms, `c.name ASC`)
query += "ORDER BY " + strings.Join(orderTerms, ", ") + "\n"
rows, err := d.tx.Query(query, kind)
if err != nil {
return []core.Collection{}, err
}
@ -114,6 +121,22 @@ func (d *CollectionDAO) FindAll(kind core.CollectionKind) ([]core.Collection, er
return collections, nil
}
func collectionOrderTerm(sorter core.CollectionSorter) string {
order := " ASC"
if !sorter.Ascending {
order = " DESC"
}
switch sorter.Field {
case core.CollectionSortName:
return "c.name COLLATE NOCASE" + order
case core.CollectionSortNoteCount:
return "count" + order
default:
panic(fmt.Sprintf("%v: unknown core.CollectionSortField", sorter.Field))
}
}
func (d *CollectionDAO) findCollection(kind core.CollectionKind, name string) (core.CollectionID, error) {
wrap := errors.Wrapperf("failed to get %s named %s", kind, name)

@ -35,18 +35,51 @@ func TestCollectionDAOFindOrCreate(t *testing.T) {
func TestCollectionDaoFindAll(t *testing.T) {
testCollectionDAO(t, func(tx Transaction, dao *CollectionDAO) {
// Finds none
cs, err := dao.FindAll("missing")
cs, err := dao.FindAll("missing", nil)
assert.Nil(t, err)
assert.Equal(t, len(cs), 0)
// Finds existing
cs, err = dao.FindAll("tag")
cs, err = dao.FindAll("tag", nil)
assert.Nil(t, err)
assert.Equal(t, cs, []core.Collection{
{Kind: "tag", Name: "adventure", NoteCount: 2},
{Kind: "tag", Name: "fantasy", NoteCount: 1},
{Kind: "tag", Name: "fiction", NoteCount: 1},
{Kind: "tag", Name: "history", NoteCount: 1},
{Kind: "tag", Name: "science", NoteCount: 3},
})
})
}
func TestCollectionDaoFindAllSortedByName(t *testing.T) {
testCollectionDAO(t, func(tx Transaction, dao *CollectionDAO) {
cs, err := dao.FindAll("tag", []core.CollectionSorter{
{Field: core.CollectionSortName, Ascending: false},
})
assert.Nil(t, err)
assert.Equal(t, cs, []core.Collection{
{Kind: "tag", Name: "science", NoteCount: 3},
{Kind: "tag", Name: "history", NoteCount: 1},
{Kind: "tag", Name: "fiction", NoteCount: 1},
{Kind: "tag", Name: "fantasy", NoteCount: 1},
{Kind: "tag", Name: "adventure", NoteCount: 2},
})
})
}
func TestCollectionDaoFindAllSortedByNoteCount(t *testing.T) {
testCollectionDAO(t, func(tx Transaction, dao *CollectionDAO) {
cs, err := dao.FindAll("tag", []core.CollectionSorter{
{Field: core.CollectionSortNoteCount, Ascending: false},
})
assert.Nil(t, err)
assert.Equal(t, cs, []core.Collection{
{Kind: "tag", Name: "science", NoteCount: 3},
{Kind: "tag", Name: "adventure", NoteCount: 2},
{Kind: "tag", Name: "fantasy", NoteCount: 1},
{Kind: "tag", Name: "fiction", NoteCount: 1},
{Kind: "tag", Name: "history", NoteCount: 1},
})
})
}

@ -673,7 +673,7 @@ func TestNoteDAOFindMentions(t *testing.T) {
RawContent: "# A nested note\nThis one is in a sub sub directory",
WordCount: 8,
Links: []core.Link{},
Tags: []string{"adventure", "history"},
Tags: []string{"adventure", "history", "science"},
Metadata: map[string]interface{}{},
Created: time.Date(2019, 11, 20, 20, 32, 56, 0, time.UTC),
Modified: time.Date(2019, 11, 20, 20, 34, 6, 0, time.UTC),

@ -47,9 +47,9 @@ func (ni *NoteIndex) FindMinimal(opts core.NoteFindOpts) (notes []core.MinimalNo
}
// FindCollections implements core.NoteIndex.
func (ni *NoteIndex) FindCollections(kind core.CollectionKind) (collections []core.Collection, err error) {
func (ni *NoteIndex) FindCollections(kind core.CollectionKind, sorters []core.CollectionSorter) (collections []core.Collection, err error) {
err = ni.commit(func(dao *dao) error {
collections, err = dao.collections.FindAll(kind)
collections, err = dao.collections.FindAll(kind, sorters)
return err
})
return

@ -16,3 +16,6 @@
- id: 6
kind: "tag"
name: "empty"
- id: 7
kind: "tag"
name: "science"

@ -16,3 +16,12 @@
- id: 6
note_id: 5 # ref/test/b.md
collection_id: 5 # tag:adventure
- id: 7
note_id: 5 # ref/test/b.md
collection_id: 7 # tag:science
- id: 8
note_id: 4 # f39c8.md
collection_id: 7 # tag:science
- id: 9
note_id: 5 # ref/test/b.md
collection_id: 7 # tag:science

@ -0,0 +1,148 @@
package cmd
import (
"fmt"
"io"
"os"
"github.com/mickael-menu/zk/internal/cli"
"github.com/mickael-menu/zk/internal/core"
"github.com/mickael-menu/zk/internal/util/errors"
"github.com/mickael-menu/zk/internal/util/strings"
)
// Tag manages the note tags in the notebook.
type Tag struct {
List TagList `cmd group:"cmd" default:"withargs" help:"List all the note tags."`
}
// TagList lists all the note tags.
type TagList struct {
Format string `group:format short:f placeholder:TEMPLATE help:"Pretty print the list using a custom template or one of the predefined formats: name, full, json, jsonl."`
Header string `group:format help:"Arbitrary text printed at the start of the list."`
Footer string `group:format default:\n help:"Arbitrary text printed at the end of the list."`
Delimiter string "group:format short:d default:\n help:\"Print tags delimited by the given separator.\""
Delimiter0 bool "group:format short:0 name:delimiter0 help:\"Print tags delimited by ASCII NUL characters. This is useful when used in conjunction with `xargs -0`.\""
NoPager bool `group:format short:P help:"Do not pipe output into a pager."`
Quiet bool `group:format short:q help:"Do not print the total number of tags found."`
Sort []string `group:sort short:s placeholder:TERM help:"Order the tags by the given criterion."`
}
func (cmd *TagList) Run(container *cli.Container) error {
cmd.Header = strings.ExpandWhitespaceLiterals(cmd.Header)
cmd.Footer = strings.ExpandWhitespaceLiterals(cmd.Footer)
cmd.Delimiter = strings.ExpandWhitespaceLiterals(cmd.Delimiter)
if cmd.Delimiter0 {
if cmd.Delimiter != "\n" {
return errors.New("--delimiter and --delimiter0 can't be used together")
}
if cmd.Header != "" {
return errors.New("--footer and --delimiter0 can't be used together")
}
if cmd.Footer != "\n" {
return errors.New("--footer and --delimiter0 can't be used together")
}
cmd.Delimiter = "\x00"
cmd.Footer = "\x00"
}
if cmd.Format == "json" || cmd.Format == "jsonl" {
if cmd.Header != "" {
return errors.New("--header can't be used with JSON format")
}
if cmd.Footer != "\n" {
return errors.New("--footer can't be used with JSON format")
}
if cmd.Delimiter != "\n" {
return errors.New("--delimiter can't be used with JSON format")
}
switch cmd.Format {
case "json":
cmd.Delimiter = ","
cmd.Header = "["
cmd.Footer = "]\n"
case "jsonl":
// > The last character in the file may be a line separator, and it
// > will be treated the same as if there was no line separator
// > present.
// > https://jsonlines.org/
cmd.Footer = "\n"
}
}
notebook, err := container.CurrentNotebook()
if err != nil {
return err
}
format, err := notebook.NewCollectionFormatter(cmd.tagTemplate())
if err != nil {
return err
}
sorters, err := core.CollectionSortersFromStrings(cmd.Sort)
if err != nil {
return err
}
tags, err := notebook.FindCollections(core.CollectionKindTag, sorters)
if err != nil {
return err
}
count := len(tags)
if count > 0 {
err = container.Paginate(cmd.NoPager, func(out io.Writer) error {
if cmd.Header != "" {
fmt.Fprint(out, cmd.Header)
}
for i, tag := range tags {
if i > 0 {
fmt.Fprint(out, cmd.Delimiter)
}
ft, err := format(tag)
if err != nil {
return err
}
fmt.Fprint(out, ft)
}
if cmd.Footer != "" {
fmt.Fprint(out, cmd.Footer)
}
return nil
})
}
if err == nil && !cmd.Quiet {
fmt.Fprintf(os.Stderr, "\nFound %d %s\n", count, strings.Pluralize("tag", count))
}
return err
}
func (cmd *TagList) tagTemplate() string {
format := cmd.Format
if format == "" {
format = "full"
}
templ, ok := defaultTagFormats[format]
if !ok {
templ = strings.ExpandWhitespaceLiterals(format)
}
return templ
}
var defaultTagFormats = map[string]string{
"json": `{{json .}}`,
"jsonl": `{{json .}}`,
"name": `{{name}}`,
"full": `{{name}} ({{note-count}})`,
}

@ -1,5 +1,11 @@
package core
import (
"fmt"
"strings"
"unicode/utf8"
)
// Collection represents a collection, such as a tag.
type Collection struct {
// Unique ID of this collection in the Notebook.
@ -43,8 +49,8 @@ type CollectionRepository interface {
FindOrCreateCollection(name string, kind CollectionKind) (CollectionID, error)
// FindCollections returns the list of all collections in the repository
// for the given kind.
FindCollections(kind CollectionKind) ([]Collection, error)
// for the given kind, ordered with the given sorters.
FindCollections(kind CollectionKind, sorters []CollectionSorter) ([]Collection, error)
// AssociateNoteCollection creates a new association between a note and a
// collection, if it does not already exist.
@ -54,3 +60,65 @@ type CollectionRepository interface {
// note.
RemoveNoteAssociations(noteId NoteID) error
}
// CollectionSorter represents an order term used to sort a list of collections.
type CollectionSorter struct {
Field CollectionSortField
Ascending bool
}
// CollectionSortField represents a collection field used to sort a list of collections.
type CollectionSortField int
const (
// Sort by the collection names.
CollectionSortName CollectionSortField = iota + 1
// Sort by the number of notes part of the collection.
CollectionSortNoteCount
)
// CollectionSortersFromStrings returns a list of CollectionSorter from their string
// representation.
func CollectionSortersFromStrings(strs []string) ([]CollectionSorter, error) {
sorters := make([]CollectionSorter, 0)
// Iterates in reverse order to be able to override sort criteria set in a
// config alias with a `--sort` flag.
for i := len(strs) - 1; i >= 0; i-- {
sorter, err := CollectionSorterFromString(strs[i])
if err != nil {
return sorters, err
}
sorters = append(sorters, sorter)
}
return sorters, nil
}
// CollectionSorterFromString returns a CollectionSorter from its string representation.
//
// If the input str has for suffix `+`, then the order will be ascending, while
// descending for `-`. If no suffix is given, then the default order for the
// sorting field will be used.
func CollectionSorterFromString(str string) (CollectionSorter, error) {
orderSymbol, _ := utf8.DecodeLastRuneInString(str)
str = strings.TrimRight(str, "+-")
var sorter CollectionSorter
switch str {
case "name", "n":
sorter = CollectionSorter{Field: CollectionSortName, Ascending: true}
case "note-count", "nc":
sorter = CollectionSorter{Field: CollectionSortNoteCount, Ascending: false}
default:
return sorter, fmt.Errorf("%s: unknown sorting term\ntry name or note-count", str)
}
switch orderSymbol {
case '+':
sorter.Ascending = true
case '-':
sorter.Ascending = false
}
return sorter, nil
}

@ -0,0 +1,44 @@
package core
import (
"encoding/json"
)
// CollectionFormatter formats collections to be printed on the screen.
type CollectionFormatter func(collection Collection) (string, error)
func newCollectionFormatter(template Template) (CollectionFormatter, error) {
return func(collection Collection) (string, error) {
return template.Render(collectionFormatRenderContext{
ID: collection.ID,
Kind: collection.Kind,
Name: collection.Name,
NoteCount: collection.NoteCount,
})
}, nil
}
// collectionFormatRenderContext holds the variables available to the
// collection formatting templates.
type collectionFormatRenderContext struct {
// Unique ID of this collection in the Notebook.
ID CollectionID `json:"id"`
// Kind of this note collection, such as a tag.
Kind CollectionKind `json:"kind"`
// Name of this collection.
Name string `json:"name"`
// Number of notes associated with this collection.
NoteCount int `json:"noteCount" handlebars:"note-count"`
}
func (c collectionFormatRenderContext) Equal(other collectionFormatRenderContext) bool {
json1, err := json.Marshal(c)
if err != nil {
return false
}
json2, err := json.Marshal(other)
if err != nil {
return false
}
return string(json1) == string(json2)
}

@ -20,7 +20,7 @@ type NoteIndex interface {
FindMinimal(opts NoteFindOpts) ([]MinimalNote, error)
// FindCollections retrieves all the collections of the given kind.
FindCollections(kind CollectionKind) ([]Collection, error)
FindCollections(kind CollectionKind, sorters []CollectionSorter) ([]Collection, error)
// Indexed returns the list of indexed note file metadata.
IndexedPaths() (<-chan paths.Metadata, error)

@ -504,7 +504,7 @@ type noteIndexAddMock struct {
func (m *noteIndexAddMock) Find(opts NoteFindOpts) ([]ContextualNote, error) { return nil, nil }
func (m *noteIndexAddMock) FindMinimal(opts NoteFindOpts) ([]MinimalNote, error) { return nil, nil }
func (m *noteIndexAddMock) FindCollections(kind CollectionKind) ([]Collection, error) {
func (m *noteIndexAddMock) FindCollections(kind CollectionKind, sorters []CollectionSorter) ([]Collection, error) {
return nil, nil
}
func (m *noteIndexAddMock) IndexedPaths() (<-chan paths.Metadata, error) { return nil, nil }

@ -247,8 +247,8 @@ func (n *Notebook) FindMatching(terms string) (*MinimalNote, error) {
}
// FindCollections retrieves all the collections of the given kind.
func (n *Notebook) FindCollections(kind CollectionKind) ([]Collection, error) {
return n.index.FindCollections(kind)
func (n *Notebook) FindCollections(kind CollectionKind, sorters []CollectionSorter) ([]Collection, error) {
return n.index.FindCollections(kind, sorters)
}
// RelPath returns the path relative to the notebook root to the given path.
@ -352,6 +352,20 @@ func (n *Notebook) NewNoteFormatter(templateString string) (NoteFormatter, error
return newNoteFormatter(n.Path, template, linkFormatter, n.osEnv(), n.fs)
}
// NewCollectionFormatter returns a CollectionFormatter used to format notes with the given template.
func (n *Notebook) NewCollectionFormatter(templateString string) (CollectionFormatter, error) {
templates, err := n.templateLoaderFactory(n.Config.Note.Lang)
if err != nil {
return nil, err
}
template, err := templates.LoadTemplate(templateString)
if err != nil {
return nil, err
}
return newCollectionFormatter(template)
}
// NewLinkFormatter returns a LinkFormatter used to generate internal links between notes.
func (n *Notebook) NewLinkFormatter() (LinkFormatter, error) {
templates, err := n.templateLoaderFactory(n.Config.Note.Lang)

@ -25,6 +25,7 @@ var root struct {
New cmd.New `cmd group:"notes" help:"Create a new note in the given notebook directory."`
List cmd.List `cmd group:"notes" help:"List notes matching the given criteria."`
Edit cmd.Edit `cmd group:"notes" help:"Edit notes matching the given criteria."`
Tag cmd.Tag `cmd group:"notes" help:"Manage the note tags."`
NotebookDir string `type:path placeholder:PATH help:"Turn off notebook auto-discovery and set manually the notebook where commands are run."`
WorkingDir string `short:W type:path placeholder:PATH help:"Run as if zk was started in <PATH> instead of the current working directory."`
@ -111,6 +112,7 @@ func options(container *cli.Container) []kong.Option {
"version": "zk " + strings.TrimPrefix(Version, "v"),
},
kong.Groups(map[string]string{
"cmd": "Commands:",
"filter": "Filtering",
"sort": "Sorting",
"format": "Formatting",

Loading…
Cancel
Save