Merge pull request #84 from Slug-Boi/feat_default-editor

Feat: default editor + config
This commit is contained in:
Theis
2025-08-18 13:43:01 +02:00
committed by GitHub
11 changed files with 396 additions and 92 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ func main() {
// mount the source code directory on the host
// at /src in the container
source := client.Container().
From("golang:1.23").
From("golang:1.24").
WithDirectory("/src_d", client.Host().Directory(".", dagger.HostDirectoryOpts{
Exclude: []string{},
})).WithMountedCache("/src_d/dagger_dep_cache/go_dep", goCache)
+1 -1
View File
@@ -1,6 +1,6 @@
module ci
go 1.23.2
go 1.24.6
require dagger.io/dagger v0.13.6
+1 -1
View File
@@ -24,7 +24,7 @@ func main() {
// mount the source code directory on the host
// at /src in the container
source := client.Container().
From("golang:1.23").
From("golang:1.24").
WithDirectory("/src_d", client.Host().Directory(".", dagger.HostDirectoryOpts{
Exclude: []string{"build/"},
})).WithMountedCache("/src_d/dagger_dep_cache/go_dep", goCache)
+3 -7
View File
@@ -24,7 +24,7 @@ var amendCmd = &cobra.Command{
pflag, _ := cmd.Flags().GetBool("print-output")
tflag, _ := cmd.Flags().GetBool("test_print")
git_flags, _ := cmd.Flags().GetString("git-flags")
edit, _ := cmd.Flags().GetBool("edit")
no_edit, _ := cmd.Flags().GetBool("no-edit")
hash, _ := cmd.Flags().GetString("hash")
@@ -34,10 +34,6 @@ var amendCmd = &cobra.Command{
return
}
if edit {
}
var authors string
if len(args) == 0 {
// open the TUI to select co-authors
@@ -56,7 +52,7 @@ var amendCmd = &cobra.Command{
git_flags_split = strings.Split(git_flags, " ")
}
err, _ := utils.GitCommitAppender(authors, hash, git_flags_split, tflag, pflag)
err, _ := utils.GitCommitAppender(authors, hash, git_flags_split, tflag, pflag, no_edit)
if err != nil {
println("Error amending commit:", err.Error())
os.Exit(1)
@@ -70,6 +66,6 @@ func init() {
amendCmd.Flags().StringP("git-flags", "g", "", "Git flags to add to the commit command")
amendCmd.Flags().BoolP("print-output", "p", false, "Print the commit message to stdout")
amendCmd.Flags().BoolP("test_print", "t", false, "Print the commit message to stdout without amending")
amendCmd.Flags().BoolP("edit", "e", false, "Edit the commit message in the editor")
amendCmd.Flags().BoolP("no-edit", "n", false, "Do not edit the commit message in the editor")
amendCmd.Flags().StringP("hash", "s", "", "Hash of the commit to amend")
}
+68
View File
@@ -0,0 +1,68 @@
package cmd
import (
"fmt"
"github.com/Slug-Boi/cocommit/src/cmd/utils"
"github.com/spf13/cobra"
)
// configCmd represents the config command
var configCmd = &cobra.Command{
Use: "config",
Short: "This command will create or edit the configuration file for cocommit",
Long: `This command will create or edit the configuration file for cocommit.
You can set various settings like the author file, starting scope, and which editor to use.
A flag can be used to print the current configuration, as well as its location.
To see what options are available to use in the config file, please refer to the wiki page on the GitHub repository:
COMING SOON`,
Run: func(cmd *cobra.Command, args []string) {
printConfig, _ := cmd.Flags().GetBool("print")
editConfig, _ := cmd.Flags().GetBool("edit")
configLocation, _ := cmd.Flags().GetBool("location")
removeConfig, _ := cmd.Flags().GetBool("remove")
if printConfig {
if !utils.CheckConfig() {
fmt.Println("No configuration file found. Default is being used.")
fmt.Println("Default configuration:")
} else {
fmt.Println("Current configuration:")
}
fmt.Println(utils.ConfigVar.String())
}
// Check if the config file exists
if !utils.CheckConfig() {
err := utils.HandleMissingConfig()
if err != nil {
panic(fmt.Sprintf("Error handling missing configuration file: %v", err))
}
return
}
if printConfig {
return
}
if editConfig {
utils.LaunchEditor("default",utils.GetConfigFilePath())
return
} else if configLocation {
fmt.Println("Configuration file location:", utils.GetConfigFilePath())
return
} else if removeConfig {
utils.RemoveConfig()
return
}
fmt.Println("No action specified. Use flags to specify an action, use -h for help.")
},
}
func init() {
rootCmd.AddCommand(configCmd)
configCmd.Flags().BoolP("print", "p", false, "Print the current configuration")
configCmd.Flags().BoolP("edit", "e", false, "Edit the configuration file in your default editor")
configCmd.Flags().BoolP("location", "l", false, "Print the location of the configuration file")
configCmd.Flags().BoolP("remove", "r", false, "Remove the configuration file")
}
+11 -2
View File
@@ -9,8 +9,8 @@ import (
"github.com/Slug-Boi/cocommit/src/cmd/tui"
"github.com/Slug-Boi/cocommit/src/cmd/utils"
"github.com/inancgumus/screen"
"github.com/charmbracelet/lipgloss"
"github.com/inancgumus/screen"
"github.com/spf13/cobra"
)
@@ -155,7 +155,16 @@ func Execute() {
func call_tui(args []string) []string {
// append commit message to args
args = append(args, tui.Entry_CM())
//args = append(args, tui.Entry_CM())
message, err := utils.LaunchEditor(utils.ConfigVar.Settings.Editor,"")
if err != nil {
panic(fmt.Sprintf("Error launching editor: %v", err))
}
if message == "" {
message = tui.Entry_CM()
}
args = append(args, message)
// clear the screen
screen.Clear()
+2 -6
View File
@@ -14,8 +14,6 @@ import (
// An example of the author file can be found in the examples folder of the repo
func Find_authorfile() string {
var file string
if os.Getenv("author_file") == "" {
if ConfigVar == nil {
cfg, _ := LoadConfig()
if cfg == nil {
@@ -31,20 +29,18 @@ func Find_authorfile() string {
Editor: "built-in",
},
}
}
cfg.SetGlobalConfig()
}
}
if os.Getenv("author_file") == "" {
if ConfigVar.Settings.AuthorFile != "" {
file = ConfigVar.Settings.AuthorFile
} else if os.Getenv("author_file") != "" {
file = os.Getenv("author_file")
} else {
userconf, err :=os.UserConfigDir()
if err != nil {
panic(fmt.Sprintf("Error getting user config dir: %v", err))
}
if _, err := os.Stat(userconf+"/cocommit/authors.json"); os.IsNotExist(err) {
panic(fmt.Sprintf("No author file set, please set the author_file environment variable or create a config file using the command: cocommit config -c"))
} else {
+30 -2
View File
@@ -2,6 +2,7 @@ package utils
import (
"fmt"
"os"
"os/exec"
"regexp"
"slices"
@@ -129,7 +130,7 @@ func group_selection(group []User, excludeMode []string) []string {
return excludeMode
}
func GitCommitAppender(authors string, hash string, flags []string, t,p bool) (error, string) {
func GitCommitAppender(authors string, hash string, flags []string, t,p,n bool) (error, string) {
// Get old commit message
var cmd *exec.Cmd
@@ -153,9 +154,36 @@ func GitCommitAppender(authors string, hash string, flags []string, t,p bool) (e
// commit shell command
// specify git command1
input := []string{"commit"}
// Edit the old message
input = append(input, flags...)
old_commit = strings.TrimSpace(old_commit)
input = append(input, "--amend", "-m", old_commit+"\n"+authors)
// Edit old commit message
var edited_commit string
if !n {
// Create tempfile for the commit message
file, err := os.CreateTemp("", "cocommit_editor_*.txt")
if err != nil {
return fmt.Errorf("Could not create tempfile: %s", err.Error()), ""
}
defer os.Remove(file.Name())
// Write the old commit message to the file
_, err = file.WriteString(old_commit + "\n" + authors)
if err != nil {
return fmt.Errorf("Could not write to tempfile: %s", err.Error()), ""
}
file.Close()
edited_commit, err = LaunchEditor(ConfigVar.Settings.Editor, file.Name())
if err != nil {
return fmt.Errorf("Could not launch editor: %s", err.Error()), ""
}
} else {
edited_commit = old_commit + "\n" + authors
}
input = append(input, "--amend", "-m", edited_commit)
if p {
println(old_commit + "\n" + authors)
+51 -5
View File
@@ -33,6 +33,13 @@ type Config struct {
} `mapstructure:"settings"`
}
func (c *Config) String() string {
return fmt.Sprintf("Author File: %s\nStarting Scope: %s\nEditor: %s",
c.Settings.AuthorFile,
c.Settings.StartingScope,
c.Settings.Editor)
}
func init() {
configDir, err := os.UserConfigDir()
if err == nil {
@@ -40,10 +47,11 @@ func init() {
}
}
var v *viper.Viper
func LoadConfig() (*Config, error) {
// TODO: create if and give param as default config location
v := viper.New()
v = viper.New()
v.SetConfigName(configName)
v.SetConfigType(configType)
@@ -92,7 +100,7 @@ func (c *Config) SetGlobalConfig() {
}
}
func handleMissingConfig(v *viper.Viper) error {
func HandleMissingConfig() error {
fmt.Println("Config file not found. Would you like to create one? (y/n)")
var response string
if _, err := fmt.Scanln(&response); err != nil {
@@ -104,10 +112,48 @@ func handleMissingConfig(v *viper.Viper) error {
return fmt.Errorf("config file not found")
}
return createConfig(v)
if v == nil {
v = viper.New()
v.SetConfigName(configName)
v.SetConfigType(configType)
}
func createConfig(v *viper.Viper) error {
return CreateConfig()
}
func CheckConfig() bool {
if v == nil {
return false
} else if v.ConfigFileUsed() == "" {
return false
} else {
return true
}
}
func GetConfigFilePath() string {
if v == nil || v.ConfigFileUsed() == "" {
return ""
}
return v.ConfigFileUsed()
}
func RemoveConfig() error {
if v == nil || v.ConfigFileUsed() == "" {
return fmt.Errorf("no config file to remove")
}
configPath := v.ConfigFileUsed()
if err := os.Remove(configPath); err != nil {
return fmt.Errorf("failed to remove config file: %w", err)
}
fmt.Printf("Config file removed: %s\n", configPath)
return nil
}
func CreateConfig() error {
fmt.Println("Where would you like to create the config file?")
for i, path := range defaultConfigLocations {
fmt.Printf("%d. %s\n", i, path)
+104
View File
@@ -0,0 +1,104 @@
package utils
import (
"fmt"
"os"
"os/exec"
"strings"
)
func HandleEditor() (string, error) {
editor := ConfigVar.Settings.Editor
if editor == "built-in" {
return "", nil
}
if editor == "" || editor == "default" {
editor = os.Getenv("EDITOR")
if editor == "" {
editor = "vim" // default to vim if no editor is set
}
}
if _, err := exec.LookPath(editor); err != nil {
return "", fmt.Errorf("editor %s not found in PATH", editor)
}
output, err := LaunchEditor(editor, "")
if err != nil {
return "", fmt.Errorf("failed to launch editor %s: %v", editor, err)
}
return output, nil
}
func LaunchEditor(editor string, filepath string, ) (string, error) {
// Create a temp file or use an existing file
var tempFile *os.File
var err error
switch strings.ToLower(editor) {
case "default", "":
editor = os.Getenv("EDITOR")
if editor == "" {
editor = "vim" // default to vim if no editor is set
}
case "built-in":
// fallback to built-in editor
return "", nil
default:
if _, err := exec.LookPath(editor); err != nil {
return "", fmt.Errorf("editor %s not found in PATH", editor)
}
}
if filepath == "" {
tempFile, err = os.CreateTemp("", "cocommit_editor_*.txt")
defer os.Remove(tempFile.Name())
} else {
tempFile, err = os.OpenFile(filepath, os.O_RDWR, 0666)
}
if err != nil {
return "", fmt.Errorf("Could not create or open tempfile: %s", err.Error())
}
cmd := exec.Command(editor, tempFile.Name())
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return "", fmt.Errorf("error running editor command: %v", err)
}
data, err := os.ReadFile(tempFile.Name())
if err != nil {
return "", fmt.Errorf("error reading temp file: %v", err)
}
message := string(data)
if message == "" {
fmt.Printf("Error: Commit message is empty. Please provide a commit message.\n")
os.Exit(0)
}
// Clean up the temp file
if strings.HasSuffix(message, "\n") {
message = strings.TrimSuffix(message, "\n")
}
if strings.HasSuffix(message, "\r") {
message = strings.TrimSuffix(message, "\r")
}
if strings.TrimSpace(message) == "" {
fmt.Printf("Error: Commit message is empty. Please provide a commit message.\n")
os.Exit(0)
}
// If the message is too long, truncate it
// if len(message) > 72 {
// fmt.Printf("Warning: Commit message is too long (%d characters). It will be truncated to 72 characters.\n", len(message))
// //TODO: Maybe add the rest to the description of the message?
// //description := message[72:]
// message = message[:72]
// }
return message, nil
}
+71 -14
View File
@@ -7,11 +7,12 @@ import (
"io"
"net/http"
"os"
"os/exec"
"strings"
"testing"
"os/exec"
"github.com/Slug-Boi/cocommit/src/cmd/utils"
"github.com/spf13/viper"
)
const author_data = `
@@ -166,6 +167,7 @@ func Test_FindAuthorFilePanic(t *testing.T) {
os.Setenv("author_file", "")
os.Setenv("HOME", "")
os.Setenv("XDG_CONFIG_HOME", "")
utils.ConfigVar.Settings.AuthorFile = ""
utils.Find_authorfile()
}
@@ -178,14 +180,10 @@ func Test_FindAuthorFileEnv(t *testing.T) {
defer func() {
os.Setenv("author_file", originalAuthorFile)
if r := recover(); r == nil {
t.Errorf("Find_authorfile() did not panic")
}
}()
// Set an invalid environment variable to trigger panic
os.Setenv("author_file", "")
os.Setenv("author_file", "author_file_test")
utils.ConfigVar.Settings.AuthorFile = ""
utils.Find_authorfile()
}
@@ -652,7 +650,7 @@ func Test_CommitAppender(t *testing.T) {
message := strings.TrimSpace(string(out))
commit := utils.Commit("", authors)
err, appendedMessage := utils.GitCommitAppender(commit, "", nil, true, true)
err, appendedMessage := utils.GitCommitAppender(commit, "", nil, true, true, true)
if err != nil {
t.Errorf("GitCommitAppender() returned error: %v", err)
}
@@ -665,7 +663,7 @@ func Test_CommitAppender(t *testing.T) {
// check inverted commit
authors = []string{"^te"}
commit = utils.Commit("", authors)
err, appendedMessage = utils.GitCommitAppender(commit, "", nil, true, true)
err, appendedMessage = utils.GitCommitAppender(commit, "", nil, true, true, true)
if err != nil {
t.Errorf("GitCommitAppender() returned error: %v", err)
}
@@ -678,7 +676,7 @@ func Test_CommitAppender(t *testing.T) {
// Test CommitAppender with multiple authors
authors = []string{"te", "testtest"}
commit = utils.Commit("", authors)
err, appendedMessage = utils.GitCommitAppender(commit, "", nil, true, true)
err, appendedMessage = utils.GitCommitAppender(commit, "", nil, true, true, true)
if err != nil {
t.Errorf("GitCommitAppender() returned error: %v", err)
}
@@ -690,7 +688,7 @@ func Test_CommitAppender(t *testing.T) {
// Test CommitAppender with all authors
authors = []string{"all"}
commit = utils.Commit("", authors)
err, appendedMessage = utils.GitCommitAppender(commit, "", nil, true, true)
err, appendedMessage = utils.GitCommitAppender(commit, "", nil, true, true, true)
if err != nil {
t.Errorf("GitCommitAppender() returned error: %v", err)
}
@@ -704,7 +702,7 @@ func Test_CommitAppender(t *testing.T) {
// Test CommitAppender with group authors
authors = []string{"gr1"}
commit = utils.Commit("", authors)
err, appendedMessage = utils.GitCommitAppender(commit, "", nil, true, true)
err, appendedMessage = utils.GitCommitAppender(commit, "", nil, true, true, true)
if err != nil {
t.Errorf("GitCommitAppender() returned error: %v", err)
}
@@ -842,6 +840,65 @@ func Test_FetchGHProfileHTTP(t *testing.T) {
}
}
// Github tests END
// Config tests BEGIN
func Test_Save(t *testing.T) {
setup()
defer teardown()
filename := "test_save_config.toml"
initial_config_data := `[settings]
author_file = "test_authors.json"
starting_scope = "git"
editor = "built-in"`
os.Create(filename)
defer os.Remove(filename)
// Write some test data to the file
os.WriteFile(filename, []byte(initial_config_data), 0644)
override_cfg := &utils.Config{
Settings: struct {
AuthorFile string `mapstructure:"author_file"`
StartingScope string `mapstructure:"starting_scope"`
Editor string `mapstructure:"editor"`
}{
AuthorFile: "test_authors.json",
StartingScope: "git",
Editor: "built-in",
}}
// Set viper config file to be cfg
viper.SetConfigFile(filename)
// Set the config type to toml
viper.SetConfigType("toml")
// Change some values in the config
override_cfg.Settings.AuthorFile = "test"
override_cfg.Settings.StartingScope = "not_git"
override_cfg.Settings.Editor = "test_editor"
// Save the config
err := override_cfg.Save()
if err != nil {
t.Errorf("Save() returned error: %v", err)
}
// Check if file exists and contains expected content
data, err := os.ReadFile(filename)
if err != nil {
t.Errorf("Save() did not create file: %v", data)
}
if string(initial_config_data) == string(data) {
t.Errorf("Save() did not write expected content:\nNew:\n%s\n\nOld:\n%s", string(data), string(initial_config_data))
}
}