github.com/jimmyfrasche/autoreadme@v0.0.0-20240504231658-aacd7e11c8ba/repo.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "io/fs" 7 "os" 8 "path/filepath" 9 ) 10 11 var errDone = errors.New("done") 12 var errTop = errors.New("top of fs") 13 14 func up(done func(dir string) error) error { 15 top := false 16 p, err := os.Getwd() 17 if err != nil { 18 return err 19 } 20 p = filepath.Clean(p) 21 22 for { 23 // check our predicate for this level 24 if err := done(p); err != nil { 25 return err 26 } 27 28 // already at the top but found nothing 29 if top { 30 return errTop 31 } 32 33 // go up one level 34 // make a note if we're at the top 35 new := filepath.Dir(p) 36 top = new == p 37 p = new 38 } 39 } 40 41 func Roots() (repo, project string, err error) { 42 stat := func(dir, p string) (os.FileInfo, error) { 43 return os.Stat(filepath.Join(dir, p)) 44 } 45 err = up(func(dir string) error { 46 if repo == "" { 47 fi, err := stat(dir, ".git") 48 if err != nil && !errors.Is(err, fs.ErrNotExist) { 49 return err 50 } 51 if err == nil && fi.IsDir() { 52 repo = dir 53 } 54 } 55 56 if project == "" { 57 fi, err := stat(dir, "go.mod") 58 if err != nil && !errors.Is(err, fs.ErrNotExist) { 59 return err 60 } 61 if err == nil && fi.Mode().IsRegular() { 62 project = dir 63 } 64 } 65 66 // found both 67 if repo != "" && project != "" { 68 return errDone 69 } 70 return nil 71 }) 72 // up always returns an error 73 switch err { 74 case errDone: 75 return repo, project, nil 76 77 case errTop: 78 // no errors but missing go.mod and/or .git 79 if project == "" { 80 err = errors.New("could not find repository root") 81 } else { 82 err = errors.New("could not find project or repository root") 83 } 84 85 default: 86 // stat error while searching for go.mod and .git 87 if project == "" { 88 err = fmt.Errorf("could not find repository root: %w", err) 89 } else { 90 err = fmt.Errorf("could not find project or repository root: %w", err) 91 } 92 } 93 return "", "", err 94 }