2023-12-08 21:52:26 +00:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
|
|
|
|
fzf "github.com/ktr0731/go-fuzzyfinder"
|
2024-10-01 18:29:14 +00:00
|
|
|
"golang.org/x/exp/slices"
|
|
|
|
|
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"
|
2023-12-08 21:52:26 +00:00
|
|
|
)
|
|
|
|
|
2024-01-14 15:33:15 +00:00
|
|
|
type fzfProjectOpts struct {
|
|
|
|
Ctx context.Context
|
|
|
|
Search string
|
|
|
|
MustHaveAlias bool
|
2024-01-17 15:06:20 +00:00
|
|
|
Remotes []string
|
2024-01-14 15:33:15 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2024-01-14 15:33:15 +00:00
|
|
|
if opts.Search != "" {
|
|
|
|
project = fzfSearchProjectAliases(opts)
|
2023-12-08 21:52:26 +00:00
|
|
|
} else {
|
|
|
|
var err error
|
2024-01-14 15:33:15 +00:00
|
|
|
project, err = fzfProject(opts)
|
2023-12-08 21:52:26 +00:00
|
|
|
if project == nil || err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return project
|
|
|
|
}
|
|
|
|
|
2023-12-29 15:24:12 +00:00
|
|
|
// 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
|
2024-01-14 15:33:15 +00:00
|
|
|
if opts.Search == "." {
|
2024-01-15 21:02:15 +00:00
|
|
|
project, _ = projectCache.GetProjectFromCwd()
|
2023-12-29 15:24:12 +00:00
|
|
|
} else {
|
2024-01-14 15:33:15 +00:00
|
|
|
project = fzfSearchProjectAliases(opts)
|
2023-12-29 15:24:12 +00:00
|
|
|
}
|
|
|
|
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
|
2024-01-17 15:06:20 +00:00
|
|
|
if alias = projectCache.GetAliasByName(opts.Search, opts.Remotes...); alias != nil {
|
2024-01-15 21:02:15 +00:00
|
|
|
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)
|
2024-01-14 15:33:15 +00:00
|
|
|
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
|
|
|
},
|
2024-01-14 15:33:15 +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-10-01 18:29:14 +00:00
|
|
|
*projects.Project, error,
|
|
|
|
) {
|
2023-12-10 16:15:52 +00:00
|
|
|
mergedProjects := projectsFromAliases(aliases)
|
2023-12-10 16:50:47 +00:00
|
|
|
if len(mergedProjects) == 1 {
|
|
|
|
return mergedProjects[0], nil
|
|
|
|
}
|
2024-01-14 15:33:15 +00:00
|
|
|
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-10-01 18:29:14 +00:00
|
|
|
projects := make([]*projects.Project, 0, len(aliases))
|
2023-12-10 16:15:52 +00:00
|
|
|
|
|
|
|
for _, a := range aliases {
|
2024-10-01 18:29:14 +00:00
|
|
|
project := projectCache.GetProjectByAlias(a)
|
|
|
|
if project != nil && !slices.Contains(projects, project) {
|
|
|
|
projects = append(projects, project)
|
2023-12-10 16:15:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-01 18:29:14 +00:00
|
|
|
return slices.Clip(projects)
|
2023-12-10 16:15:52 +00:00
|
|
|
}
|
|
|
|
|
2024-01-14 15:33:15 +00:00
|
|
|
// 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
|
2024-01-14 15:33:15 +00:00
|
|
|
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
|
|
|
}
|
2024-01-17 15:06:20 +00:00
|
|
|
// Filter out unwanted remotes if provided
|
|
|
|
searchableProjects = filterProjectsWithRemotes(searchableProjects, opts.Remotes...)
|
2024-01-14 15:33:15 +00:00
|
|
|
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) (
|
2024-10-01 18:29:14 +00:00
|
|
|
*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
|
|
|
},
|
|
|
|
),
|
2024-01-14 15:33:15 +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
|
|
|
}
|
2023-12-29 15:24:12 +00:00
|
|
|
|
2024-01-17 15:06:20 +00:00
|
|
|
func filterProjectsWithRemotes(gitProjects []*projects.Project, remotes ...string) []*projects.Project {
|
2024-01-15 20:39:35 +00:00
|
|
|
filteredProjects := make([]*projects.Project, 0, len(gitProjects))
|
2024-01-17 15:06:20 +00:00
|
|
|
if len(remotes) > 0 {
|
2024-01-15 20:39:35 +00:00
|
|
|
for _, p := range gitProjects {
|
2024-01-17 15:06:20 +00:00
|
|
|
if slices.Contains(remotes, p.Remote) {
|
2024-01-14 15:33:15 +00:00
|
|
|
filteredProjects = append(filteredProjects, p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2024-01-15 20:39:35 +00:00
|
|
|
filteredProjects = gitProjects
|
2024-01-14 15:33:15 +00:00
|
|
|
}
|
|
|
|
return filteredProjects
|
|
|
|
}
|
|
|
|
|
2023-12-29 15:24:12 +00:00
|
|
|
// 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
|
|
|
|
}
|