summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/pkg/matcher/matcher.go
blob: 76d903325105c0d5a259b053d50d1e92d6dd735c (plain)
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
// SPDX-License-Identifier: GPL-3.0-or-later

package matcher

import (
	"errors"
	"fmt"
	"regexp"
)

type (
	// Matcher is an interface that wraps MatchString method.
	Matcher interface {
		// Match performs match against given []byte
		Match(b []byte) bool
		// MatchString performs match against given string
		MatchString(string) bool
	}

	// Format matcher format
	Format string
)

const (
	// FmtString is a string match format.
	FmtString Format = "string"
	// FmtGlob is a glob match format.
	FmtGlob Format = "glob"
	// FmtRegExp is a regex match format.
	FmtRegExp Format = "regexp"
	// FmtSimplePattern is a simple pattern match format
	// https://docs.netdata.cloud/libnetdata/simple_pattern/
	FmtSimplePattern Format = "simple_patterns"

	// Separator is a separator between match format and expression.
	Separator = ":"
)

const (
	symString = "="
	symGlob   = "*"
	symRegExp = "~"
)

var (
	reShortSyntax = regexp.MustCompile(`(?s)^(!)?(.)\s*(.*)$`)
	reLongSyntax  = regexp.MustCompile(`(?s)^(!)?([^:]+):(.*)$`)

	errNotShortSyntax = errors.New("not short syntax")
)

// Must is a helper that wraps a call to a function returning (Matcher, error) and panics if the error is non-nil.
// It is intended for use in variable initializations such as
//
//	var m = matcher.Must(matcher.New(matcher.FmtString, "hello world"))
func Must(m Matcher, err error) Matcher {
	if err != nil {
		panic(err)
	}
	return m
}

// New create a matcher
func New(format Format, expr string) (Matcher, error) {
	switch format {
	case FmtString:
		return NewStringMatcher(expr, true, true)
	case FmtGlob:
		return NewGlobMatcher(expr)
	case FmtRegExp:
		return NewRegExpMatcher(expr)
	case FmtSimplePattern:
		return NewSimplePatternsMatcher(expr)
	default:
		return nil, fmt.Errorf("unsupported matcher format: '%s'", format)
	}
}

// Parse parses line and returns appropriate matcher based on matched format.
//
// Short Syntax
//
//	<line>      ::= [ <not> ] <format> <space> <expr>
//	<not>       ::= '!'
//	                  negative expression
//	<format>    ::= [ '=', '~', '*' ]
//	                  '=' means string match
//	                  '~' means regexp match
//	                  '*' means glob match
//	<space>     ::= { ' ' | '\t' | '\n' | '\n' | '\r' }
//	<expr>      ::= any string
//
// Long Syntax
//
//	<line>      ::= [ <not> ] <format> <separator> <expr>
//	<format>    ::= [ 'string' | 'glob' | 'regexp' | 'simple_patterns' ]
//	<not>       ::= '!'
//	                  negative expression
//	<separator> ::= ':'
//	<expr>      ::= any string
func Parse(line string) (Matcher, error) {
	matcher, err := parseShortFormat(line)
	if err == nil {
		return matcher, nil
	}
	return parseLongSyntax(line)
}

func parseShortFormat(line string) (Matcher, error) {
	m := reShortSyntax.FindStringSubmatch(line)
	if m == nil {
		return nil, errNotShortSyntax
	}
	var format Format
	switch m[2] {
	case symString:
		format = FmtString
	case symGlob:
		format = FmtGlob
	case symRegExp:
		format = FmtRegExp
	default:
		return nil, fmt.Errorf("invalid short syntax: unknown symbol '%s'", m[2])
	}
	expr := m[3]
	matcher, err := New(format, expr)
	if err != nil {
		return nil, err
	}
	if m[1] != "" {
		matcher = Not(matcher)
	}
	return matcher, nil
}

func parseLongSyntax(line string) (Matcher, error) {
	m := reLongSyntax.FindStringSubmatch(line)
	if m == nil {
		return nil, fmt.Errorf("invalid syntax")
	}
	matcher, err := New(Format(m[2]), m[3])
	if err != nil {
		return nil, err
	}
	if m[1] != "" {
		matcher = Not(matcher)
	}
	return matcher, nil
}