mirror of https://github.com/xrehpicx/pee.git
init v0
This commit is contained in:
commit
1859cbf02d
|
@ -0,0 +1,226 @@
|
||||||
|
package projectconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Configuration struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
SessionName string `yaml:"session_name"`
|
||||||
|
WorkingDir string `yaml:"working_dir"` // New field for working directory
|
||||||
|
Tabs []struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Commands []string `yaml:"commands"`
|
||||||
|
} `yaml:"tabs"`
|
||||||
|
LastOpened time.Time `yaml:"last_opened"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var configDir string
|
||||||
|
|
||||||
|
func initConfigDir() string {
|
||||||
|
var configDirPath string
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
usr, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
configDirPath = filepath.Join(usr, "AppData", "Local", "pee")
|
||||||
|
} else {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
configDirPath = filepath.Join(homeDir, ".config", "pee")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := os.Stat(configDirPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
if err := os.MkdirAll(configDirPath, 0755); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configDirPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
configDir = initConfigDir() // Initialize the configDir variable.
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProjectConfigFilePath(projectName string) string {
|
||||||
|
return filepath.Join(configDir, projectName+".yml")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load(filename string) (*Configuration, error) {
|
||||||
|
data, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var config Configuration
|
||||||
|
err = yaml.Unmarshal(data, &config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// config.LastOpened = time.Now()
|
||||||
|
|
||||||
|
err = WriteConfigToFile(filename, &config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateLastOpened(projectName string) error {
|
||||||
|
configFile := ProjectConfigFilePath(projectName)
|
||||||
|
|
||||||
|
config, err := Load(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
config.LastOpened = time.Now()
|
||||||
|
|
||||||
|
err = WriteConfigToFile(configFile, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListProjects() (map[string]*Configuration, error) {
|
||||||
|
projectConfigs := make(map[string]*Configuration)
|
||||||
|
|
||||||
|
files, err := os.ReadDir(configDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
projectName := strings.TrimSuffix(file.Name(), ".yml")
|
||||||
|
|
||||||
|
projectConfigFile := filepath.Join(configDir, file.Name())
|
||||||
|
config, err := Load(projectConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
projectConfigs[projectName] = config
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectConfigs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProjectConfig(projectName string) (*Configuration, error) {
|
||||||
|
projectConfigFile := ProjectConfigFilePath(projectName)
|
||||||
|
config, err := Load(projectConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateProjectConfig(projectName string, updatedConfig *Configuration) error {
|
||||||
|
configFile := ProjectConfigFilePath(projectName)
|
||||||
|
|
||||||
|
err := WriteConfigToFile(configFile, updatedConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProjectExists(projectName string) bool {
|
||||||
|
configFile := ProjectConfigFilePath(projectName)
|
||||||
|
|
||||||
|
if _, err := os.Stat(configFile); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateProject(projectName, sessionName, workingDir string, tabs []struct {
|
||||||
|
Name string
|
||||||
|
Commands []string
|
||||||
|
},
|
||||||
|
) (string, error) {
|
||||||
|
configFile := ProjectConfigFilePath(projectName)
|
||||||
|
|
||||||
|
if _, err := os.Stat(configFile); err == nil {
|
||||||
|
return "", fmt.Errorf("Project with the name '%s' already exists", projectName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tabsWithYAMLTags []struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Commands []string `yaml:"commands"`
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tab := range tabs {
|
||||||
|
tabWithYAMLTags := struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Commands []string `yaml:"commands"`
|
||||||
|
}{
|
||||||
|
Name: tab.Name,
|
||||||
|
Commands: tab.Commands,
|
||||||
|
}
|
||||||
|
tabsWithYAMLTags = append(tabsWithYAMLTags, tabWithYAMLTags)
|
||||||
|
}
|
||||||
|
|
||||||
|
newConfig := &Configuration{
|
||||||
|
Name: projectName,
|
||||||
|
SessionName: sessionName,
|
||||||
|
WorkingDir: workingDir,
|
||||||
|
Tabs: tabsWithYAMLTags,
|
||||||
|
LastOpened: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := WriteConfigToFile(configFile, newConfig)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return configFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteConfigToFile(filename string, config *Configuration) error {
|
||||||
|
data, err := yaml.Marshal(config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
indentedYAML := indentYAML(string(data), "") // Convert data to string
|
||||||
|
|
||||||
|
err = os.WriteFile(filename, []byte(indentedYAML), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func indentYAML(yamlString, prefix string) string {
|
||||||
|
lines := strings.Split(yamlString, "\n")
|
||||||
|
indentedLines := make([]string, len(lines))
|
||||||
|
|
||||||
|
for i, line := range lines {
|
||||||
|
indentedLines[i] = prefix + line
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(indentedLines, "\n")
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
projectconfig "pee/config"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateTmuxSession creates a tmux session based on the given Configuration.
|
||||||
|
func CreateTmuxSession(config *projectconfig.Configuration) error {
|
||||||
|
sessionName := config.SessionName
|
||||||
|
|
||||||
|
// Check if the session exists
|
||||||
|
checkSessionCmd := exec.Command("tmux", "has-session", "-t", sessionName)
|
||||||
|
if err := checkSessionCmd.Run(); err == nil {
|
||||||
|
// If it exists, switch to the session
|
||||||
|
switchSessionCmd := exec.Command("tmux", "switch-client", "-t", sessionName)
|
||||||
|
if err := switchSessionCmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If it doesn't exist, create the session
|
||||||
|
createSessionCmd := exec.Command("tmux", "new-session", "-d", "-s", sessionName)
|
||||||
|
if err := createSessionCmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Ran command", "command", createSessionCmd.String())
|
||||||
|
|
||||||
|
// Change the working directory
|
||||||
|
changeDirCmd := exec.Command("tmux", "send-keys", "-t", sessionName, "cd "+config.WorkingDir, "Enter")
|
||||||
|
if err := changeDirCmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Ran command", "command", changeDirCmd.String())
|
||||||
|
|
||||||
|
// Send commands to the session for the first tab
|
||||||
|
sendCommandsCmd := exec.Command("tmux", "send-keys", "-t", sessionName, strings.Join(config.Tabs[0].Commands, " && "), "Enter")
|
||||||
|
if err := sendCommandsCmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Ran command", "command", sendCommandsCmd.String())
|
||||||
|
// Rename the tab to the specified name
|
||||||
|
renameTabCmd := exec.Command("tmux", "rename-window", "-t", sessionName+":1", config.Tabs[0].Name)
|
||||||
|
if err := renameTabCmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and run commands for additional tabs
|
||||||
|
for i, tab := range config.Tabs[1:] {
|
||||||
|
windowName := fmt.Sprintf("%s:%d", sessionName, i+2)
|
||||||
|
createWindowCmd := exec.Command("tmux", "new-window", "-t", windowName, "-n", tab.Name)
|
||||||
|
if err := createWindowCmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Ran command", "command", createWindowCmd.String())
|
||||||
|
|
||||||
|
changeDirCmd = exec.Command("tmux", "send-keys", "-t", windowName, "cd "+config.WorkingDir, "Enter")
|
||||||
|
if err := changeDirCmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Ran command", "command", changeDirCmd.String())
|
||||||
|
|
||||||
|
sendCommandsCmd = exec.Command("tmux", "send-keys", "-t", windowName, strings.Join(tab.Commands, " && "), "Enter")
|
||||||
|
if err := sendCommandsCmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Info("Ran command", "command", sendCommandsCmd.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the initial window and switch to the session
|
||||||
|
selectWindowCmd := exec.Command("tmux", "select-window", "-t", sessionName+":1")
|
||||||
|
if err := selectWindowCmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switchSessionCmd := exec.Command("tmux", "switch-client", "-t", sessionName)
|
||||||
|
if err := switchSessionCmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
module pee
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/alecthomas/kong v0.8.1
|
||||||
|
github.com/spf13/cobra v1.7.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/charmbracelet/lipgloss v0.9.1 // indirect
|
||||||
|
github.com/charmbracelet/log v0.2.5 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
github.com/charmbracelet/bubbles v0.16.1
|
||||||
|
github.com/charmbracelet/bubbletea v0.24.2 // indirect
|
||||||
|
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||||
|
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||||
|
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
||||||
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
|
github.com/muesli/reflow v0.3.0 // indirect
|
||||||
|
github.com/muesli/termenv v0.15.2 // indirect
|
||||||
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
|
golang.org/x/sys v0.12.0 // indirect
|
||||||
|
golang.org/x/term v0.6.0 // indirect
|
||||||
|
golang.org/x/text v0.3.8 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
)
|
|
@ -0,0 +1,69 @@
|
||||||
|
github.com/alecthomas/kong v0.8.1 h1:acZdn3m4lLRobeh3Zi2S2EpnXTd1mOL6U7xVml+vfkY=
|
||||||
|
github.com/alecthomas/kong v0.8.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
|
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
|
||||||
|
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
||||||
|
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
|
||||||
|
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
|
||||||
|
github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU=
|
||||||
|
github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU=
|
||||||
|
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
||||||
|
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
|
||||||
|
github.com/charmbracelet/log v0.2.5 h1:1yVvyKCKVV639RR4LIq1iy1Cs1AKxuNO+Hx2LJtk7Wc=
|
||||||
|
github.com/charmbracelet/log v0.2.5/go.mod h1:nQGK8tvc4pS9cvVEH/pWJiZ50eUq1aoXUOjGpXvdD0k=
|
||||||
|
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||||
|
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||||
|
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||||
|
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||||
|
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||||
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
|
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||||
|
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||||
|
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
|
||||||
|
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
|
||||||
|
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||||
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||||
|
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||||
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||||
|
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
|
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||||
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,21 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
projectconfig "pee/config"
|
||||||
|
projectmanager "pee/project-manager"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
projectconfig.Init()
|
||||||
|
projectmanager.RootCmd.AddCommand(projectmanager.ListProjects)
|
||||||
|
projectmanager.RootCmd.AddCommand(projectmanager.InitCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := projectmanager.RootCmd.Execute(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package projectmanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
projectconfig "pee/config"
|
||||||
|
"pee/controller"
|
||||||
|
"pee/ui/filepicker"
|
||||||
|
"pee/ui/table"
|
||||||
|
|
||||||
|
btable "github.com/charmbracelet/bubbles/table"
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ListProjects = &cobra.Command{
|
||||||
|
Use: "ls",
|
||||||
|
Short: "List all projects",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
projects, err := projectconfig.ListProjects()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
columns := []btable.Column{
|
||||||
|
{Title: "Name", Width: 20},
|
||||||
|
{Title: "Session Name", Width: 20},
|
||||||
|
{Title: "Working Dir", Width: 50},
|
||||||
|
{Title: "Last Opened", Width: 20},
|
||||||
|
}
|
||||||
|
var rows []btable.Row
|
||||||
|
|
||||||
|
for projectName, config := range projects {
|
||||||
|
row := []string{
|
||||||
|
projectName,
|
||||||
|
config.SessionName,
|
||||||
|
config.WorkingDir,
|
||||||
|
config.LastOpened.Format("2006-01-02 15:04:05"),
|
||||||
|
}
|
||||||
|
rows = append(rows, row)
|
||||||
|
}
|
||||||
|
selectedRow, action := table.Table(columns, rows)
|
||||||
|
if action == "edit" {
|
||||||
|
// print a vim command to open the config file
|
||||||
|
fmt.Println("vim", projectconfig.ProjectConfigFilePath(selectedRow[0]))
|
||||||
|
}
|
||||||
|
if action == "open" {
|
||||||
|
ExecuteProjectEnv(selectedRow[0])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var RootCmd = &cobra.Command{
|
||||||
|
Use: "pee",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
ExecuteProjectEnv(args[0])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExecuteProjectEnv(projectName string) {
|
||||||
|
config, err := projectconfig.GetProjectConfig(projectName)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = controller.CreateTmuxSession(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
projectconfig.UpdateLastOpened(projectName)
|
||||||
|
log.Info("Created tmux session", "name", config.SessionName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var InitCmd = &cobra.Command{
|
||||||
|
Use: "init",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
projectName := args[0]
|
||||||
|
projectExists := projectconfig.ProjectExists(projectName)
|
||||||
|
if projectExists {
|
||||||
|
log.Warn("Project already exists", "name", projectName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selected, err := filepicker.FilePicker("Select your project dir", "Selected Dir: ")
|
||||||
|
if selected == "" {
|
||||||
|
log.Warn("No dir selected, aborting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("Selected", "work_dir", selected)
|
||||||
|
|
||||||
|
sessionName := projectName
|
||||||
|
tabs := []struct {
|
||||||
|
Name string
|
||||||
|
Commands []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "editor",
|
||||||
|
Commands: []string{"echo 'command to open ur editor'"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "dev server",
|
||||||
|
Commands: []string{"echo 'command to start dev server'", "echo 'command to just initialize ur dependencies'"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "git",
|
||||||
|
Commands: []string{"echo 'command to open ur git client (use lazygit its amazing)'"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
logger := log.NewWithOptions(os.Stderr, log.Options{
|
||||||
|
ReportCaller: false,
|
||||||
|
ReportTimestamp: false,
|
||||||
|
})
|
||||||
|
ppath, err := projectconfig.CreateProject(projectName, sessionName, selected, tabs)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
} else {
|
||||||
|
// logger.Info("Created Project", "path", ppath)
|
||||||
|
fmt.Println("Created Project", "setup your config by editing: ", ppath)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
package filepicker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbles/filepicker"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
type model struct {
|
||||||
|
headerMessage string
|
||||||
|
selectedMessage string
|
||||||
|
filepicker filepicker.Model
|
||||||
|
selectedFile string
|
||||||
|
quitting bool
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type clearErrorMsg struct{}
|
||||||
|
|
||||||
|
func clearErrorAfter(t time.Duration) tea.Cmd {
|
||||||
|
return tea.Tick(t, func(_ time.Time) tea.Msg {
|
||||||
|
return clearErrorMsg{}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) Init() tea.Cmd {
|
||||||
|
return m.filepicker.Init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.String() {
|
||||||
|
case " ":
|
||||||
|
m.quitting = true
|
||||||
|
return m, tea.Quit
|
||||||
|
case "q", "ctrl+c", "esc":
|
||||||
|
m.quitting = true
|
||||||
|
m.selectedFile = ""
|
||||||
|
m.filepicker.FileSelected = ""
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
case clearErrorMsg:
|
||||||
|
m.err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmd tea.Cmd
|
||||||
|
m.filepicker, cmd = m.filepicker.Update(msg)
|
||||||
|
|
||||||
|
// Did the user select a file?
|
||||||
|
if didSelect, path := m.filepicker.DidSelectFile(msg); didSelect {
|
||||||
|
// Get the path of the selected file.
|
||||||
|
m.selectedFile = path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Did the user select a disabled file?
|
||||||
|
// This is only necessary to display an error to the user.
|
||||||
|
if didSelect, path := m.filepicker.DidSelectDisabledFile(msg); didSelect {
|
||||||
|
// Let's clear the selectedFile and display an error.
|
||||||
|
m.err = errors.New(path + " is not valid.")
|
||||||
|
m.selectedFile = ""
|
||||||
|
return m, tea.Batch(cmd, clearErrorAfter(2*time.Second))
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) View() string {
|
||||||
|
if m.quitting {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var s strings.Builder
|
||||||
|
s.WriteString("\n ")
|
||||||
|
if m.err != nil {
|
||||||
|
s.WriteString(m.filepicker.Styles.DisabledFile.Render(m.err.Error()))
|
||||||
|
} else if m.selectedFile == "" {
|
||||||
|
s.WriteString(m.headerMessage)
|
||||||
|
} else {
|
||||||
|
s.WriteString(m.selectedMessage + m.filepicker.Styles.Selected.Render(m.selectedFile) + " <space> to select")
|
||||||
|
}
|
||||||
|
s.WriteString("\n\n" + m.filepicker.View() + "\n" + "<esc> or q to quit")
|
||||||
|
return s.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilePicker(headerMessage string, selectedMessage string) (string, error) {
|
||||||
|
fp := filepicker.New()
|
||||||
|
// fp.AllowedTypes = []string{".mod", ".sum", ".go", ".txt", ".md"}
|
||||||
|
fp.DirAllowed = true
|
||||||
|
fp.FileAllowed = false
|
||||||
|
fp.CurrentDirectory, _ = os.Getwd()
|
||||||
|
fp.FileSelected, _ = os.Getwd()
|
||||||
|
|
||||||
|
// Set default values for header and footer messages
|
||||||
|
if headerMessage == "" {
|
||||||
|
headerMessage = "Select file..."
|
||||||
|
}
|
||||||
|
if selectedMessage == "" {
|
||||||
|
selectedMessage = "Selected file: "
|
||||||
|
}
|
||||||
|
m := model{
|
||||||
|
headerMessage: headerMessage,
|
||||||
|
selectedMessage: selectedMessage,
|
||||||
|
filepicker: fp,
|
||||||
|
selectedFile: fp.FileSelected,
|
||||||
|
}
|
||||||
|
tm, err := tea.NewProgram(&m, tea.WithOutput(os.Stderr)).Run()
|
||||||
|
mm := tm.(model)
|
||||||
|
|
||||||
|
return mm.selectedFile, err
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
package table
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/charmbracelet/bubbles/help"
|
||||||
|
"github.com/charmbracelet/bubbles/key"
|
||||||
|
"github.com/charmbracelet/bubbles/table"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var baseStyle = lipgloss.NewStyle().
|
||||||
|
BorderStyle(lipgloss.NormalBorder()).
|
||||||
|
BorderForeground(lipgloss.Color("240"))
|
||||||
|
|
||||||
|
type keyMap struct {
|
||||||
|
Quit key.Binding
|
||||||
|
Edit key.Binding
|
||||||
|
Open key.Binding
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k keyMap) ShortHelp() []key.Binding {
|
||||||
|
return []key.Binding{k.Quit, k.Edit, k.Open}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k keyMap) FullHelp() [][]key.Binding {
|
||||||
|
return [][]key.Binding{
|
||||||
|
{k.Quit, k.Edit, k.Open}, // first column
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = keyMap{
|
||||||
|
Quit: key.NewBinding(
|
||||||
|
key.WithKeys("q", "esc", "ctrl+c"),
|
||||||
|
key.WithHelp("q", "quit"),
|
||||||
|
),
|
||||||
|
Edit: key.NewBinding(
|
||||||
|
key.WithKeys("e"),
|
||||||
|
key.WithHelp("e", "edit selected project's configuration"),
|
||||||
|
),
|
||||||
|
Open: key.NewBinding(
|
||||||
|
key.WithKeys("enter"),
|
||||||
|
key.WithHelp("enter", "open selected project"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
type model struct {
|
||||||
|
table table.Model
|
||||||
|
action string
|
||||||
|
help help.Model
|
||||||
|
keys keyMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) Init() tea.Cmd { return nil }
|
||||||
|
|
||||||
|
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
var cmd tea.Cmd
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.String() {
|
||||||
|
case "esc":
|
||||||
|
if m.table.Focused() {
|
||||||
|
m.table.Blur()
|
||||||
|
} else {
|
||||||
|
m.table.Focus()
|
||||||
|
}
|
||||||
|
case "q", "ctrl+c":
|
||||||
|
return m, tea.Quit
|
||||||
|
case "enter":
|
||||||
|
m.action = "open"
|
||||||
|
return m, tea.Quit
|
||||||
|
case "e":
|
||||||
|
m.action = "edit"
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.table, cmd = m.table.Update(msg)
|
||||||
|
return m, cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m model) View() string {
|
||||||
|
helpView := m.help.View(m.keys)
|
||||||
|
return baseStyle.Render(m.table.View()) + "\n" + helpView + "\n\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
func Table(columns []table.Column, rows []table.Row) (table.Row, string) {
|
||||||
|
t := table.New(
|
||||||
|
table.WithColumns(columns),
|
||||||
|
table.WithRows(rows),
|
||||||
|
table.WithFocused(true),
|
||||||
|
table.WithHeight(7),
|
||||||
|
)
|
||||||
|
|
||||||
|
s := table.DefaultStyles()
|
||||||
|
s.Header = s.Header.
|
||||||
|
BorderStyle(lipgloss.NormalBorder()).
|
||||||
|
BorderForeground(lipgloss.Color("240")).
|
||||||
|
BorderBottom(true).
|
||||||
|
Bold(false)
|
||||||
|
s.Selected = s.Selected.
|
||||||
|
Foreground(lipgloss.Color("229")).
|
||||||
|
Background(lipgloss.Color("57")).
|
||||||
|
Bold(false)
|
||||||
|
t.SetStyles(s)
|
||||||
|
|
||||||
|
m := model{t, "", help.New(), keys}
|
||||||
|
newModel, err := tea.NewProgram(m).Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
m = newModel.(model)
|
||||||
|
selected := rows[m.table.Cursor()]
|
||||||
|
|
||||||
|
return selected, m.action
|
||||||
|
}
|
Loading…
Reference in New Issue