github.com/benchkram/bob@v0.0.0-20240314204020-b7a57f2f9be9/bob/bobfile/project/project.go (about)

     1  package project
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/benchkram/bob/pkg/usererror"
    10  	"github.com/pkg/errors"
    11  )
    12  
    13  // RestrictedProjectNameChars collects the characters allowed to represent a project.
    14  const RestrictedProjectNameChars = `[a-zA-Z0-9/_.\-:]`
    15  
    16  // RestrictedProjectNamePattern is a regular expression to validate projectnames.
    17  var RestrictedProjectNamePattern = regexp.MustCompile(`^` + RestrictedProjectNameChars + `+$`)
    18  
    19  // ProjectNameDoubleSlashPattern matches a string containing a double slash (useful to check for URL schema)
    20  var ProjectNameDoubleSlashPattern = regexp.MustCompile(`//+`)
    21  
    22  var (
    23  	ErrProjectIsLocal  = fmt.Errorf("can't use Remote() with local project")
    24  	ErrProjectIsRemote = fmt.Errorf("can't use Local() with remote project")
    25  
    26  	ErrInvalidProjectName = fmt.Errorf("invalid project name")
    27  
    28  	ProjectNameFormatHint = "project name should be in the form 'project' or 'bob.build/user/project'"
    29  )
    30  
    31  type T string
    32  
    33  const (
    34  	Local  T = "local"
    35  	Remote T = "remote"
    36  )
    37  
    38  type Name string
    39  
    40  func (n *Name) Type() T {
    41  	t, _, _ := parse(*n)
    42  	return t
    43  }
    44  
    45  func (n *Name) Local() (string, error) {
    46  	t, l, _ := parse(*n)
    47  	switch t {
    48  	case Local:
    49  		return string(l), nil
    50  	case Remote:
    51  		return "", ErrProjectIsLocal
    52  	default:
    53  		return string(l), nil
    54  	}
    55  }
    56  
    57  func (n *Name) Remote() (*url.URL, error) {
    58  	t, _, url := parse(*n)
    59  	switch t {
    60  	case Local:
    61  		return nil, ErrProjectIsRemote
    62  	case Remote:
    63  		return url, nil
    64  	default:
    65  		return url, nil
    66  	}
    67  }
    68  
    69  // Parse a projectname and validate it against `RestrictedProjectNamePattern`
    70  func Parse(projectname string) (Name, error) {
    71  	if !RestrictedProjectNamePattern.MatchString(projectname) {
    72  		return "", usererror.Wrap(errors.WithMessage(ErrInvalidProjectName,
    73  			"project name should be in the form 'project' or 'bob.build/user/project'",
    74  		))
    75  	}
    76  
    77  	// test for double slash (do not allow prepended schema)
    78  	if ProjectNameDoubleSlashPattern.MatchString(projectname) {
    79  		return "", usererror.Wrap(errors.WithMessage(ErrInvalidProjectName, ProjectNameFormatHint))
    80  	}
    81  
    82  	return Name(projectname), nil
    83  }
    84  
    85  func parse(projectname Name) (T, Name, *url.URL) {
    86  	n := string(projectname)
    87  	if n == "" {
    88  		return Local, "", nil
    89  	}
    90  
    91  	segs := strings.Split(n, "/")
    92  	if len(segs) <= 1 {
    93  		return Local, projectname, nil
    94  	}
    95  
    96  	url, err := url.Parse("https://" + n)
    97  	if err != nil {
    98  		return Local, projectname, nil
    99  	}
   100  
   101  	// in case o a relative path expect it to be local
   102  	if url.Host == "" {
   103  		return Local, projectname, nil
   104  	}
   105  
   106  	url.Scheme = "https"
   107  
   108  	return Remote, "", url
   109  }