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 }