package cmd import ( "context" fzf "github.com/ktr0731/go-fuzzyfinder" "gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/gitlab" "gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/projects" ) // This will try to find a project by alias if a search term // is given, otherwise will fuzzy find by project func fzfFindProject(searchString string) *gitlab.Project { var project *gitlab.Project if searchString != "" { project = fzfSearchProjectAliases(searchString) } else { var err error project, err = fzfProject(rootCmd.Context()) if project == nil || err != nil { return nil } } return project } // This will fuzzy search only aliases, preferring an exact // match if one is given func fzfSearchProjectAliases(searchString string) *gitlab.Project { var project *gitlab.Project var alias *projects.ProjectAlias if alias = cache.GetAliasByName(searchString); alias != nil { project = cache.GetProjectByAlias(alias) plog.Info("Perfect alias match... flawless") } else { // Get fuzzy if we don't have an exact match aliases := cache.FuzzyFindAlias(searchString) if len(aliases) > 1 { // If multiple aliases were found, switch over to project // by alias mode with merging // alias = fzfAliasFromAliases(rootCmd.Context(), aliases) project, _ = fzfProjectFromAliases(rootCmd.Context(), aliases) } else if len(aliases) == 1 { alias = aliases[0] project = cache.GetProjectByAlias(alias) } } return project } // Given a list of aliases, will fuzzy-find and return // a single one. Replaced by fzfProjectFromAliases in fzfSearchProjectAliases // as merging is preferred, but can be used if it's ever desirable to // return a single alias from all aliases func fzfAliasFromAliases(ctx context.Context, aliases []*projects.ProjectAlias) *projects.ProjectAlias { var alias *projects.ProjectAlias i, err := fzf.Find( aliases, func(i int) string { return aliases[i].Alias + " -> " + cache.GetProjectByAlias(aliases[i]).PathWithNamespace }, fzf.WithContext(ctx), fzf.WithHeader("Choose an Alias"), ) if err != nil { plog.Error("Failed to fzf alias slice", plog.Args("error", err)) } else { alias = aliases[i] } return alias } // Given a list of aliases, merge them together and use the resulting // list of projects to return a project func fzfProjectFromAliases(ctx context.Context, aliases []*projects.ProjectAlias) ( *gitlab.Project, error) { mergedProjects := projectsFromAliases(aliases) return fzfProjectFromProjects(ctx, mergedProjects) } func projectsFromAliases(aliases []*projects.ProjectAlias) []*gitlab.Project { projects := make([]*gitlab.Project, 0) ALIASES: for _, a := range aliases { for _, p := range projects { // Already have it if a.ProjectID == p.ID { continue ALIASES } } projects = append(projects, cache.GetProjectByAlias(a)) } return projects } // If a bool=true is provided, will only allow selection of projects // that have at least one alias defined func fzfProject(ctx context.Context, mustHaveAlias ...bool) (*gitlab.Project, error) { var searchableProjects []*gitlab.Project if len(mustHaveAlias) == 1 && mustHaveAlias[0] { searchableProjects = cache.GetProjectsWithAliases() } else { searchableProjects = cache.Projects } return fzfProjectFromProjects(ctx, searchableProjects) } // Takes a list of projects and performs a fuzzyfind func fzfProjectFromProjects(ctx context.Context, projects []*gitlab.Project) ( *gitlab.Project, error) { i, err := fzf.Find(projects, func(i int) string { // Display the project along with its aliases return cache.GetProjectStringWithAliases(projects[i]) }, fzf.WithPreviewWindow( func(i, width, height int) string { return cache.ProjectString(projects[i]) }, ), fzf.WithContext(ctx), fzf.WithHeader("Fuzzy find yourself a project"), ) if err != nil || i < 0 { return nil, err } return projects[i], nil } func fzfPreviewWindow(i, w, h int) string { p := cache.Projects[i] return cache.ProjectString(p) }