/
scope.go
144 lines (126 loc) · 3.01 KB
/
scope.go
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
package check
import (
"strings"
"github.com/errata-ai/vale/v2/internal/core"
"github.com/errata-ai/vale/v2/internal/nlp"
)
var allowedScopes = []string{
"text",
"heading",
"heading.h1",
"heading.h2",
"heading.h3",
"heading.h4",
"heading.h5",
"heading.h6",
"table",
"table.header",
"table.cell",
"table.caption",
"figure.caption",
"list",
"paragraph",
"sentence",
"alt",
"title",
"blockquote",
"summary",
"raw",
}
// A Selector represents a named section of text.
type Selector struct {
Value []string // e.g., text.comment.line.py
Negated bool
}
type Scope struct {
Selectors map[string][]Selector
}
func NewSelector(value []string) Selector {
negated := false
parts := []string{}
for i, m := range value {
m = strings.TrimSpace(m)
if i == 0 && strings.HasPrefix(m, "~") {
m = strings.TrimPrefix(m, "~")
negated = true
}
parts = append(parts, m)
}
return Selector{Value: parts, Negated: negated}
}
func NewScope(value []string) Scope {
scope := map[string][]Selector{}
for _, v := range value {
selectors := []Selector{}
for _, part := range strings.Split(v, "&") {
selectors = append(selectors, NewSelector(strings.Split(part, ".")))
}
scope[v] = selectors
}
return Scope{Selectors: scope}
}
// Macthes the scope `s` matches `s2`.
func (s Scope) Matches(blk nlp.Block) bool {
candidate := NewSelector(strings.Split(blk.Scope, "."))
parent := NewSelector(strings.Split(blk.Parent, "."))
for _, sel := range s.Selectors {
if s.partMatches(candidate, parent, sel) {
return true
}
}
return false
}
func (s Scope) partMatches(target, parent Selector, options []Selector) bool {
for _, part := range options {
tm := target.Contains(part)
pm := parent.Contains(part)
if part.Negated && !pm {
if target.Has("raw") || target.Has("summary") {
// This can't apply to sized scopes.
return false
}
} else if (!part.Negated && !tm) || (part.Negated && pm) {
return false
}
}
return true
}
// Sections splits a Selector into its parts -- e.g., text.comment.line.py ->
// []string{"text", "comment", "line", "py"}.
func (s *Selector) Sections() []string {
parts := []string{}
for _, m := range s.Value {
parts = append(parts, strings.Split(m, ".")...)
}
return parts
}
// Contains determines if all if sel's sections are in s.
func (s *Selector) Contains(sel Selector) bool {
return core.AllStringsInSlice(sel.Sections(), s.Sections())
}
// ContainsString determines if all if sel's sections are in s.
func (s *Selector) ContainsString(scope []string) bool {
for _, option := range scope {
sel := Selector{Value: []string{option}}
if !s.Contains(sel) {
return false
}
}
return true
}
// Equal determines if sel == s.
func (s *Selector) Equal(sel Selector) bool {
if len(s.Value) == len(sel.Value) {
for i, v := range s.Value {
if sel.Value[i] != v {
return false
}
}
return true
}
return false
}
// Has determines if s has a part equal to scope.
func (s *Selector) Has(scope string) bool {
return core.StringInSlice(scope, s.Sections())
}