Refine path filtering with regexes

pull/6/head
Mickaël Menu 3 years ago
parent ac545f550a
commit c10449a425
No known key found for this signature in database
GPG Key ID: 53D73664CD359895

@ -10,6 +10,7 @@ import (
"github.com/mickael-menu/zk/util"
"github.com/mickael-menu/zk/util/errors"
"github.com/mickael-menu/zk/util/fts5"
"github.com/mickael-menu/zk/util/icu"
"github.com/mickael-menu/zk/util/paths"
strutil "github.com/mickael-menu/zk/util/strings"
)
@ -427,23 +428,23 @@ func (d *NoteDAO) findRows(opts note.FinderOpts) (*sql.Rows, error) {
if len(filter) == 0 {
break
}
globs := make([]string, 0)
regexes := make([]string, 0)
for _, path := range filter {
globs = append(globs, "n.path GLOB ?")
args = append(args, path+"*")
regexes = append(regexes, "n.path REGEXP ?")
args = append(args, pathRegex(path))
}
whereExprs = append(whereExprs, strings.Join(globs, " OR "))
whereExprs = append(whereExprs, strings.Join(regexes, " OR "))
case note.ExcludePathFilter:
if len(filter) == 0 {
break
}
globs := make([]string, 0)
regexes := make([]string, 0)
for _, path := range filter {
globs = append(globs, "n.path NOT GLOB ?")
args = append(args, path+"*")
regexes = append(regexes, "n.path NOT REGEXP ?")
args = append(args, pathRegex(path))
}
whereExprs = append(whereExprs, strings.Join(globs, " AND "))
whereExprs = append(whereExprs, strings.Join(regexes, " AND "))
case note.LinkedByFilter:
maxDistance = filter.MaxDistance
@ -606,3 +607,10 @@ func orderTerm(sorter note.Sorter) string {
panic(fmt.Sprintf("%v: unknown note.SortField", sorter.Field))
}
}
// pathRegex returns an ICU regex to match the files in the folder at given
// `path`, or any file having `path` for prefix.
func pathRegex(path string) string {
path = icu.EscapePattern(path)
return path + "[^/]*|" + path + "/.+"
}

@ -483,15 +483,42 @@ func TestNoteDAOFindMatchWithSort(t *testing.T) {
)
}
func TestNoteDAOFindInPath(t *testing.T) {
func TestNoteDAOFindInPathAbsoluteFile(t *testing.T) {
testNoteDAOFindPaths(t,
note.FinderOpts{
Filters: []note.Filter{note.PathFilter([]string{"log/2021-01-*"})},
Filters: []note.Filter{note.PathFilter([]string{"log/2021-01-03.md"})},
},
[]string{"log/2021-01-03.md"},
)
}
// You can look for files with only their prefix.
func TestNoteDAOFindInPathWithFilePrefix(t *testing.T) {
testNoteDAOFindPaths(t,
note.FinderOpts{
Filters: []note.Filter{note.PathFilter([]string{"log/2021-01"})},
},
[]string{"log/2021-01-03.md", "log/2021-01-04.md"},
)
}
// For directory, only complete names work, no prefixes.
func TestNoteDAOFindInPathRequiresCompleteDirName(t *testing.T) {
testNoteDAOFindPaths(t,
note.FinderOpts{
Filters: []note.Filter{note.PathFilter([]string{"lo"})},
},
[]string{},
)
testNoteDAOFindPaths(t,
note.FinderOpts{
Filters: []note.Filter{note.PathFilter([]string{"log"})},
},
[]string{"log/2021-02-04.md", "log/2021-01-03.md", "log/2021-01-04.md"},
)
}
// You can look for multiple paths, in which case notes can be in any of them.
func TestNoteDAOFindInMultiplePaths(t *testing.T) {
testNoteDAOFindPaths(t,
note.FinderOpts{
@ -513,7 +540,7 @@ func TestNoteDAOFindExcludingPath(t *testing.T) {
func TestNoteDAOFindExcludingMultiplePaths(t *testing.T) {
testNoteDAOFindPaths(t,
note.FinderOpts{
Filters: []note.Filter{note.ExcludePathFilter([]string{"ref", "log/2021-01-*"})},
Filters: []note.Filter{note.ExcludePathFilter([]string{"ref", "log/2021-01"})},
},
[]string{"f39c8.md", "log/2021-02-04.md", "index.md"},
)

@ -0,0 +1,19 @@
package icu
// EscapePattern adds backslash escapes to protect any characters that would
// match as ICU pattern metacharacters.
//
// http://userguide.icu-project.org/strings/regexp
func EscapePattern(s string) string {
out := ""
for _, c := range s {
switch c {
case '\\', '.', '^', '$', '(', ')', '[', ']', '{', '}', '|', '*', '+', '?':
out += `\`
}
out += string(c)
}
return out
}

@ -0,0 +1,32 @@
package icu
import (
"testing"
"github.com/mickael-menu/zk/util/test/assert"
)
func TestEscapePAttern(t *testing.T) {
tests := map[string]string{
`foo bar`: `foo bar`,
`\a`: `\\a`,
`.`: `\.`,
`^`: `\^`,
`$`: `\$`,
`(`: `\(`,
`)`: `\)`,
`[`: `\[`,
`]`: `\]`,
`{`: `\{`,
`}`: `\}`,
`|`: `\|`,
`*`: `\*`,
`+`: `\+`,
`?`: `\?`,
`(?:[A-Za-z0-9]+[._]?){1,}[A-Za-z0-9]+\@(?:(?:[A-Za-z0-9]+[-]?){1,}[A-Za-z0-9]+\.){1,}`: `\(\?:\[A-Za-z0-9\]\+\[\._\]\?\)\{1,\}\[A-Za-z0-9\]\+\\@\(\?:\(\?:\[A-Za-z0-9\]\+\[-\]\?\)\{1,\}\[A-Za-z0-9\]\+\\\.\)\{1,\}`,
}
for input, expected := range tests {
assert.Equal(t, EscapePattern(input), expected)
}
}
Loading…
Cancel
Save