Skip to content

Commit

Permalink
#11 Implement setup command. Initial implementation working on mac.
Browse files Browse the repository at this point in the history
  • Loading branch information
David May committed Apr 8, 2024
1 parent db409e0 commit e2adaf3
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 93 deletions.
14 changes: 13 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"program": "${workspaceFolder}",
"cwd": "/Users/davidmay/code/testarea",
"args": [
"--ppid", "0",
"create",
"/~https://github.com/davfive/gitspaces.git",
"-n",
Expand All @@ -27,6 +28,7 @@
"program": "${workspaceFolder}",
"cwd": "/Users/davidmay/code/testarea",
"args": [
"--ppid", "0",
"create",
"git@github.com:davfive/gitspaces.git",
"-n",
Expand All @@ -41,7 +43,7 @@
"mode": "auto",
"program": "${workspaceFolder}",
"cwd": "${workspaceFolder}",
"args": ["switch"],
"args": ["--ppid", "0", "switch"],
"console": "integratedTerminal"
},
{
Expand Down Expand Up @@ -74,6 +76,16 @@
"args": ["-h"],
"console": "integratedTerminal"
},
{
"name": "gitspaces --ppid=0 <empty>",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}",
"cwd": "${workspaceFolder}",
"args": ["--ppid", "0"],
"console": "integratedTerminal"
},
{
"name": "gitspaces <empty>",
"type": "go",
Expand Down
20 changes: 11 additions & 9 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,7 @@ func Execute() {
rootCmd.PersistentFlags().BoolP("debug", "d", false, "Add additional debugging information")
rootCmd.PersistentFlags().MarkHidden("debug")

if utils.PathExists(filepath.Join(utils.GetUserHomeDir(), ".gitspaces")) {
setDefaultCommandIfNonePresent("switch")
} else {
// TODO: setDefaultCommandIfNonePresent("setup")
setDefaultCommandIfNonePresent("setup")
}

setDefaultCommandIfNonePresent()
if cmd, err := rootCmd.ExecuteC(); err != nil {
skipErrors := []string{"user aborted"}
if !slices.Contains(skipErrors, err.Error()) {
Expand Down Expand Up @@ -76,7 +70,7 @@ func flagsContain(flags []string, contains ...string) bool {
return false
}

func setDefaultCommandIfNonePresent(defaultCommand string) {
func setDefaultCommandIfNonePresent() {
// Taken from cobra source code in command.go::ExecuteC()
var cmd *cobra.Command
var err error
Expand All @@ -89,7 +83,15 @@ func setDefaultCommandIfNonePresent(defaultCommand string) {

if err != nil || cmd.Use == rootCmd.Use {
if !flagsContain(flags, "-v", "-h", "--version", "--help") {
rootCmd.SetArgs(append(os.Args[1:], defaultCommand))
firstRun := !utils.PathExists(filepath.Join(utils.GetUserHomeDir(), ".gitspaces"))

if firstRun || !flagsContain(flags, "--ppid") {
// Not setup or not setup correctly (not using wrapper)
rootCmd.SetArgs([]string{"setup"})
} else {
// Run w/o commands, default to switching projects/spaces
rootCmd.SetArgs(append(os.Args[1:], "switch"))
}
}
}
}
25 changes: 22 additions & 3 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package config

import (
"fmt"
"os"

"github.com/davfive/gitspaces/v2/internal/console"
"github.com/spf13/cobra"
)

var Debug bool = false

var User *userStruct

const (
Expand All @@ -16,10 +22,23 @@ const (
func Init(cmd *cobra.Command) (err error) {
var ppidFlag int
if ppidFlag, err = cmd.Flags().GetInt("ppid"); err != nil {
User, err = initUser(ppidFlag)
} else {
User, err = initUser(-1)
ppidFlag = -1
}
if ppidFlag == 0 { // 0 is the debugging pid to autoselect parent pid
ppidFlag = os.Getppid()
}

User, err = initUser(ppidFlag)

if debug, err := cmd.Flags().GetBool("debug"); err == nil {
Debug = debug
console.SetDebug(Debug)
}

if User.wrapped == false && cmd.Name() != "setup" {
err = fmt.Errorf("GitSpaces not run from wrapper. Run 'gitspaces setup' to initialize GitSpaces. For more information, see README at /~https://github.com/davfive/gitspaces.")
}

return err
}

Expand Down
127 changes: 57 additions & 70 deletions internal/config/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,12 @@ import (
//go:embed templates/config.yaml.tmpl
var defaultConfigYaml []byte

//go:embed templates/gitspaces.setup.go.tmpl
var shellSetupInstructions []byte

type userStruct struct {
config *viper.Viper
dotDir string
ppid int
pterm string // Parent os stdout type (uname -o/-s)
wrapped bool
wrapped bool // exe called from gitspaces wrapper
projectPaths []string
}

Expand All @@ -43,8 +40,9 @@ func GetUserDotDir() string {
func Setup() {
console.Println(`
** Setup Action 1/2
Add ProjectPaths to %s
GitSpaces will use these paths to find your projects
** Add ProjectPaths to %s
GitSpaces will use these paths to find your projects
`, User.config.ConfigFileUsed())

if console.NewConfirm().Prompt("Edit config file?").Run() == true {
Expand All @@ -53,71 +51,64 @@ func Setup() {
}
}

shellrc := User.getShellRcFile()
console.Println(`
** Setup Action 2/2
Update shell profile for %s ...
GitSpaces requires a wrapper function in your shell profile/rc file because
some commands change the current working directory. The wrapper handles this.`, User.pterm)
console.Println("\nThe following wrapper files were created:")
** Update shell profile for %s ...
GitSpaces requires a wrapper function in your shell profile/rc file because
some commands change the current working directory. The wrapper handles this.
The following wrapper files were created:`, User.pterm)
shellFiles := GetShellFiles()
for _, key := range utils.SortKeys(shellFiles) {
console.Println(" %s", shellFiles[key].path)
console.Println(" %s", shellFiles[key].path)
}
console.Println("")

shellrc := User.getShellRcFile()
if User.pterm == "pwsh" {
console.Println("Add the following lines to your PowerShell $PROFILE file:")
console.Println(". %s", utils.CygwinizePath(shellFiles["ps1Script"].path))
console.Println("Set-Alias -Name gs -Value gitspaces # optional")
console.Println("")
console.Println("Your PowerShell profile is located at: %s", shellrc)
console.Println("For more information on your PowerShell profile, see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.4#the-profile-variable")
console.Println(`
Add the following lines to your PowerShell $PROFILE file:
. %s
Set-Alias -Name gs -Value gitspaces # optional
Your PowerShell profile is located at: %s
For more information on your PowerShell profile, see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles?view=powershell-7.4#the-profile-variable
`, shellFiles["ps1Script"].path, shellrc)
if console.NewConfirm().Prompt("Edit PowerShell $PROFILE?").Run() == true {
if err := utils.OpenFileInDefaultApp(shellrc); err != nil {
console.Errorln("Editing PowerShell $PROFILE failed: %s", err)
}
}
} else if shellrc != "" {
console.Println("Add the following lines to your shell rc file: %s", shellrc)
console.Println(". %s/gitspaces.sh", utils.CygwinizePath(User.dotDir))
console.Println("alias gs=gitspaces")
console.Println("")
console.Println("Your shell rc file is located at: %s", shellrc)
console.Println(`
Add the following lines to your current shell rc file:
. %s/gitspaces.sh
alias gs=gitspaces
Your shell rc file is located at: %s",
`, utils.CygwinizePath(User.dotDir), shellrc)
if console.NewConfirm().Prompt("Edit shell rc file?").Run() == true {
if err := utils.OpenFileInDefaultApp(shellrc); err != nil {
console.Errorln("Editing shell rc file failed: %s", err)
}
}
} else {
console.Println("Unable to determine your shell. Assuming *nix-style, add")
console.Println("the following lines to your shell rc file")
console.Println(". %s/gitspaces.sh", utils.CygwinizePath(User.dotDir))
console.Println("alias gs=gitspaces")
}
}

func (user *userStruct) writeShellWrapperSetupHelpFile() error {
shellFiles := GetShellFiles()
shellTmplVars := user.getShellTmplVars(shellFiles)

tmpl, err := template.
New("SetupHelp").
Funcs(template.FuncMap{"cygwinizePath": utils.CygwinizePath}).
Parse(string(shellSetupInstructions))
if err != nil {
return err
}

help, err := utils.WriteTemplateToString(tmpl, shellTmplVars)
if err != nil {
return err
console.Println(`
Unable to determine your current shell rc file. Assuming *nix-style, add")
the following lines to your shell rc file:
. %s/gitspaces.sh
alias gs=gitspaces
`, utils.CygwinizePath(User.dotDir))
}
console.Println(help)
return nil
}

func initUser(ppidFlag int) (user *userStruct, err error) {
user = &userStruct{
dotDir: GetUserDotDir(),
wrapped: ppidFlag > 0, // ppid flag was passed on cmdline
}
user = &userStruct{dotDir: GetUserDotDir()}
user.SetParentProperties(ppidFlag)

for _, path := range []string{user.dotDir} {
if err := os.MkdirAll(path, os.ModePerm); err != nil {
return nil, err
}
if err := os.MkdirAll(user.dotDir, os.ModePerm); err != nil {
return nil, err
}

if err = user.initConfig(); err != nil {
Expand Down Expand Up @@ -211,38 +202,34 @@ func (user *userStruct) getShellRcFile() string {
return os.Getenv("PROFILE")
}

return "~/.<shell>rc"
return ""
}

func (user *userStruct) SetParentProperties(ppid int) {
if ppid > 0 {
realppid := os.Getppid()
if ppid > 0 && ppid == realppid {
user.ppid = ppid
user.wrapped = true
} else {
user.wrapped = false
user.ppid = os.Getppid()
}
if parentps, err := ps.FindProcess(user.ppid); err == nil {

if parentps, _ := ps.FindProcess(user.ppid); parentps != nil {
user.pterm = strings.ToLower(filepath.Base(parentps.Executable()))
} else {
console.Errorln("Parent process name not found: %s", err)
console.Debugln("Parent process name not found. Continuing without knowing parent shell type.")
user.pterm = ""
}

console.Println("Parent pid: %d", user.ppid)
console.Println("Parent terminal: %s", user.pterm)
console.Debugln("Parent pid: %d", user.ppid)
console.Debugln("Parent terminal: %s", user.pterm)
}

func (user *userStruct) GetParentTerminal() string {
return user.pterm
}

func (user *userStruct) RanFromWrapper(wrapped bool) bool {
return user.wrapped
}

func (user *userStruct) SetRunFromWrapper(wrapped bool) {
user.wrapped = wrapped
}

func (user *userStruct) WriteChdirPath(newdir string) {
if user.ppid <= 0 {
return
Expand Down
12 changes: 12 additions & 0 deletions internal/console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ import (
"os"
)

var debug bool = false

func SetDebug(debugFlag bool) {
debug = debugFlag
}

func Debugln(format string, a ...any) {
if debug {
Println(format, a...)
}
}

func Println(format string, a ...any) {
fmt.Printf(format, a...)
fmt.Println()
Expand Down
14 changes: 4 additions & 10 deletions internal/utils/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,12 @@ func CreateEmptyFile(path string) (err error) {
return nil
}

func CreateFile(path string) (err error) {
if err = os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
return err
func CreateEmptyFileIfNotExists(path string) (err error) {
if PathExists(path) {
return nil
}

var file *os.File
if file, err = os.Create(path); err != nil {
return err
}

defer file.Close()
return nil
return CreateEmptyFile(path)
}

func GetUserHomeDir() string {
Expand Down
22 changes: 22 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,24 @@ func Get[E comparable](v E, fallbacks ...E) E {
}
}

// GetCygpathHomeDir returns the user's ~/ dir from the cygpath command.
// If cygpath doesn't exist (either not on Windows or Cygwin is not installed)
// then the normal $USERPROFILE or /Users/<username> is returned. This
// method is only used to determine the location of the user's rc file for
// setup, in all other cases the user's home dir is the normal one.
func GetCygpathHomeDir() string {
if runtime.GOOS != "windows" {
return GetUserHomeDir()
}

cmd := exec.Command("cygpath", "-m", "~")
out, err := cmd.Output()
if err != nil {
return GetUserHomeDir()
}
return strings.TrimSpace(string(out)) // Already Cygwinized
}

func MakeDirnameAvailableValidator(parentDir string) func(string) error {
return func(dirname string) error {
if strings.HasPrefix(dirname, ".") || PathExists(filepath.Join(parentDir, dirname)) {
Expand All @@ -54,6 +72,10 @@ func MakeDirnameAvailableValidator(parentDir string) func(string) error {
}

func OpenFileInDefaultApp(path string) error {
if err := CreateEmptyFileIfNotExists(path); err != nil {
return err
}

switch runtime.GOOS {
case "windows":
return exec.Command("cmd", "/c", path).Start()
Expand Down

0 comments on commit e2adaf3

Please sign in to comment.