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  }