// Command gendiscovery writes the module discovery aggregator // (internal/modules/imports_gen.go) from the directories under // internal/modules (D-028). It is invoked by `go generate ./internal/modules`. // // Usage: gendiscovery [dir] (dir defaults to ".") package main import ( "fmt" "os" "path" "path/filepath" "strings" ) func main() { dir := "." if len(os.Args) > 1 { dir = os.Args[1] } if err := run(dir); err != nil { fmt.Fprintln(os.Stderr, "gendiscovery:", err) os.Exit(1) } } func run(dir string) error { abs, err := filepath.Abs(dir) if err != nil { return err } root, modulePath, err := findModule(abs) if err != nil { return err } rel, err := filepath.Rel(root, abs) if err != nil { return err } importBase := path.Join(modulePath, filepath.ToSlash(rel)) src, err := Generate(dir, importBase) if err != nil { return err } out := filepath.Join(dir, "imports_gen.go") if err := os.WriteFile(out, src, 0o644); err != nil { return err } fmt.Fprintln(os.Stderr, "gendiscovery: wrote", out) return nil } // findModule walks up from dir to the nearest go.mod and returns its directory // and module path. func findModule(dir string) (root, modulePath string, err error) { cur := dir for { candidate := filepath.Join(cur, "go.mod") if data, readErr := os.ReadFile(candidate); readErr == nil { if mp := parseModulePath(string(data)); mp != "" { return cur, mp, nil } return "", "", fmt.Errorf("go.mod at %s declares no module path", candidate) } parent := filepath.Dir(cur) if parent == cur { return "", "", fmt.Errorf("no go.mod found above %s", dir) } cur = parent } } // parseModulePath extracts the `module ` line from a go.mod. func parseModulePath(goMod string) string { for _, line := range strings.Split(goMod, "\n") { line = strings.TrimSpace(line) if strings.HasPrefix(line, "module ") { return strings.TrimSpace(strings.TrimPrefix(line, "module ")) } } return "" }