mirror of
https://github.com/Slug-Boi/cocommit.git
synced 2026-05-13 12:45:47 +00:00
Merge pull request #64 from Slug-Boi/refactor_authors_as_json
feat: change defautl author format to json
This commit is contained in:
@@ -128,13 +128,49 @@ customCommands:
|
||||
A sample lazygit config file can be found [here](https://github.com/Slug-Boi/cocommit/blob/main/lazygit_config/config.yml)
|
||||
|
||||
# Syntax for the author file
|
||||
The syntax for the author file can be found at the top of the template file included in the repo. It should look like this (opt) is optional syntax:
|
||||
The syntax for the author file is json below is a small example with fake information to show what it looks like. The author file can be edited safely from the tool so there is no real need to edit this manually. Whilst this format is a little heavier than the old custom CSV format it is much easier to work with and handle json so rest assured this is best way forward
|
||||
```json
|
||||
{
|
||||
"Authors":{
|
||||
"Morgan Rivers":{
|
||||
"shortname":"mr",
|
||||
"longname":"Morgan Rivers",
|
||||
"username":"morgan-rivers",
|
||||
"email":"mrivers@example.com",
|
||||
"ex":false,
|
||||
"groups":[
|
||||
"dev",
|
||||
"qa",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
"Taylor Chen":{
|
||||
"shortname":"tc",
|
||||
"longname":"Taylor Chen",
|
||||
"username":"tchen",
|
||||
"email":"tchen@example.org",
|
||||
"ex":true,
|
||||
"groups":[
|
||||
"dev",
|
||||
"admin",
|
||||
"support"
|
||||
]
|
||||
},
|
||||
"Jordan Smithfield":{
|
||||
"shortname":"js",
|
||||
"longname":"Jordan Smithfield",
|
||||
"username":"jsmith",
|
||||
"email":"j.smithfield@test.net",
|
||||
"ex":false,
|
||||
"groups":[
|
||||
"marketing",
|
||||
"content",
|
||||
"social"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
name_short|Name|Username|email (opt: |ex) (opt: ;;group1 or ;;group1|group2|group3...)
|
||||
```
|
||||
opt explained:
|
||||
ex -> excludes the given author for all and negation selection commands
|
||||
group -> groups an author which can then be called as an argument to add all people from that group. An author can be a part of multiple groups
|
||||
|
||||
# Why?
|
||||
Co-authoring commits is a feature that is supported by github and gitlab and other git hosting services but creating the commits can be a bit of a pain. Co-authoring is extremely useful as teams can be much more transparent in who worked on what and it can be a great way to give credit to people who have helped on projects. This will make git-blame a lot more useful as you can quickly see who to contact or talk to about a specific part of the code. I strongly believe that this feature is underutilized and i attribute it mostly to the fact that is combersome to use. This tool aims to fix and streamline that process. (It even allows for automation of the process with the CLI mode)
|
||||
|
||||
+23
-3
@@ -10,9 +10,29 @@ import (
|
||||
"github.com/Slug-Boi/cocommit/src/cmd/utils"
|
||||
)
|
||||
|
||||
const author_data = `syntax for the test file
|
||||
te|testing|TestUser|test@test.test|ex
|
||||
ti|testtest|UserName2|testing@user.io;;gr1`
|
||||
const author_data = `
|
||||
{
|
||||
"Authors": {
|
||||
"testing": {
|
||||
"shortname": "te",
|
||||
"longname": "testing",
|
||||
"username": "TestUser",
|
||||
"email": "test@test.test",
|
||||
"ex": true,
|
||||
"groups": []
|
||||
},
|
||||
"testtest": {
|
||||
"shortname": "ti",
|
||||
"longname": "testtest",
|
||||
"username": "UserName2",
|
||||
"email": "testing@user.io",
|
||||
"ex": false,
|
||||
"groups": [
|
||||
"gr1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
var envVar = utils.Find_authorfile()
|
||||
|
||||
|
||||
+34
-17
@@ -4,6 +4,7 @@ package tui
|
||||
// from the Bubbles component library.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -249,29 +250,45 @@ func (m *model_ca) AddAuthor() {
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
sb := strings.Builder{}
|
||||
sb.WriteRune('\n')
|
||||
|
||||
sb.WriteString(fmt.Sprintf("%s|%s|%s|%s",
|
||||
m.inputs[0].Value(),
|
||||
m.inputs[1].Value(),
|
||||
m.inputs[2].Value(),
|
||||
m.inputs[3].Value()))
|
||||
|
||||
if m.exclude {
|
||||
sb.WriteString(fmt.Sprintf("|%s", "ex"))
|
||||
var groups []string
|
||||
if m.inputs[4].Value() == "" {
|
||||
groups = []string{}
|
||||
} else {
|
||||
groups = strings.Split(m.inputs[4].Value(), "|")
|
||||
}
|
||||
|
||||
if m.inputs[4].Value() != "" {
|
||||
sb.WriteString(fmt.Sprintf(";;%s", m.inputs[4].Value()))
|
||||
|
||||
|
||||
|
||||
// create and add the user to the users map
|
||||
usr := utils.User{
|
||||
Shortname: m.inputs[0].Value(),
|
||||
Longname: m.inputs[1].Value(),
|
||||
Username: m.inputs[2].Value(),
|
||||
Email: m.inputs[3].Value(),
|
||||
Ex: m.exclude,
|
||||
Groups: groups,
|
||||
}
|
||||
|
||||
//sb.WriteRune('\n')
|
||||
utils.Users[m.inputs[0].Value()] = usr
|
||||
utils.Users[m.inputs[1].Value()] = usr
|
||||
|
||||
|
||||
utils.Authors.Authors[m.inputs[1].Value()] = usr
|
||||
|
||||
data, err := json.MarshalIndent(utils.Authors, "", " ")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error marshalling json: %v", err))
|
||||
|
||||
if _, err = f.WriteString(sb.String()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// write the data to the file
|
||||
f.Truncate(0)
|
||||
f.Seek(0, 0)
|
||||
f.Write(data)
|
||||
f.Close()
|
||||
|
||||
// redefine the users map for the tui to use
|
||||
utils.Define_users(utils.Find_authorfile())
|
||||
|
||||
author := m.inputs[0].Value()
|
||||
|
||||
@@ -125,8 +125,7 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
for k, v := range dupProtect {
|
||||
if _, ok := selected[k]; !ok {
|
||||
for _, user := range users {
|
||||
split := strings.Split(user.Names, "/")
|
||||
if split[0] == v || split[1] == v {
|
||||
if user.Shortname == v || user.Longname == v {
|
||||
selectToggle(item(k))
|
||||
}
|
||||
}
|
||||
|
||||
+24
-4
@@ -12,9 +12,29 @@ import (
|
||||
"github.com/charmbracelet/x/exp/teatest"
|
||||
)
|
||||
|
||||
const author_data = `syntax for the test file
|
||||
te|testing|TestUser|test@test.test|ex;;gr0
|
||||
ti|testtest|UserName2|testing@user.io;;gr1`
|
||||
const author_data = `
|
||||
{
|
||||
"Authors": {
|
||||
"testing": {
|
||||
"shortname": "te",
|
||||
"longname": "testing",
|
||||
"username": "TestUser",
|
||||
"email": "test@test.test",
|
||||
"ex": true,
|
||||
"groups": []
|
||||
},
|
||||
"testtest": {
|
||||
"shortname": "ti",
|
||||
"longname": "testtest",
|
||||
"username": "UserName2",
|
||||
"email": "testing@user.io",
|
||||
"ex": false,
|
||||
"groups": [
|
||||
"gr1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
var envVar string
|
||||
|
||||
@@ -54,7 +74,7 @@ func TestShowUser(t *testing.T) {
|
||||
teatest.WithInitialTermSize(300, 300),
|
||||
)
|
||||
teatest.WaitFor(t, tm.Output(), func(bts []byte) bool {
|
||||
return bytes.Contains(bts, []byte("syntax for the test file"))
|
||||
return bytes.Contains(bts, []byte("\"Authors\": {"))
|
||||
}, teatest.WithCheckInterval(time.Millisecond*100), teatest.WithDuration(time.Second*2))
|
||||
|
||||
keyPress(tm, "q")
|
||||
|
||||
+2
-3
@@ -3,7 +3,6 @@ package cmd
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -34,8 +33,8 @@ func UsersCmd() *cobra.Command {
|
||||
seen_users := []utils.User{}
|
||||
user_sb := []string{}
|
||||
for name, usr := range utils.Users {
|
||||
if !slices.Contains(seen_users, usr) {
|
||||
user_sb = append(user_sb, utils.Users[name].Names+" ->"+" Username: "+usr.Username+" Email: "+usr.Email+"\n")
|
||||
if !utils.ContainsUser(seen_users, usr) {
|
||||
user_sb = append(user_sb, utils.Users[name].Shortname+"/"+utils.Users[name].Longname+" ->"+" Username: "+usr.Username+" Email: "+usr.Email+"\n")
|
||||
seen_users = append(seen_users, usr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -20,7 +18,7 @@ func Find_authorfile() string {
|
||||
fmt.Println("Error getting user config directory")
|
||||
os.Exit(2)
|
||||
}
|
||||
return (authors + "/cocommit/authors")
|
||||
return (authors + "/cocommit/authors.json")
|
||||
} else {
|
||||
return os.Getenv("author_file")
|
||||
}
|
||||
@@ -42,10 +40,12 @@ func CheckAuthorFile() string {
|
||||
cocommit_folder = strings.Join(parts[:len(parts)-1], "/")
|
||||
|
||||
// create the author file
|
||||
err := os.Mkdir(cocommit_folder, 0766)
|
||||
if err != nil {
|
||||
fmt.Println("Error creating directory: ", err, cocommit_folder)
|
||||
os.Exit(1)
|
||||
if _, dirErr := os.Stat(cocommit_folder); os.IsNotExist(dirErr) {
|
||||
err := os.Mkdir(cocommit_folder, 0766)
|
||||
if err != nil {
|
||||
fmt.Println("Error creating directory: ", err, cocommit_folder)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
file, err := os.Create(authorfile)
|
||||
if err != nil {
|
||||
@@ -56,10 +56,15 @@ func CheckAuthorFile() string {
|
||||
defer file.Close()
|
||||
|
||||
// write the header to the file
|
||||
file.WriteString("Syntax: name_short|Name|Username|email (opt: |ex) (opt: ;;group1|group2|group3...)\n")
|
||||
json_string :=
|
||||
`{
|
||||
"Authors": {
|
||||
}
|
||||
}`
|
||||
|
||||
file.Write([]byte(json_string))
|
||||
|
||||
fmt.Println("Author file created. To add authors please launch the TUI with -a and press 'C'")
|
||||
|
||||
} else {
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -71,47 +76,44 @@ func CheckAuthorFile() string {
|
||||
func DeleteOneAuthor(author string) {
|
||||
author_file := Find_authorfile()
|
||||
|
||||
if _, exists := Users[author]; !exists {
|
||||
fmt.Println("User not found")
|
||||
return
|
||||
}
|
||||
|
||||
// open author_file
|
||||
file, err := os.OpenFile(author_file, os.O_RDWR, 0644)
|
||||
file, err := os.OpenFile(author_file, os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
// create regex to capture author line
|
||||
regexp, err := regexp.Compile(fmt.Sprintf("^(.+\\|%s\\|.+|%s\\|.+\\|.+)$", author, author))
|
||||
if err != nil {
|
||||
fmt.Println("Error compiling regex: ", err)
|
||||
// check that users aren't empty
|
||||
if len(Users) < 1 {
|
||||
fmt.Println("No users to remove")
|
||||
return
|
||||
}
|
||||
|
||||
var b []byte
|
||||
buf := bytes.NewBuffer(b)
|
||||
usr := Users[author]
|
||||
|
||||
// create a scanner for the file
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
// write the header to the buffer
|
||||
scanner.Scan()
|
||||
buf.WriteString(scanner.Text() + "\n")
|
||||
|
||||
// check if author matches the regex and skip
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if regexp.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
buf.WriteString(line + "\n")
|
||||
// Remove the user from the Author struct (try both short and long name)
|
||||
delete(Authors.Authors, usr.Shortname)
|
||||
delete(Authors.Authors, usr.Longname)
|
||||
|
||||
// marshal the struct back to json
|
||||
data, err := json.MarshalIndent(Authors, "", " ")
|
||||
if err != nil {
|
||||
fmt.Println("Error marshalling json: ", err)
|
||||
return
|
||||
}
|
||||
// remove the last newline character
|
||||
buf.Truncate(buf.Len() - 1)
|
||||
|
||||
|
||||
// write the data to the file
|
||||
file.Truncate(0)
|
||||
file.Seek(0, 0)
|
||||
buf.WriteTo(file)
|
||||
file.Write(data)
|
||||
file.Close()
|
||||
|
||||
RemoveUser(author)
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ func add_x_users(excludeMode []string) {
|
||||
// helper function to select groups of users to exclude in the commit message
|
||||
func group_selection(group []User, excludeMode []string) []string {
|
||||
for _, user := range Users {
|
||||
if !(slices.Contains(group, user)) {
|
||||
if !(ContainsUser(group, user)) {
|
||||
excludeMode = append(excludeMode, user.Username)
|
||||
}
|
||||
}
|
||||
|
||||
+47
-56
@@ -1,84 +1,80 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"slices"
|
||||
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// This util file is used to handle users and their information
|
||||
|
||||
type User struct {
|
||||
Username string
|
||||
Email string
|
||||
Names string
|
||||
Shortname string `json:"shortname"`
|
||||
Longname string `json:"longname"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Ex bool `json:"ex"`
|
||||
Groups []string `json:"groups"`
|
||||
}
|
||||
|
||||
type Author struct {
|
||||
Authors map[string]User
|
||||
}
|
||||
|
||||
// purely used for editing the author file later
|
||||
var Authors = Author{}
|
||||
|
||||
var Users = map[string]User{}
|
||||
var DefExclude = []string{}
|
||||
var Groups = map[string][]User{}
|
||||
|
||||
func ContainsUser(users []User, user User) bool {
|
||||
return slices.ContainsFunc(users, func(u User) bool {
|
||||
return u.Shortname == user.Shortname &&
|
||||
u.Longname == user.Longname &&
|
||||
u.Username == user.Username &&
|
||||
u.Email == user.Email &&
|
||||
u.Ex == user.Ex &&
|
||||
slices.Equal(u.Groups, user.Groups)
|
||||
})
|
||||
}
|
||||
|
||||
func Define_users(author_file string) {
|
||||
// wipe the users map
|
||||
Users = map[string]User{}
|
||||
DefExclude = []string{}
|
||||
Groups = map[string][]User{}
|
||||
|
||||
file, err := os.Open(author_file)
|
||||
var auth Author
|
||||
|
||||
data, err := os.ReadFile(author_file)
|
||||
if err != nil {
|
||||
print("File not found")
|
||||
fmt.Println("Error reading author file: ", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
err = json.Unmarshal(data, &auth)
|
||||
if err != nil {
|
||||
fmt.Println("Error unmarshalling json: ", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
Authors = auth
|
||||
|
||||
// eat a single input
|
||||
scanner.Scan()
|
||||
for _, usr := range auth.Authors {
|
||||
Users[usr.Shortname] = usr
|
||||
Users[usr.Longname] = usr
|
||||
if usr.Ex {
|
||||
DefExclude = append(DefExclude, usr.Shortname)
|
||||
}
|
||||
|
||||
// reads the input of authors file and formats accordingly
|
||||
for scanner.Scan() {
|
||||
input_str := scanner.Text()
|
||||
group_info := []string{}
|
||||
if strings.Contains(input_str, ";;") {
|
||||
input := strings.Split(input_str, ";;")
|
||||
input_str = input[0]
|
||||
group_info = append(group_info, strings.Split(input[1], "|")...)
|
||||
}
|
||||
info := strings.Split(input_str, "|")
|
||||
if len(info) < 4 {
|
||||
if len(info) > 0 {
|
||||
if info[0] == "" {
|
||||
info[0] = "(empty string)"
|
||||
}
|
||||
fmt.Println("Error: User", info[0], "is missing information")
|
||||
} else {
|
||||
fmt.Println("Error: Some user is missing information")
|
||||
}
|
||||
fmt.Println("Please check the author file for proper syntax")
|
||||
if input_str == "" {
|
||||
fmt.Println("empty line found in author file")
|
||||
} else {
|
||||
fmt.Println("author file input:", input_str)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
usr := User{Username: info[2], Email: info[3], Names: info[0] + "/" + info[1]}
|
||||
Users[info[0]] = usr
|
||||
Users[info[1]] = usr
|
||||
// Adds users with the ex tag to the defExclude list
|
||||
if len(info) == 5 {
|
||||
if info[4] == "ex" {
|
||||
DefExclude = append(DefExclude, info[2])
|
||||
}
|
||||
}
|
||||
group_info := usr.Groups
|
||||
if len(group_info) > 0 {
|
||||
// Group assignment
|
||||
for _, group := range group_info {
|
||||
if Groups[group] == nil {
|
||||
Groups[group] = []User{usr}
|
||||
} else {
|
||||
//TODO: Try and find a cleaner way of doing this
|
||||
usr_lst := Groups[group]
|
||||
usr_lst = append(usr_lst, usr)
|
||||
Groups[group] = usr_lst
|
||||
@@ -86,17 +82,12 @@ func Define_users(author_file string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveUser(short string) {
|
||||
usr := Users[short]
|
||||
split := strings.Split(usr.Names, "/")
|
||||
delete(Users, split[0])
|
||||
delete(Users, split[1])
|
||||
delete(Users, usr.Shortname)
|
||||
delete(Users, usr.Longname)
|
||||
}
|
||||
|
||||
func TempAddUser(username, email string) {
|
||||
|
||||
@@ -6,9 +6,29 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const author_data = `syntax for the test file
|
||||
te|testing|TestUser|test@test.test|ex
|
||||
ti|testtest|UserName2|testing@user.io;;gr1`
|
||||
const author_data = `
|
||||
{
|
||||
"Authors": {
|
||||
"testing": {
|
||||
"shortname": "te",
|
||||
"longname": "testing",
|
||||
"username": "TestUser",
|
||||
"email": "test@test.test",
|
||||
"ex": true,
|
||||
"groups": []
|
||||
},
|
||||
"testtest": {
|
||||
"shortname": "ti",
|
||||
"longname": "testtest",
|
||||
"username": "UserName2",
|
||||
"email": "testing@user.io",
|
||||
"ex": false,
|
||||
"groups": [
|
||||
"gr1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
var envVar = os.Getenv("author_file")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user