-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
154 lines (134 loc) · 4.37 KB
/
main.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
145
146
147
148
149
150
151
152
153
154
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
)
func main() {
// Needs as argument a path to an Antora project
var antoraPath string
flag.StringVar(&antoraPath, "antoraPath", "/docs", "Path to Antora document sources")
// Module to check. If none passed, it is assumed to be ROOT
var module string
flag.StringVar(&module, "module", "ROOT", "Module to analyze")
// File to parse and analyze; if none is passed, it is assumed to
// be a standard Antora navigation file at the ROOT module.
var filename string
flag.StringVar(&filename, "filename", "/modules/ROOT/nav.adoc", "File to analyze")
flag.Parse()
// Main block
errors := check(antoraPath, module, filename)
// If there are errors, print the list of orphan files
if len(errors) > 0 {
for _, file := range errors {
fmt.Fprintf(os.Stderr, "File '%s' not in '%s'\n", file, filename)
}
os.Exit(1)
}
// No errors, all good!
}
// Takes a path and assumes it's a valid Antora project. It lists all pages
// contained in the module and then walks the file system to verify that
// all relevant files are referenced in the file.
func check(antoraPath string, module string, filename string) []string {
// We assume that the project follows a standard Antora layout
startPath := filepath.Join(antoraPath, "modules", module, "pages")
allFiles, err := listAllFiles(startPath)
if err != nil {
fmt.Println("Cannot list files in provided path " + startPath)
os.Exit(1)
}
// Remove the initial part of the path from all filenames in the list
simplified := mapArray(allFiles, func(input string) string {
return strings.Replace(input, startPath+"/", "", -1)
})
// Filter out some stuff we don't show on the navigation anyway
filtered := filterArray(simplified, func(input string) bool {
avoid := []string{"search.adoc", "index.adoc", "sitesearch.adoc", "changelog_from_commits.adoc", "changelog_legacy.adoc"}
return !stringInSlice(input, avoid)
})
// Verify that all filtered files appear in the file at least once
fullPath := filepath.Join(antoraPath, filename)
regex := `xref:(.+)\[`
// If the file being checked is not nav.adoc, then it is assumed to be a standard
// Asciidoc file, where files are referenced using `include::...[]` instead of `xref:...[]`
if !strings.HasSuffix(fullPath, "nav.adoc") {
regex = `include::(.+)\[`
}
// Gather all errors
errors := walk(fullPath, filtered, regex)
return errors
}
// Checks the index file for the existence of at least one
// reference to each one of the files passed in the second parameter,
// using the regular expresssion as parameter.
func walk(fullPath string, files []string, regex string) []string {
var errors []string
re := regexp.MustCompile(regex)
contents, err := os.ReadFile(fullPath)
if err == nil {
stringContents := string(contents)
matches := re.FindAllString(stringContents, -1)
errors = filterArray(files, func(file string) bool {
return !substringInSlice(file, matches)
})
} else {
errors = append(errors, "Cannot read file "+fullPath)
}
return errors
}
// Collect filenames recursively at the path provided as argument
func listAllFiles(startPath string) ([]string, error) {
var files []string
_, err := os.Stat(startPath)
if err != nil {
fmt.Println("Path does not exist " + startPath)
return files, err
}
err = filepath.Walk(startPath, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() && strings.HasSuffix(info.Name(), ".adoc") {
files = append(files, path)
}
return nil
})
return files, err
}
// Filter array using the function passed as argument
func filterArray(array []string, f func(string) bool) []string {
filtered := make([]string, 0)
for _, s := range array {
if f(s) {
filtered = append(filtered, s)
}
}
return filtered
}
// Apply the transformation function to all items of the array
func mapArray(array []string, f func(string) string) []string {
mapped := make([]string, len(array))
for i, e := range array {
mapped[i] = f(e)
}
return mapped
}
// Checks whether a string appears in an array
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
// Checks whether a string appears in an array complete or as a substring
func substringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a || strings.Index(b, a) != -1 {
return true
}
}
return false
}