aboutsummaryrefslogtreecommitdiffstats
path: root/cache/query.go
blob: 14ef0baec794b5aaf98aeee46da1a4332b1af592 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package cache

import (
	"fmt"
	"strings"
	"unicode"
)

type Query struct {
	Filters
	OrderBy
	OrderDirection
}

// ParseQuery parse a query DSL
//
// Ex: "status:open author:descartes sort:edit-asc"
//
// Supported filter fields and syntax are described in docs/queries.md
func ParseQuery(query string) (*Query, error) {
	fields := splitQuery(query)

	result := &Query{
		OrderBy:        OrderByCreation,
		OrderDirection: OrderDescending,
	}

	sortingDone := false

	for _, field := range fields {
		split := strings.Split(field, ":")
		if len(split) != 2 {
			return nil, fmt.Errorf("can't parse \"%s\"", field)
		}

		qualifierName := split[0]
		qualifierQuery := removeQuote(split[1])

		switch qualifierName {
		case "status", "state":
			f, err := StatusFilter(qualifierQuery)
			if err != nil {
				return nil, err
			}
			result.Status = append(result.Status, f)

		case "author":
			f := AuthorFilter(qualifierQuery)
			result.Author = append(result.Author, f)

		case "label":
			f := LabelFilter(qualifierQuery)
			result.Label = append(result.Label, f)

		case "no":
			err := result.parseNoFilter(qualifierQuery)
			if err != nil {
				return nil, err
			}

		case "sort":
			if sortingDone {
				return nil, fmt.Errorf("multiple sorting")
			}

			err := result.parseSorting(qualifierQuery)
			if err != nil {
				return nil, err
			}

			sortingDone = true

		default:
			return nil, fmt.Errorf("unknow qualifier name %s", qualifierName)
		}
	}

	return result, nil
}

func splitQuery(query string) []string {
	lastQuote := rune(0)
	f := func(c rune) bool {
		switch {
		case c == lastQuote:
			lastQuote = rune(0)
			return false
		case lastQuote != rune(0):
			return false
		case unicode.In(c, unicode.Quotation_Mark):
			lastQuote = c
			return false
		default:
			return unicode.IsSpace(c)
		}
	}

	return strings.FieldsFunc(query, f)
}

func removeQuote(field string) string {
	if len(field) >= 2 {
		if field[0] == '"' && field[len(field)-1] == '"' {
			return field[1 : len(field)-1]
		}
	}
	return field
}

func (q *Query) parseNoFilter(query string) error {
	switch query {
	case "label":
		q.NoFilters = append(q.NoFilters, NoLabelFilter())
	default:
		return fmt.Errorf("unknown \"no\" filter")
	}

	return nil
}

func (q *Query) parseSorting(query string) error {
	switch query {
	// default ASC
	case "id-desc":
		q.OrderBy = OrderById
		q.OrderDirection = OrderDescending
	case "id", "id-asc":
		q.OrderBy = OrderById
		q.OrderDirection = OrderAscending

	// default DESC
	case "creation", "creation-desc":
		q.OrderBy = OrderByCreation
		q.OrderDirection = OrderDescending
	case "creation-asc":
		q.OrderBy = OrderByCreation
		q.OrderDirection = OrderAscending

	// default DESC
	case "edit", "edit-desc":
		q.OrderBy = OrderByEdit
		q.OrderDirection = OrderDescending
	case "edit-asc":
		q.OrderBy = OrderByEdit
		q.OrderDirection = OrderAscending

	default:
		return fmt.Errorf("unknow sorting %s", query)
	}

	return nil
}