mesh/merge-gate head a3b51a9d9a02 merged into trunk db67a0e55ec3 — pass: the change touches no module of the mesh's graph
mesh/repo-check head a3b51a9d9a02 merged into trunk db67a0e55ec3 — pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh): main's version judged it; the ch…
mesh/delivery delivered
mesh/check failed: unit: nfsserver/cmd/nfs-server.TestARealRootOwnedFolderIsRefused: --- FAIL: TestARealRootOwnedFolderIsRefused (0.00s) | a3b51a9d9a0…
The forge blank hid any novox/ path segment, and joined names passed; read covered from the root.
160 lines
5.1 KiB
Go
160 lines
5.1 KiB
Go
// Command particulars keeps this mesh's node names out of the modules it covers (novox/hq ADR 0507 §9,
|
|
// issue 1838): a module is for any mesh, so no manifest, file, code string, identifier, comment, test name or
|
|
// fixture of a covered module names a node of this one.
|
|
//
|
|
// The names are in the file `particulars` at the catalogue's root, one a line, `#` comments — the one place
|
|
// they may be written. The modules covered are in `covered` at checks/particulars/covered, one a line. A name is matched
|
|
// whole and in any case; a hyphen, an underscore or a CamelCase boundary parts one, so a longer word holding a name is another word.
|
|
// The forge is not a node name: its host (git.novox.be) and the organisation path (novox/<repo>) are blanked
|
|
// before a line is read.
|
|
//
|
|
// go run . <catalogue root>
|
|
//
|
|
// Exits 1 naming file:line and the word, 2 when a list cannot be read.
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// forge is the forge's host and organisation path, blanked before a line is read; a path segment novox/ elsewhere
|
|
// (testdata/novox/x, /srv/novox/data) is not blanked.
|
|
var forge = regexp.MustCompile(`(?i)git\.novox\.be(?:/novox/[\w.-]+)?|(?:^|[^/\w.-])novox/[\w.-]+`)
|
|
|
|
func main() {
|
|
root := "."
|
|
if len(os.Args) > 1 {
|
|
root = os.Args[1]
|
|
}
|
|
os.Exit(check(root))
|
|
}
|
|
|
|
// check runs the check over a catalogue and returns the exit code: 0 clean, 1 a finding, 2 a list unreadable.
|
|
func check(root string) int {
|
|
names, err := readList(filepath.Join(root, "particulars"))
|
|
if err == nil && len(names) == 0 {
|
|
err = fmt.Errorf("particulars lists no name — the check would pass on anything")
|
|
}
|
|
var covered []string
|
|
if err == nil {
|
|
covered, err = readList(filepath.Join(root, "checks/particulars/covered"))
|
|
if err == nil && len(covered) == 0 {
|
|
err = fmt.Errorf("covered lists no module — the check would pass on anything")
|
|
}
|
|
}
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "particulars:", err)
|
|
return 2
|
|
}
|
|
findings, err := run(root, names, covered)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "particulars:", err)
|
|
return 2
|
|
}
|
|
for _, f := range findings {
|
|
fmt.Println(f)
|
|
}
|
|
if len(findings) > 0 {
|
|
fmt.Printf("particulars: %d node name(s) in a module for any mesh (novox/hq ADR 0507 §9) — say what it shows, not where it was seen\n", len(findings))
|
|
return 1
|
|
}
|
|
fmt.Printf("particulars: none of %d node names in the %d covered modules\n", len(names), len(covered))
|
|
return 0
|
|
}
|
|
|
|
// readList reads a list: one entry a line; blank lines and lines starting with # are not entries.
|
|
func readList(path string) ([]string, error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
var out []string
|
|
sc := bufio.NewScanner(f)
|
|
for sc.Scan() {
|
|
line := strings.TrimSpace(sc.Text())
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
out = append(out, line)
|
|
}
|
|
return out, sc.Err()
|
|
}
|
|
|
|
// run reads every text file of each covered module and returns the findings, sorted.
|
|
func run(root string, names, covered []string) ([]string, error) {
|
|
var out []string
|
|
for _, m := range covered {
|
|
dir := filepath.Join(root, "modules", m)
|
|
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if d.IsDir() {
|
|
switch d.Name() {
|
|
case "node_modules", "dist", "vendor", ".git":
|
|
return filepath.SkipDir
|
|
}
|
|
return nil
|
|
}
|
|
rel, _ := filepath.Rel(root, path)
|
|
// A path is read too: a testdata directory named after a node is a finding.
|
|
for _, n := range hits(filepath.ToSlash(rel), names) {
|
|
out = append(out, fmt.Sprintf("%s:0: node name %q in the path", filepath.ToSlash(rel), n))
|
|
}
|
|
if d.Name() == "go.sum" {
|
|
return nil
|
|
}
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bytes.IndexByte(b, 0) >= 0 {
|
|
return nil
|
|
}
|
|
out = append(out, scan(filepath.ToSlash(rel), string(b), names)...)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
sort.Strings(out)
|
|
return out, nil
|
|
}
|
|
|
|
// scan returns a finding for each node name in src, whole-word and in any case.
|
|
func scan(file, src string, names []string) []string {
|
|
var out []string
|
|
for i, line := range strings.Split(src, "\n") {
|
|
for _, n := range hits(line, names) {
|
|
out = append(out, fmt.Sprintf("%s:%d: node name %q — %s", file, i+1, n, strings.Join(strings.Fields(line), " ")))
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// hits returns the node names one line holds: as a word (a hyphen or underscore parts it), as a part of a
|
|
// CamelCase identifier (TestAce, novoxTree), the forge's host and organisation path blanked first.
|
|
func hits(line string, names []string) []string {
|
|
line = forge.ReplaceAllStringFunc(line, func(s string) string { return strings.Repeat(" ", len(s)) })
|
|
var out []string
|
|
for _, n := range names {
|
|
q := regexp.QuoteMeta(n)
|
|
camel := regexp.QuoteMeta(strings.ToUpper(n[:1]) + n[1:])
|
|
rx := regexp.MustCompile(`(?i:(?:^|[^A-Za-z0-9])` + q + `(?:$|[^A-Za-z0-9]))|[a-z0-9]` + camel + `(?:$|[^a-z])|(?:^|[^A-Za-z0-9])` + q + `[A-Z0-9]`)
|
|
if rx.MatchString(line) {
|
|
out = append(out, strings.ToLower(n))
|
|
}
|
|
}
|
|
return out
|
|
}
|