diff --git a/pkg/search/expr.go b/pkg/search/expr.go index 90fe6f7a3..54b73e0b1 100644 --- a/pkg/search/expr.go +++ b/pkg/search/expr.go @@ -32,8 +32,9 @@ import ( ) var ( - tagExpr = regexp.MustCompile(`^tag:(\w+)$`) - titleExpr = regexp.MustCompile(`^title:(\S+)$`) // TODO: proper expr parser supporting quoting + tagExpr = regexp.MustCompile(`^tag:(.+)$`) + titleExpr = regexp.MustCompile(`^title:(.+)$`) // TODO: proper expr parser supporting quoting + attrExpr = regexp.MustCompile(`^attr:(\w+):(.+)$`) // used for width/height ranges. 10 is max length of 32-bit // int (strconv.Atoi on 32-bit platforms), even though a max @@ -259,6 +260,16 @@ func parseExpression(ctx *context.Context, exp string) (*SearchQuery, error) { and(locConstraint) continue } + if m := attrExpr.FindStringSubmatch(word); m != nil { + and(&Constraint{ + Permanode: &PermanodeConstraint{ + Attr: m[1], + SkipHidden: true, + Value: m[2], + }, + }) + continue + } log.Printf("Unknown search expression word %q", word) // TODO: finish. better tokenization. non-operator tokens // are text searches, etc. diff --git a/pkg/search/expr_test.go b/pkg/search/expr_test.go index 9bf0e6c81..f19068c19 100644 --- a/pkg/search/expr_test.go +++ b/pkg/search/expr_test.go @@ -103,6 +103,78 @@ var parseExprTests = []struct { }, }, + { + name: "tag with spaces", + in: `tag:"Foo Bar"`, + want: &SearchQuery{ + Constraint: &Constraint{ + Logical: &LogicalConstraint{ + Op: "and", + A: &Constraint{ + Permanode: &PermanodeConstraint{ + SkipHidden: true, + }, + }, + B: &Constraint{ + Permanode: &PermanodeConstraint{ + Attr: "tag", + Value: "Foo Bar", + SkipHidden: true, + }, + }, + }, + }, + }, + }, + + { + name: "attribute search", + in: "attr:foo:bar", + want: &SearchQuery{ + Constraint: &Constraint{ + Logical: &LogicalConstraint{ + Op: "and", + A: &Constraint{ + Permanode: &PermanodeConstraint{ + SkipHidden: true, + }, + }, + B: &Constraint{ + Permanode: &PermanodeConstraint{ + Attr: "foo", + Value: "bar", + SkipHidden: true, + }, + }, + }, + }, + }, + }, + + { + name: "attribute search with space in value", + in: `attr:foo:"fun bar"`, + want: &SearchQuery{ + Constraint: &Constraint{ + Logical: &LogicalConstraint{ + Op: "and", + A: &Constraint{ + Permanode: &PermanodeConstraint{ + SkipHidden: true, + }, + }, + B: &Constraint{ + Permanode: &PermanodeConstraint{ + Attr: "foo", + Value: "fun bar", + SkipHidden: true, + }, + }, + }, + }, + }, + }, + { in: "tag:funny", want: &SearchQuery{ @@ -204,6 +276,7 @@ func TestSplitExpr(t *testing.T) { {"foo bar", []string{"foo", "bar"}}, {" foo bar ", []string{"foo", "bar"}}, {`foo:"quoted string" bar`, []string{`foo:quoted string`, "bar"}}, + {`foo:"quoted \"-containing"`, []string{`foo:quoted "-containing`}}, } for _, tt := range tests { got := splitExpr(tt.in) @@ -225,6 +298,7 @@ func TestTokenizeExpr(t *testing.T) { {" -foo bar", []string{" ", "-", "foo", " ", "bar"}}, {`-"quote"foo`, []string{"-", `"quote"`, "foo"}}, {`foo:"quoted string" bar`, []string{"foo:", `"quoted string"`, " ", "bar"}}, + {`"quoted \"-containing"`, []string{`"quoted \"-containing"`}}, } for _, tt := range tests { got := tokenizeExpr(tt.in)