package main import ( "bytes" "fmt" "go/format" "os" "path" "path/filepath" "sort" "strings" ) // Generate renders the discovery aggregator for the module directory dir. It // blank-imports every sub-directory that holds Go source, in sorted order, so // the aggregator is deterministic (SPEC-008 AC-2) and the DIRECTORY is the // source of truth (D-028). importBase is the Go import path of dir. func Generate(dir, importBase string) ([]byte, error) { names, err := moduleDirs(dir) if err != nil { return nil, err } var buf bytes.Buffer buf.WriteString("// Code generated by go generate; DO NOT EDIT.\n") buf.WriteString("//\n") buf.WriteString("// Produced by `go generate ./internal/modules` (internal/modules/generate.go).\n") buf.WriteString("// Every sub-directory that holds Go source is blank-imported here so its\n") buf.WriteString("// init() self-registration runs at startup (D-028): THE DIRECTORY IS THE\n") buf.WriteString("// SOURCE OF TRUTH for the module set. Add a directory and re-run go generate.\n") buf.WriteString("package modules\n\n") if len(names) == 0 { buf.WriteString("// No module directories were found.\n") } else { buf.WriteString("import (\n") for _, n := range names { fmt.Fprintf(&buf, "\t_ %q\n", path.Join(importBase, n)) } buf.WriteString(")\n") } src, err := format.Source(buf.Bytes()) if err != nil { return nil, fmt.Errorf("gendiscovery: format output: %w", err) } return src, nil } // moduleDirs lists the module directories under dir, sorted. func moduleDirs(dir string) ([]string, error) { entries, err := os.ReadDir(dir) if err != nil { return nil, err } var names []string for _, e := range entries { if !e.IsDir() || skipDir(e.Name()) { continue } ok, err := hasGoSource(filepath.Join(dir, e.Name())) if err != nil { return nil, err } if ok { names = append(names, e.Name()) } } sort.Strings(names) return names, nil } // skipDir reports whether a directory is tooling, not a module. func skipDir(name string) bool { switch name { case "gen", "testdata": return true } return strings.HasPrefix(name, ".") || strings.HasPrefix(name, "_") } // hasGoSource reports whether dir holds at least one non-test Go file. func hasGoSource(dir string) (bool, error) { entries, err := os.ReadDir(dir) if err != nil { return false, err } for _, e := range entries { if e.IsDir() { continue } n := e.Name() if strings.HasSuffix(n, ".go") && !strings.HasSuffix(n, "_test.go") { return true, nil } } return false, nil }