summaryrefslogtreecommitdiff
path: root/internal/cond/types.go
blob: 9455f63bbc21b778fb85991ec9e2b33737fc3e1e (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
// SPDX-License-Identifier: GPL-3.0-or-later

// Package cond compiles the s-expression conditions of a rule's (when ...)
// into a tree that Task 8's evaluator walks against one file's facts.
package cond

import (
	"regexp"
	"time"

	"krino/internal/sexp"
)

// Options carries a rule's resolved case and fold settings into compilation.
type Options struct {
	IgnoreCase bool // the rule's resolved case setting is "ignore"
	Fold       bool // the rule's resolved fold setting
}

// Cond is a compiled condition. A Cond compiled from no conditions (a rule
// without when) is always true.
type Cond struct {
	root        *node      // nil: always true
	opt         Options    // the case/fold settings conditions were compiled with; Task 8 needs them again at eval time
	UsesContent bool       // some content test exists
	DupDirs     [][]string // the raw directory arguments of each duplicate test, in order
}

// kind is what a compiled node tests, or how it combines its children.
type kind int

const (
	kAnd kind = iota
	kOr
	kNot
	kType
	kName
	kPath
	kContent
	kSize
	kAge
	kDuplicate
	kMatched
)

// Costs, per the brief's cost order: cheapest first when sorting and/or
// children. not takes its child's cost; and/or take the sum of theirs.
const (
	costCheap     = 1 // type, size, age, matched
	costRegex     = 2 // name, path
	costDuplicate = 5
	costContent   = 10
)

// pattern is one name/path regex, compiled and paired with the text it was
// written as (undecorated by (?i) or folding), for labels and reasons.
type pattern struct {
	re  *regexp.Regexp
	src string
}

// keyword is one content keyword, normalised for matching and paired with
// the text it was written as, for labels and reasons.
type keyword struct {
	norm string
	src  string
}

// node is one compiled condition: a leaf test, or an and/or/not combinator
// over other nodes. Task 8 evaluates this tree.
type node struct {
	kind     kind
	pos      sexp.Pos // the position of the node as written, for diagnostics
	label    string   // the test as written, e.g. `content "acme ltd" "0000000000"`
	cost     int      // this node's evaluation cost; and/or sort children by it
	children []*node  // and, or, not

	// type
	suffixes []string // leading-dot, lower-case, e.g. ".pdf"

	// name, path
	patterns []pattern

	// content
	keywords []keyword

	// size, age (kind tells which is populated)
	op      string
	sizeVal int64
	ageVal  time.Duration

	// duplicate
	dirs []string
}

// NameGroups returns the number of capture groups of every name test in the
// condition that can ever supply captures, in compile order: a name test
// nested inside and/or counts however deep, but one inside a not does not
// (B1) - it can never be the source of Result.Captures, so it must not be
// asked to justify a rule's use of {N} either. Empty when the rule has no
// such name test.
func (c *Cond) NameGroups() []int {
	var out []int
	collectNameGroups(c.root, &out)
	return out
}

// groups maps a (type ...) group name to the extensions it expands to,
// spec Appendix A.
var groups = map[string][]string{
	"image":        {"jpg", "jpeg", "png", "gif", "webp", "bmp", "tif", "tiff", "heic", "heif", "avif", "svg", "ico", "raw", "cr2", "nef", "arw", "dng"},
	"video":        {"mp4", "mkv", "webm", "mov", "avi", "m4v", "mpg", "mpeg", "wmv", "flv", "3gp"},
	"audio":        {"mp3", "flac", "ogg", "opus", "m4a", "aac", "wav", "wma", "aiff"},
	"archive":      {"zip", "tar", "gz", "tgz", "bz2", "tbz2", "xz", "txz", "zst", "7z", "rar", "lz", "lzma", "cpio"},
	"document":     {"pdf", "doc", "docx", "odt", "rtf", "txt", "md", "tex"},
	"spreadsheet":  {"xls", "xlsx", "ods", "csv", "tsv"},
	"presentation": {"ppt", "pptx", "odp"},
	"ebook":        {"epub", "mobi", "azw", "azw3", "fb2", "djvu"},
	"code":         {"go", "c", "h", "cpp", "hpp", "py", "sh", "js", "ts", "rs", "java", "rb", "pl", "lua", "html", "css", "json", "yaml", "yml", "toml", "xml", "sql"},
	"text":         {"txt", "md", "log", "csv", "tsv", "json", "yaml", "yml", "toml", "xml", "ini", "conf"},
	"package":      {"deb", "rpm", "apk", "appimage", "exe", "msi", "flatpak", "snap"},
	"font":         {"ttf", "otf", "woff", "woff2"},
}