-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcheck.go
141 lines (120 loc) · 3.41 KB
/
check.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
package filenameslinter
import (
"errors"
"fmt"
"io/fs"
"path"
"regexp"
"sort"
"strings"
"github.com/cartabinaria/synta"
syntaRegexp "github.com/cartabinaria/synta/regexp"
log "log/slog"
)
type Options struct {
Recursive bool
EnsureKebabCasing bool
IgnoreDotfiles bool
FailFast bool
}
var kebabRegexp = regexp.MustCompile("^[a-z0-9]+(-[a-z0-9]+)*(\\.[a-z0-9]+)?$")
// ReadDir uses the `readDir` method if the filesystem implements
// `fs.ReadDirFS`, otherwise opens the path and parses it using
// the `ReadDirFile` interface.
func ReadDir(fsys fs.FS, name string) ([]fs.DirEntry, error) {
if fsys, ok := fsys.(fs.ReadDirFS); ok {
return fsys.ReadDir(name)
}
file, err := fsys.Open(name)
if err != nil {
return nil, err
}
dir, ok := file.(fs.ReadDirFile)
if !ok {
return nil, &fs.PathError{Op: "readdir", Path: name, Err: errors.New("not implemented")}
}
list, err := dir.ReadDir(-1)
sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
err = file.Close()
return list, err
}
func CheckDir(synta *synta.Synta, fs fs.FS, dirPath string, opts *Options) []error {
log.Info("checking dir", "path", dirPath)
errors := make([]error, 0)
entries, err := ReadDir(fs, dirPath)
if err != nil {
return []error{fmt.Errorf("Could not read directory: %v", err)}
}
for _, entry := range entries {
file, err := entry.Info()
if err != nil {
return []error{fmt.Errorf("could not read directory: %v", err)}
}
absPath := path.Join(dirPath, file.Name())
shouldIgonre := opts.IgnoreDotfiles && strings.HasPrefix(file.Name(), ".")
log.Info("checking path basename", "path", absPath, "ignored", shouldIgonre)
// 1. (optionally) ignore files that start with a '.'
if shouldIgonre {
continue
}
// 2. (optionally) force all files to use kebab casing
if opts.EnsureKebabCasing && !kebabRegexp.Match([]byte(file.Name())) {
err = RegexMatchError{
Regexp: kebabRegexp.String(),
Filename: file.Name(),
Path: absPath,
}
errors = append(errors, err)
if opts.FailFast {
return errors
}
}
// 3. If we have a synta definition, check the filename against it. Otherwise,
// only run recursive checks if it is a directory.
if synta != nil {
// 3a. Check the filename; if it's a directory and the name doesn't match,
// recursively check it.
err = CheckName(*synta, file.Name(), file.IsDir())
if err != nil && file.IsDir() && opts.Recursive {
errors = append(errors, CheckDir(synta, fs, absPath, opts)...)
if opts.FailFast && len(errors) > 0 {
return errors
}
} else if err != nil {
errors = append(errors, err)
if opts.FailFast {
return errors
}
}
} else if file.IsDir() && opts.Recursive {
errors = append(errors, CheckDir(synta, fs, absPath, opts)...)
if opts.FailFast && len(errors) > 0 {
return errors
}
}
}
return errors
}
func CheckName(synta synta.Synta, name string, isDir bool) (err error) {
var reg *regexp.Regexp = nil
if isDir {
reg, err = syntaRegexp.ConvertWithoutExtension(synta)
if err != nil {
err = fmt.Errorf("Could not convert synta to (dir) regexp: %v", err)
return
}
} else {
reg, err = syntaRegexp.Convert(synta)
if err != nil {
err = fmt.Errorf("Could not convert synta to (file) regexp: %v", err)
return
}
}
if !reg.Match([]byte(name)) {
err = RegexMatchError{
Regexp: reg.String(),
Filename: name,
}
}
return
}