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  }