git-project-manager/cmd/util_fzf.go

188 lines
5.4 KiB
Go
Raw Normal View History

2023-12-08 21:52:26 +00:00
package cmd
import (
"context"
fzf "github.com/ktr0731/go-fuzzyfinder"
2024-01-15 21:02:15 +00:00
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/cache"
2024-01-15 20:39:35 +00:00
"gitlab.sweetwater.com/it/devops/tools/gitlab-project-manager/internal/remotes/projects"
"golang.org/x/exp/slices"
2023-12-08 21:52:26 +00:00
)
type fzfProjectOpts struct {
Ctx context.Context
Search string
MustHaveAlias bool
Gitlabs []string
}
2023-12-10 16:15:52 +00:00
// This will try to find a project by alias if a search term
// is given, otherwise will fuzzy find by project
2024-01-15 20:39:35 +00:00
func fzfFindProject(opts *fzfProjectOpts) *projects.Project {
var project *projects.Project
2023-12-08 21:52:26 +00:00
if opts.Search != "" {
project = fzfSearchProjectAliases(opts)
2023-12-08 21:52:26 +00:00
} else {
var err error
project, err = fzfProject(opts)
2023-12-08 21:52:26 +00:00
if project == nil || err != nil {
return nil
}
}
return project
}
// If . is given as a project, will open project from the
// current working directory. Otherwise, will attempt to fuzzy-find
// a project given a search term if provided
2024-01-15 20:39:35 +00:00
func fzfCwdOrSearchProjectAliases(opts *fzfProjectOpts) *projects.Project {
var project *projects.Project
if opts.Search == "." {
2024-01-15 21:02:15 +00:00
project, _ = projectCache.GetProjectFromCwd()
} else {
project = fzfSearchProjectAliases(opts)
}
return project
}
2023-12-10 16:15:52 +00:00
// This will fuzzy search only aliases, preferring an exact
// match if one is given
2024-01-15 20:39:35 +00:00
func fzfSearchProjectAliases(opts *fzfProjectOpts) *projects.Project {
var project *projects.Project
2024-01-15 21:02:15 +00:00
var alias *cache.ProjectAlias
if alias = projectCache.GetAliasByName(opts.Search, opts.Gitlabs...); alias != nil {
project = projectCache.GetProjectByAlias(alias)
2023-12-08 21:52:26 +00:00
plog.Info("Perfect alias match... flawless")
} else {
// Get fuzzy if we don't have an exact match
2024-01-15 21:02:15 +00:00
aliases := projectCache.FuzzyFindAlias(opts.Search)
2023-12-08 21:52:26 +00:00
if len(aliases) > 1 {
2023-12-10 16:15:52 +00:00
// If multiple aliases were found, switch over to project
// by alias mode with merging
// alias = fzfAliasFromAliases(rootCmd.Context(), aliases)
project, _ = fzfProjectFromAliases(opts, aliases)
2023-12-08 21:52:26 +00:00
} else if len(aliases) == 1 {
alias = aliases[0]
2024-01-15 21:02:15 +00:00
project = projectCache.GetProjectByAlias(alias)
2023-12-08 21:52:26 +00:00
}
}
return project
}
2023-12-10 16:15:52 +00:00
// 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
2024-01-15 21:02:15 +00:00
func fzfAliasFromAliases(opts *fzfProjectOpts, aliases []*cache.ProjectAlias) *cache.ProjectAlias {
var alias *cache.ProjectAlias
2023-12-08 21:52:26 +00:00
i, err := fzf.Find(
aliases,
func(i int) string {
2024-01-15 21:02:15 +00:00
return aliases[i].Alias + " -> " + projectCache.GetProjectByAlias(aliases[i]).PathWithNamespace
2023-12-08 21:52:26 +00:00
},
fzf.WithContext(opts.Ctx),
2023-12-08 21:52:26 +00:00
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
}
2023-12-10 16:15:52 +00:00
// Given a list of aliases, merge them together and use the resulting
// list of projects to return a project
2024-01-15 21:02:15 +00:00
func fzfProjectFromAliases(opts *fzfProjectOpts, aliases []*cache.ProjectAlias) (
2024-01-15 20:39:35 +00:00
*projects.Project, error) {
2023-12-10 16:15:52 +00:00
mergedProjects := projectsFromAliases(aliases)
if len(mergedProjects) == 1 {
return mergedProjects[0], nil
}
return fzfProjectFromProjects(opts, mergedProjects)
2023-12-10 16:15:52 +00:00
}
2024-01-15 21:02:15 +00:00
func projectsFromAliases(aliases []*cache.ProjectAlias) []*projects.Project {
2024-01-15 20:39:35 +00:00
projects := make([]*projects.Project, 0)
2023-12-10 16:15:52 +00:00
ALIASES:
for _, a := range aliases {
for _, p := range projects {
// Already have it
if a.ProjectID == p.ID && a.Remote == p.Remote {
2023-12-10 16:15:52 +00:00
continue ALIASES
}
}
2024-01-15 21:02:15 +00:00
projects = append(projects, projectCache.GetProjectByAlias(a))
2023-12-10 16:15:52 +00:00
}
return projects
}
// If opts.MustHaveAlias, will only allow selection of projects
2023-12-09 04:13:17 +00:00
// that have at least one alias defined
2024-01-15 20:39:35 +00:00
func fzfProject(opts *fzfProjectOpts) (*projects.Project, error) {
var searchableProjects []*projects.Project
if opts.MustHaveAlias {
2024-01-15 21:02:15 +00:00
searchableProjects = projectCache.GetProjectsWithAliases()
2023-12-09 04:13:17 +00:00
} else {
2024-01-15 21:02:15 +00:00
searchableProjects = projectCache.Projects
2023-12-09 04:13:17 +00:00
}
// Filter out unwanted gitlabs if provided
searchableProjects = filterProjectsWithGitlabs(searchableProjects, opts.Gitlabs...)
return fzfProjectFromProjects(opts, searchableProjects)
2023-12-10 16:15:52 +00:00
}
2023-12-09 04:13:17 +00:00
2023-12-10 16:15:52 +00:00
// Takes a list of projects and performs a fuzzyfind
2024-01-15 20:39:35 +00:00
func fzfProjectFromProjects(opts *fzfProjectOpts, projects []*projects.Project) (
*projects.Project, error) {
2023-12-10 16:15:52 +00:00
i, err := fzf.Find(projects,
2023-12-09 04:13:17 +00:00
func(i int) string {
2023-12-10 16:15:52 +00:00
// Display the project along with its aliases
2024-01-15 21:02:15 +00:00
return projectCache.GetProjectStringWithAliases(projects[i])
2023-12-09 04:13:17 +00:00
},
fzf.WithPreviewWindow(
func(i, width, height int) string {
2024-01-15 21:02:15 +00:00
return projectCache.ProjectString(projects[i])
2023-12-09 04:13:17 +00:00
},
),
fzf.WithContext(opts.Ctx),
2023-12-08 21:52:26 +00:00
fzf.WithHeader("Fuzzy find yourself a project"),
)
if err != nil || i < 0 {
return nil, err
}
2023-12-10 16:15:52 +00:00
return projects[i], nil
2023-12-08 21:52:26 +00:00
}
func fzfPreviewWindow(i, w, h int) string {
2024-01-15 21:02:15 +00:00
p := projectCache.Projects[i]
return projectCache.ProjectString(p)
2023-12-08 21:52:26 +00:00
}
2024-01-15 20:39:35 +00:00
func filterProjectsWithGitlabs(gitProjects []*projects.Project, gitlabs ...string) []*projects.Project {
filteredProjects := make([]*projects.Project, 0, len(gitProjects))
if len(gitlabs) > 0 {
2024-01-15 20:39:35 +00:00
for _, p := range gitProjects {
if slices.Contains(gitlabs, p.Remote) {
filteredProjects = append(filteredProjects, p)
}
}
} else {
2024-01-15 20:39:35 +00:00
filteredProjects = gitProjects
}
return filteredProjects
}
// Nearly useless function that simply returns either an
// empty string, or a string from the first arg if one is provided
func searchStringFromArgs(args []string) string {
var term string
if len(args) > 0 {
term = args[0]
}
return term
}