Package subcommand code

This commit is contained in:
2024-12-30 15:50:31 -05:00
parent 96378d047e
commit b9d7d5a4f2
51 changed files with 357 additions and 295 deletions

66
cmd/project/project.go Normal file
View File

@ -0,0 +1,66 @@
package project
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/alias"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/cache"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
)
var ProjectCmd = &cobra.Command{
Use: "project [fuzzy alias search]",
Short: "Use a Git project",
Aliases: []string{"proj", "projects", "p"},
Args: cobra.MaximumNArgs(1),
ArgAliases: []string{"alias"},
Long: util.ProjCmdLong,
PersistentPreRun: projectCmdPreRun,
PersistentPostRun: util.PostProjectCmd,
// Run: projectGoCmdRun,
}
var utils *util.Utils
func projectCmdPreRun(cmd *cobra.Command, args []string) {
utils = util.MustFromCtx(cmd.Context())
util.InitProjects(cmd, args)
}
func getProject(cmd *cobra.Command, args []string) *projects.Project {
remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(),
Search: utils.SearchStringFromArgs(args),
Remotes: remotes,
}
project := utils.FzfFindProject(fzfOpts)
if project == nil {
utils.Logger().Fatal("Failed to find a project, nothing to do")
} else {
utils.Logger().Debug("Houston, we have a project", utils.Logger().Args(
"project", project.String(),
"aliases", cache.ProjectAliasesString(
utils.Cache().GetProjectAliases(project)),
))
}
if len(utils.Cache().GetProjectAliases(project)) == 0 {
utils.Logger().Info("New project, set aliases or press enter for default")
alias.AddNewAliases(cmd, project.ID)
}
return project
}
func init() {
ProjectCmd.AddCommand(projectAddCmd)
ProjectCmd.AddCommand(projectGoCmd)
ProjectCmd.AddCommand(projectListCmd)
ProjectCmd.AddCommand(projectOpenCmd)
ProjectCmd.AddCommand(projectRunCmd)
ProjectCmd.AddCommand(projectShowCmd)
}

View File

@ -0,0 +1,19 @@
package project
import (
"github.com/spf13/cobra"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
)
var projectAddCmd = &cobra.Command{
Use: "add",
Short: "Add a new Git project",
Aliases: []string{"a", "alias"},
Long: util.ProjAddCmdLong,
Run: projectAddCmdRun,
}
func projectAddCmdRun(cmd *cobra.Command, args []string) {
getProject(cmd, args)
}

53
cmd/project/project_go.go Normal file
View File

@ -0,0 +1,53 @@
package project
import (
"fmt"
"os"
"os/exec"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
)
var projectGoCmd = &cobra.Command{
Use: "go [fuzzy alias search]",
Short: "Go to a Git project",
Aliases: []string{"goto", "cd"},
Args: cobra.MaximumNArgs(1),
ArgAliases: []string{"project"},
ValidArgsFunction: util.ValidAliasesFunc,
Long: util.ProjGoCmdLong,
Run: projectGoCmdRun,
}
func projectGoCmdRun(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(),
Search: utils.SearchStringFromArgs(args),
MustHaveAlias: true,
Remotes: remotes,
}
project := utils.FzfSearchProjectAliases(fzfOpts)
if project == nil {
utils.Logger().Fatal("No project selected, nowhere to go")
}
utils.Cache().GoTo(project)
project.SetRepo(utils.Cache().OpenProject(cmd.Context(), project))
utils.Logger().Debug("Project ready", utils.Logger().Args(
"path", utils.Cache().GetProjectPath(project),
"project", project,
))
fmt.Fprintln(os.Stderr, project.GetGitInfo())
// This should be read by any source command, for instance
// `cd "$(git-project-manager projects cd somealias)"`
fmt.Println(utils.Cache().GetProjectPath(project))
exec.Command("cd", utils.Cache().GetProjectPath(project)).Run()
}

View File

@ -0,0 +1,28 @@
package project
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
)
var projectListCmd = &cobra.Command{
Use: "list",
Short: "List Git Projects",
Aliases: []string{"ls", "l"},
Long: util.ProjListCmdLong,
Run: projectListCmdRun,
}
func projectListCmdRun(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(util.FlagRemote)
fmt.Println(utils.Cache().DumpString(viper.GetBool(util.ViperProjectListAll), utils.SearchStringFromArgs(args), remotes...))
}
func init() {
projectListCmd.PersistentFlags().Bool(util.FlagAll, false, "List all, not just cloned locally")
viper.BindPFlag(util.ViperProjectListAll, projectListCmd.Flag(util.FlagAll))
}

192
cmd/project/project_open.go Normal file
View File

@ -0,0 +1,192 @@
package project
import (
"context"
"errors"
"os"
"os/exec"
"path/filepath"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
)
var projectOpenCmd = &cobra.Command{
Use: "open [fuzzy alias search]",
Short: "Open project in your IDE",
Aliases: []string{"goto", "cd"},
Args: cobra.OnlyValidArgs,
ValidArgsFunction: util.ValidAliasesFunc,
Long: util.ProjOpenCmdLong,
Run: projectOpenCmdRun,
}
var knownEditors = []string{
"vim",
"emacs",
"code",
"codium",
"/Applications/GoLand.app/Contents/MacOS/goland",
}
var entrypointFiles = []string{
"README.md",
"README",
"main.go",
"main.py",
"app.py",
"index.php",
"server.php",
"index.js",
"app.js",
"server.js",
"app.ts",
"main.c",
"main.cpp",
}
func projectOpenCmdRun(cmd *cobra.Command, args []string) {
// Find an editor
editor := findEditor()
if editor == "" {
utils.Logger().Fatal("No usable editor found")
}
remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(),
Search: utils.SearchStringFromArgs(args),
Remotes: remotes,
}
project := utils.FzfCwdOrSearchProjectAliases(fzfOpts)
if project == nil {
utils.Logger().Fatal("No project to open, nothing to do")
}
// Check the project
path := utils.Cache().GetProjectPath(project)
if _, err := os.Stat(path); err != nil {
utils.Logger().Fatal("Unable to open project", utils.Logger().Args("error", err))
}
// Open the project with the editor
file := getEntrypointFile(path)
openEditor(cmd.Context(), editor, path+"/"+file)
}
func openEditor(ctx context.Context, editor string, path string) {
// Compile arguments
args := make([]string, 0, 1)
if utils.Config().Editor.OpenFlags != "" {
args = append(args, utils.Config().Editor.OpenFlags)
}
args = append(args, path)
// Launch editor
cmd := exec.CommandContext(ctx, editor, args...)
cmd.Dir = filepath.Dir(path)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
utils.Logger().Error("Failed to open project", utils.Logger().Args(
"error", err.Error(),
"command", cmd.String(),
))
}
cmd.Wait()
}
func getEntrypointFile(projectPath string) string {
if err := os.Chdir(projectPath); err != nil {
return ""
} else if utils.Config().Editor.OpenDirectory {
return "."
}
// Search list of well-known main files
for _, f := range entrypointFiles {
if _, err := os.Stat(f); err == nil {
return f
}
}
return ""
}
func findEditor() string {
var editor string
var err error
// First try configured editor
if utils.Config().Editor.Binary != "" {
editor, err = getEditor(utils.Config().Editor.Binary)
if err != nil || editor == "" {
utils.Logger().Error("Configured editor is not usable, finding others", utils.Logger().Args(
"error", err,
))
}
}
// Then try to find a known editor
if editor == "" || err != nil {
utils.Config().Editor.OpenFlags = ""
for _, e := range knownEditors {
path, err := getEditor(e)
if err == nil && path != "" {
editor = path
break
}
}
}
utils.Logger().Debug("Editor found for open", utils.Logger().Args(
"editor", editor,
"command", editor+" "+utils.Config().Editor.OpenFlags,
))
return editor
}
func getEditor(editor string) (string, error) {
path, err := getEditorPath(editor)
if path != "" && err == nil {
if !isEditorExecutable(path) {
err = errors.New("editor is not executable")
}
}
return path, err
}
func getEditorPath(editor string) (string, error) {
// Check path first
if editor[0] != '/' {
editor, _ = exec.LookPath(editor)
}
return util.ResolvePath(editor)
}
func isEditorExecutable(editor string) bool {
var canExec bool
stat, err := os.Stat(editor)
if err == nil && (stat.Mode()&0o444 != 0 && stat.Mode()&0o111 != 0) {
canExec = true
}
return canExec
}
func init() {
projectOpenCmd.PersistentFlags().String("displayName", "", "Set display name of editor (meant for config file)")
projectOpenCmd.PersistentFlags().String("binary", "", "Path to editor binary")
projectOpenCmd.PersistentFlags().String("openFlags", "", "Optional flags when opening project (e.g. --reuse-window)")
viper.BindPFlag("editor.displayName", projectOpenCmd.Flag("displayName"))
viper.BindPFlag("editor.binary", projectOpenCmd.Flag("binary"))
viper.BindPFlag("editor.openFlags", projectOpenCmd.Flag("openFlags"))
}

View File

@ -0,0 +1,56 @@
package project
import (
"fmt"
"os"
"os/exec"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
)
var projectRunCmd = &cobra.Command{
Use: "run",
Short: "Run the project (e.g. go run .)",
Aliases: []string{"exec", "r"},
Long: util.ProjRunCmdLong,
Run: projectRunCmdRun,
}
func projectRunCmdRun(cmd *cobra.Command, args []string) {
remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(),
Search: utils.SearchStringFromArgs(args),
Remotes: remotes,
}
project := utils.FzfCwdOrSearchProjectAliases(fzfOpts)
if project == nil {
utils.Logger().Fatal("No project selected, nothing to open")
}
lang := project.GetLanguage()
if lang == nil {
utils.Logger().Fatal("Git remote isn't sure what language this project is... can't run.")
}
utils.Logger().Debug(fmt.Sprintf("Project is written in %s, %.2f%% coverage", lang.Name, lang.Percentage))
switch lang.Name {
case "Go":
cmdArgs := []string{"run", "."} // Run from cwd
cmdArgs = append(cmdArgs, args[1:]...) // Support flags from shell command
cmd := exec.CommandContext(cmd.Context(), "go", cmdArgs...) // Honor parent context
cmd.Env = os.Environ()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
}
}
func init() {
ProjectCmd.AddCommand(projectRunCmd)
}

View File

@ -0,0 +1,85 @@
package project
import (
"fmt"
"os"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/cmd/util"
"gitea.libretechconsulting.com/rmcguire/git-project-manager/internal/remotes/projects"
)
var projectShowCmd = &cobra.Command{
Use: "show [fuzzy alias search]",
Short: "Show detail for a Git project",
Aliases: []string{"cat", "s"},
Args: cobra.ArbitraryArgs,
Long: util.ProjShowCmdLong,
Run: projectShowCmdRun,
}
func projectShowCmdRun(cmd *cobra.Command, args []string) {
var project *projects.Project
var inCwd bool
remotes := viper.GetStringSlice(util.FlagRemote)
fzfOpts := &util.FzfProjectOpts{
Ctx: cmd.Context(),
Search: utils.SearchStringFromArgs(args),
Remotes: remotes,
}
// Try to find project from current directory
if viper.GetBool(util.ViperProjectShowCurrent) {
var err error
project, err = utils.Cache().GetProjectFromCwd()
if err != nil {
// Not an error because we're still going to try to find a project
utils.Logger().Warn("Failed to get project from current directory", utils.Logger().Args(
"error", err,
))
} else if project == nil {
utils.Logger().Warn("Failed to use --current flag, project not found in current path")
} else {
inCwd = true
}
}
// Otherwise find from the given search string
if project == nil {
project = utils.FzfFindProject(fzfOpts)
}
// Do a full fuzzy find if all else fails
if project == nil {
var err error
project, err = utils.FzfProject(fzfOpts)
if err != nil || project == nil {
utils.Logger().Fatal("Failed to find project, nothing to show", utils.Logger().Args(
"error", err,
))
}
}
fmt.Println()
pterm.DefaultBox.
WithLeftPadding(5).WithRightPadding(5).
WithBoxStyle(&pterm.Style{pterm.FgLightBlue}).
WithTitle(pterm.Bold.Sprint(pterm.LightGreen("Project Information"))).
Println(utils.Cache().ProjectString(project))
fmt.Println()
if inCwd {
project.SetRepo(utils.Cache().OpenProject(cmd.Context(), project))
fmt.Fprintln(os.Stderr, project.GetGitInfo()+"\n")
}
}
func init() {
ProjectCmd.AddCommand(projectShowCmd)
projectShowCmd.PersistentFlags().Bool(util.FlagCurrent, false, "Use project in CWD rather than fuzzy find")
viper.BindPFlag(util.ViperProjectShowCurrent, projectShowCmd.Flag(util.FlagCurrent))
}