github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runbits/findproject/project.go (about) 1 package findproject 2 3 import ( 4 "sort" 5 "strings" 6 7 "github.com/ActiveState/cli/internal/errs" 8 "github.com/ActiveState/cli/internal/locale" 9 "github.com/ActiveState/cli/internal/logging" 10 "github.com/ActiveState/cli/internal/osutils" 11 "github.com/ActiveState/cli/internal/prompt" 12 "github.com/ActiveState/cli/pkg/project" 13 "github.com/ActiveState/cli/pkg/projectfile" 14 ) 15 16 // LocalProjectDoesNotExist is an error returned when a requested project is not checked out locally. 17 type LocalProjectDoesNotExist struct{ *locale.LocalizedError } 18 19 // IsLocalProjectDoesNotExistError checks if the error is a LocalProjectDoesNotExist. 20 func IsLocalProjectDoesNotExistError(err error) bool { 21 return errs.Matches(err, &LocalProjectDoesNotExist{}) 22 } 23 24 func FromInputByPriority(path string, ns *project.Namespaced, cfg projectfile.ConfigGetter, prompt prompt.Prompter) (*project.Project, error) { 25 // Priority #1 - PATH 26 if path != "" { 27 return FromPath(path, ns) 28 } 29 30 // Priority #2 - Namespace 31 if ns != nil && ns.IsValid() { 32 return FromNamespaceLocal(ns, cfg, prompt) 33 } 34 35 // Priority #3 - Env 36 pj, err := project.FromEnv() 37 if err != nil { 38 return nil, locale.WrapError(err, "err_project_fromenv") 39 } 40 41 return pj, nil 42 } 43 44 func FromPath(path string, ns *project.Namespaced) (*project.Project, error) { 45 pj, err := project.FromPath(path) 46 if err != nil { 47 return nil, &LocalProjectDoesNotExist{locale.WrapInputError(err, "err_project_frompath_notexist", "", path)} 48 } 49 50 if ns != nil && ns.IsValid() && ((ns.Owner != "" && pj.Namespace().Owner != ns.Owner) || pj.Namespace().Project != ns.Project) { 51 return nil, locale.WrapInputError(err, "err_project_namespace_missmatch", "", path, ns.String()) 52 } 53 return pj, nil 54 } 55 56 // FromNamespaceLocal returns a local project (if any) that matches the given namespace (or the 57 // project in the current working directory if namespace was not given). 58 // This is primarily used by `state use` in order to fetch a project to switch to if it already 59 // exists locally. The namespace may omit the owner. 60 func FromNamespaceLocal(ns *project.Namespaced, cfg projectfile.ConfigGetter, prompt prompt.Prompter) (*project.Project, error) { 61 if ns == nil || !ns.IsValid() { 62 root, err := osutils.Getwd() 63 if err != nil { 64 return nil, locale.WrapInputError(err, "Unable to determine current working directory. Please specify a project to use.") 65 } 66 return project.FromPath(root) 67 } 68 69 // Get the stale project mapping early as GetProjectMapping will clean stale projects 70 staleProjects := projectfile.GetStaleProjectMapping(cfg) 71 72 matchingProjects := make(map[string][]string) 73 matchingNamespaces := make([]string, 0) 74 for namespace, paths := range projectfile.GetProjectMapping(cfg) { 75 if len(paths) == 0 { 76 continue 77 } 78 namespaced, err := project.ParseNamespace(namespace) 79 if err != nil { 80 logging.Debug("Cannot parse namespace: %v") // should not happen since this is stored 81 continue 82 } 83 if !ns.AllowOmitOwner && strings.EqualFold(strings.ToLower(namespaced.String()), strings.ToLower(ns.String())) || 84 (ns.AllowOmitOwner && strings.EqualFold(strings.ToLower(namespaced.Project), strings.ToLower(ns.Project))) { 85 matchingProjects[namespace] = paths 86 matchingNamespaces = append(matchingNamespaces, namespace) 87 } 88 } 89 90 if len(matchingProjects) > 0 { 91 var err error 92 93 sort.Strings(matchingNamespaces) 94 namespace := matchingNamespaces[0] 95 if len(matchingProjects) > 1 { 96 namespace, err = prompt.Select( 97 "", 98 locale.Tl("project_select_namespace", "Multiple projects with that name were found. Please select one."), 99 matchingNamespaces, 100 &namespace) 101 if err != nil { 102 return nil, locale.WrapError(err, "err_project_select_namespace", "Error selecting project") 103 } 104 } 105 106 paths, exists := matchingProjects[namespace] 107 if !exists { 108 return nil, errs.New("Selected project not mapped to a namespace") // programmer error 109 } 110 111 sort.Strings(paths) 112 path := paths[0] 113 if len(paths) > 1 { 114 path, err = prompt.Select( 115 "", 116 locale.Tl("project_select_path", "Multiple project paths for the selected project were found. Please select one."), 117 paths, 118 &path) 119 if err != nil { 120 return nil, locale.WrapError(err, "err_project_select_path", "Error selecting project path") 121 } 122 } 123 124 return project.FromPath(path) 125 } 126 127 for namespace, paths := range staleProjects { 128 namespaced, err := project.ParseNamespace(namespace) 129 if err != nil { 130 logging.Debug("Cannot parse namespace: %v") // should not happen since this is stored 131 continue 132 } 133 134 if !ns.AllowOmitOwner && strings.EqualFold(strings.ToLower(namespaced.String()), strings.ToLower(ns.String())) || 135 (ns.AllowOmitOwner && strings.EqualFold(strings.ToLower(namespaced.Project), strings.ToLower(ns.Project))) && len(paths) > 0 { 136 return nil, &LocalProjectDoesNotExist{ 137 locale.NewInputError("err_findproject_notfound", "", ns.Project, paths[0]), 138 } 139 } 140 } 141 142 return nil, &LocalProjectDoesNotExist{ 143 locale.NewInputError("err_local_project_not_checked_out", "", ns.Project), 144 } 145 }