Skip to content

Commit 36d3389

Browse files
committed
refactor: start splitting up files
1 parent 404b1e0 commit 36d3389

File tree

4 files changed

+150
-77
lines changed

4 files changed

+150
-77
lines changed

cmd/readmevalidation/contributors.go

Lines changed: 8 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package main
22

33
import (
4-
"bufio"
54
"errors"
65
"fmt"
76
"net/url"
@@ -13,18 +12,10 @@ import (
1312
"gopkg.in/yaml.v3"
1413
)
1514

16-
const rootRegistryPath = "./registry"
17-
1815
var (
19-
validContributorStatuses = []string{"official", "partner", "community"}
20-
supportedAvatarFileFormats = []string{".png", ".jpeg", ".jpg", ".gif", ".svg"}
16+
validContributorStatuses = []string{"official", "partner", "community"}
2117
)
2218

23-
type readme struct {
24-
filePath string
25-
rawText string
26-
}
27-
2819
type contributorProfileFrontmatter struct {
2920
DisplayName string `yaml:"display_name"`
3021
Bio string `yaml:"bio"`
@@ -44,61 +35,6 @@ type contributorProfile struct {
4435
filePath string
4536
}
4637

47-
var _ error = validationPhaseError{}
48-
49-
type validationPhaseError struct {
50-
phase string
51-
errors []error
52-
}
53-
54-
func (vpe validationPhaseError) Error() string {
55-
validationStrs := []string{}
56-
for _, e := range vpe.errors {
57-
validationStrs = append(validationStrs, fmt.Sprintf("- %v", e))
58-
}
59-
slices.Sort(validationStrs)
60-
61-
msg := fmt.Sprintf("Error during %q phase of README validation:", vpe.phase)
62-
msg += strings.Join(validationStrs, "\n")
63-
msg += "\n"
64-
65-
return msg
66-
}
67-
68-
func extractFrontmatter(readmeText string) (string, error) {
69-
if readmeText == "" {
70-
return "", errors.New("README is empty")
71-
}
72-
73-
const fence = "---"
74-
fm := ""
75-
fenceCount := 0
76-
lineScanner := bufio.NewScanner(
77-
strings.NewReader(strings.TrimSpace(readmeText)),
78-
)
79-
for lineScanner.Scan() {
80-
nextLine := lineScanner.Text()
81-
if fenceCount == 0 && nextLine != fence {
82-
return "", errors.New("README does not start with frontmatter fence")
83-
}
84-
85-
if nextLine != fence {
86-
fm += nextLine + "\n"
87-
continue
88-
}
89-
90-
fenceCount++
91-
if fenceCount >= 2 {
92-
break
93-
}
94-
}
95-
96-
if fenceCount == 1 {
97-
return "", errors.New("README does not have two sets of frontmatter fences")
98-
}
99-
return fm, nil
100-
}
101-
10238
func validateContributorGithubUsername(githubUsername string) error {
10339
if githubUsername == "" {
10440
return errors.New("missing GitHub username")
@@ -260,10 +196,6 @@ func validateContributorAvatarURL(avatarURL *string) []error {
260196
return problems
261197
}
262198

263-
func addFilePathToError(filePath string, err error) error {
264-
return fmt.Errorf("%q: %v", filePath, err)
265-
}
266-
267199
func validateContributorYaml(yml contributorProfile) []error {
268200
allProblems := []error{}
269201

@@ -297,7 +229,7 @@ func validateContributorYaml(yml contributorProfile) []error {
297229
}
298230

299231
func parseContributorProfile(rm readme) (contributorProfile, error) {
300-
fm, err := extractFrontmatter(rm.rawText)
232+
fm, _, err := separateFrontmatter(rm.rawText)
301233
if err != nil {
302234
return contributorProfile{}, fmt.Errorf("%q: failed to parse frontmatter: %v", rm.filePath, err)
303235
}
@@ -331,7 +263,7 @@ func parseContributorFiles(readmeEntries []readme) (map[string]contributorProfil
331263
}
332264
if len(yamlParsingErrors) != 0 {
333265
return nil, validationPhaseError{
334-
phase: "YAML parsing",
266+
phase: validationPhaseReadmeParsing,
335267
errors: yamlParsingErrors,
336268
}
337269
}
@@ -356,11 +288,11 @@ func parseContributorFiles(readmeEntries []readme) (map[string]contributorProfil
356288
if _, found := profilesByUsername[companyName]; found {
357289
continue
358290
}
359-
yamlValidationErrors = append(yamlValidationErrors, fmt.Errorf("company %q does not exist in %q directory but is referenced by these profiles: [%s]", companyName, rootRegistryPath, strings.Join(group, ", ")))
291+
yamlValidationErrors = append(yamlValidationErrors, fmt.Errorf("%q: company %q does not exist but is referenced by these profiles: [%s]", rootRegistryPath, companyName, strings.Join(group, ", ")))
360292
}
361293
if len(yamlValidationErrors) != 0 {
362294
return nil, validationPhaseError{
363-
phase: "Raw YAML Validation",
295+
phase: validationPhaseReadmeParsing,
364296
errors: yamlValidationErrors,
365297
}
366298
}
@@ -397,15 +329,15 @@ func aggregateContributorReadmeFiles() ([]readme, error) {
397329

398330
if len(problems) != 0 {
399331
return nil, validationPhaseError{
400-
phase: "FileSystem reading",
332+
phase: validationPhaseFileLoad,
401333
errors: problems,
402334
}
403335
}
404336

405337
return allReadmeFiles, nil
406338
}
407339

408-
func validateRelativeUrls(
340+
func validateContributorRelativeUrls(
409341
contributors map[string]contributorProfile,
410342
) error {
411343
// This function only validates relative avatar URLs for now, but it can be
@@ -440,7 +372,7 @@ func validateRelativeUrls(
440372
return nil
441373
}
442374
return validationPhaseError{
443-
phase: "Relative URL validation",
375+
phase: validationPhaseAssetCrossReference,
444376
errors: problems,
445377
}
446378
}

cmd/readmevalidation/errors.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package main
2+
3+
import "fmt"
4+
5+
// validationPhaseError represents an error that occurred during a specific
6+
// phase of README validation. It should be used to collect ALL validation
7+
// errors that happened during a specific phase, rather than the first one
8+
// encountered.
9+
type validationPhaseError struct {
10+
phase validationPhase
11+
errors []error
12+
}
13+
14+
var _ error = validationPhaseError{}
15+
16+
func (vpe validationPhaseError) Error() string {
17+
msg := fmt.Sprintf("Error during %q phase of README validation:", vpe.phase.String())
18+
for _, e := range vpe.errors {
19+
msg += fmt.Sprintf("\n- %v", e)
20+
}
21+
msg += "\n"
22+
23+
return msg
24+
}
25+
26+
func addFilePathToError(filePath string, err error) error {
27+
return fmt.Errorf("%q: %v", filePath, err)
28+
}

cmd/readmevalidation/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func main() {
2626
log.Panic(err)
2727
}
2828

29-
err = validateRelativeUrls(contributors)
29+
err = validateContributorRelativeUrls(contributors)
3030
if err != nil {
3131
log.Panic(err)
3232
}

cmd/readmevalidation/readmeFiles.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"errors"
6+
"fmt"
7+
"strings"
8+
)
9+
10+
const rootRegistryPath = "./registry"
11+
12+
var supportedAvatarFileFormats = []string{".png", ".jpeg", ".jpg", ".gif", ".svg"}
13+
14+
// readme represents a single README file within the repo (usually within the
15+
// top-level "/registry" directory).
16+
type readme struct {
17+
filePath string
18+
rawText string
19+
}
20+
21+
// separateFrontmatter attempts to separate a README file's frontmatter content
22+
// from the main README body, returning both values in that order. It does not
23+
// validate whether the structure of the frontmatter is valid (i.e., that it's
24+
// structured as YAML).
25+
func separateFrontmatter(readmeText string) (string, string, error) {
26+
if readmeText == "" {
27+
return "", "", errors.New("README is empty")
28+
}
29+
30+
const fence = "---"
31+
fm := ""
32+
body := ""
33+
fenceCount := 0
34+
lineScanner := bufio.NewScanner(
35+
strings.NewReader(strings.TrimSpace(readmeText)),
36+
)
37+
for lineScanner.Scan() {
38+
nextLine := lineScanner.Text()
39+
if fenceCount < 2 && nextLine == fence {
40+
fenceCount++
41+
continue
42+
}
43+
// Break early if the very first line wasn't a fence, because then we
44+
// know for certain that the README has problems
45+
if fenceCount == 0 {
46+
break
47+
}
48+
49+
// It should be safe to trim each line of the frontmatter on a per-line
50+
// basis, because there shouldn't be any extra meaning attached to the
51+
// indentation. The same does NOT apply to the README; best we can do is
52+
// gather all the lines, and then trim around it
53+
if inReadmeBody := fenceCount >= 2; inReadmeBody {
54+
body += nextLine + "\n"
55+
} else {
56+
fm += strings.TrimSpace(nextLine) + "\n"
57+
}
58+
}
59+
if fenceCount < 2 {
60+
return "", "", errors.New("README does not have two sets of frontmatter fences")
61+
}
62+
if fm == "" {
63+
return "", "", errors.New("readme has frontmatter fences but no frontmatter content")
64+
}
65+
66+
return fm, strings.TrimSpace(body), nil
67+
}
68+
69+
// validationPhase represents a specific phase during README validation. It is
70+
// expected that each phase is discrete, and errors during one will prevent a
71+
// future phase from starting.
72+
type validationPhase int
73+
74+
const (
75+
// validationPhaseFileStructureValidation indicates when the entire Registry
76+
// directory is being verified for having all files be placed in the file
77+
// system as expected.
78+
validationPhaseFileStructureValidation validationPhase = iota
79+
80+
// validationPhaseFileLoad indicates when README files are being read from
81+
// the file system
82+
validationPhaseFileLoad
83+
84+
// validationPhaseReadmeParsing indicates when a README's frontmatter is
85+
// being parsed as YAML. This phase does not include YAML validation.
86+
validationPhaseReadmeParsing
87+
88+
// validationPhaseReadmeValidation indicates when a README's frontmatter is
89+
// being validated as proper YAML with expected keys.
90+
validationPhaseReadmeValidation
91+
92+
// validationPhaseAssetCrossReference indicates when a README's frontmatter
93+
// is having all its relative URLs be validated for whether they point to
94+
// valid resources.
95+
validationPhaseAssetCrossReference
96+
)
97+
98+
func (p validationPhase) String() string {
99+
switch p {
100+
case validationPhaseFileStructureValidation:
101+
return "File structure validation"
102+
case validationPhaseFileLoad:
103+
return "Filesystem reading"
104+
case validationPhaseReadmeParsing:
105+
return "README parsing"
106+
case validationPhaseReadmeValidation:
107+
return "README validation"
108+
case validationPhaseAssetCrossReference:
109+
return "Cross-referencing relative asset URLs"
110+
default:
111+
return fmt.Sprintf("Unknown validation phase: %d", p)
112+
}
113+
}

0 commit comments

Comments
 (0)