mirror of
https://github.com/Slug-Boi/cocommit.git
synced 2026-05-13 12:45:47 +00:00
Merge pull request #65 from Slug-Boi/feat_github_profile_create
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Slug-Boi/cocommit/src/cmd/tui"
|
||||
"github.com/Slug-Boi/cocommit/src/cmd/utils"
|
||||
//"github.com/charmbracelet/lipgloss"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ghProfileCmd represents the ghProfile command
|
||||
func GHCmd () *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "gh <github username>",
|
||||
Short: "This command will create add a github profile to your author list for use in commits",
|
||||
Long: `This command will create add a github profile to your author list.
|
||||
You just have to run the command with a username of the github profile you want to add.
|
||||
The email will be added manually by following the TUI or adding the email flag to the command.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
email, _ := cmd.Flags().GetString("email")
|
||||
shortname, _ := cmd.Flags().GetString("shortname")
|
||||
longname, _ := cmd.Flags().GetString("longname")
|
||||
username, _ := cmd.Flags().GetString("username")
|
||||
groups, _ := cmd.Flags().GetStringSlice("groups")
|
||||
exclude, _ := cmd.Flags().GetBool("exclude")
|
||||
|
||||
user := utils.FetchGithubProfile(args[0])
|
||||
|
||||
// Update values if flags are set
|
||||
if shortname != "" {
|
||||
user.Shortname = shortname
|
||||
}
|
||||
if longname != "" {
|
||||
user.Longname = longname
|
||||
}
|
||||
if username != "" {
|
||||
user.Username = username
|
||||
}
|
||||
if len(groups) > 0 {
|
||||
user.Groups = groups
|
||||
}
|
||||
if exclude {
|
||||
user.Ex = true
|
||||
}
|
||||
|
||||
if email != "" {
|
||||
user.Email = email
|
||||
if utils.CheckUserFields(user) {
|
||||
utils.CreateAuthor(user)
|
||||
// print sucess message
|
||||
//fmt.Print(lipgloss.NewStyle().Foreground(lipgloss.Color("170")).Render("Author added successfully"))
|
||||
fmt.Print("Author added successfully\n")
|
||||
} else {
|
||||
panic("Invalid author data")
|
||||
}
|
||||
} else {
|
||||
// run the TUI to get the email
|
||||
tui.EntryGHAuthorModel(user)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
ghCmd := GHCmd()
|
||||
rootCmd.AddCommand(ghCmd)
|
||||
ghCmd.Flags().StringP("email", "@", "", "Email to be used for the author")
|
||||
ghCmd.Flags().StringP("longname", "n", "", "Name to be used for the author")
|
||||
ghCmd.Flags().StringP("username", "u", "", "Username to be used for the author")
|
||||
ghCmd.Flags().StringP("shortname", "s", "", "Shortname to be used for the author")
|
||||
ghCmd.Flags().BoolP("exclude", "e", false, "Exclude the author from the list of authors")
|
||||
ghCmd.Flags().StringSliceP("groups", "g", []string{}, "Groups to add the author to")
|
||||
}
|
||||
+183
-43
@@ -4,7 +4,6 @@ package tui
|
||||
// from the Bubbles component library.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -14,7 +13,6 @@ import (
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
//"github.com/inancgumus/screen"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -36,6 +34,63 @@ type model_ca struct {
|
||||
inputs []textinput.Model
|
||||
quitting bool
|
||||
exclude bool
|
||||
errorModel *errorModel
|
||||
}
|
||||
|
||||
// Error popup model
|
||||
type errorModel struct {
|
||||
missing []string
|
||||
visible bool
|
||||
}
|
||||
|
||||
func errorGetMissingFields(m model_ca) {
|
||||
inpLen := len(m.inputs)
|
||||
if !tempAuthorToggle {
|
||||
inpLen -= 1
|
||||
}
|
||||
|
||||
if len(m.inputs) > 0 {
|
||||
for i := 0; i < inpLen; i++ {
|
||||
if m.inputs[i].Value() == "" {
|
||||
m.errorModel.missing = append(m.errorModel.missing, "- "+strings.Split(m.inputs[i].Placeholder,"(")[0])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m.errorModel.missing = append(m.errorModel.missing, "GIGA ERROR NO INPUTS")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (e errorModel) View() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("Error")
|
||||
if len(e.missing) > 0 {
|
||||
sb.WriteString("\nMissing fields: \n")
|
||||
sb.WriteString(strings.Join(e.missing, "\n"))
|
||||
}
|
||||
|
||||
// Create centered content
|
||||
content := lipgloss.JoinVertical(
|
||||
lipgloss.Left, // Changed from Center to Left for better alignment
|
||||
sb.String(),
|
||||
"\n\n[enter/esc]",
|
||||
|
||||
)
|
||||
|
||||
// Create the error box
|
||||
errorBox := lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("9")).
|
||||
Padding(1, 2).
|
||||
Width(40).
|
||||
Foreground(lipgloss.Color("9")).
|
||||
Background(lipgloss.Color("0")).
|
||||
Align(lipgloss.Center).
|
||||
Render(content)
|
||||
|
||||
return lipgloss.NewStyle().
|
||||
Padding(1, 0).
|
||||
Render(errorBox)
|
||||
}
|
||||
|
||||
var parent_m *model
|
||||
@@ -45,13 +100,13 @@ func createAuthorModel(old_m *model) model_ca {
|
||||
|
||||
m := model_ca{
|
||||
inputs: make([]textinput.Model, 5),
|
||||
errorModel: intitialErrorModel(),
|
||||
}
|
||||
|
||||
var t textinput.Model
|
||||
for i := range m.inputs {
|
||||
t = textinput.New()
|
||||
t.Cursor.Style = cursorStyle
|
||||
//t.CharLimit = 32
|
||||
|
||||
switch i {
|
||||
case 0:
|
||||
@@ -75,11 +130,71 @@ func createAuthorModel(old_m *model) model_ca {
|
||||
return m
|
||||
}
|
||||
|
||||
func intitialErrorModel() *errorModel {
|
||||
return &errorModel{
|
||||
missing: []string{},
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
|
||||
func createGHAuthorModel(old_m *model, user utils.User) model_ca {
|
||||
parent_m = old_m
|
||||
|
||||
m := model_ca{
|
||||
inputs: make([]textinput.Model, 5),
|
||||
errorModel: intitialErrorModel(),
|
||||
}
|
||||
|
||||
var t textinput.Model
|
||||
for i := range m.inputs {
|
||||
t = textinput.New()
|
||||
t.Cursor.Style = cursorStyle
|
||||
|
||||
switch i {
|
||||
case 0:
|
||||
t.Placeholder = "Shortname (e.g. jo)"
|
||||
t.SetValue(user.Shortname)
|
||||
t.Focus()
|
||||
t.PromptStyle = focusedStyle
|
||||
t.TextStyle = focusedStyle
|
||||
case 1:
|
||||
t.Placeholder = "Long name (e.g. JohnDoe)"
|
||||
t.SetValue(user.Longname)
|
||||
case 2:
|
||||
t.Placeholder = "Username (e.g. JohnDoe-gh)"
|
||||
t.SetValue(user.Username)
|
||||
case 3:
|
||||
t.Placeholder = "Email (e.g. JohnDoe@domain.do"
|
||||
t.SetValue("")
|
||||
case 4:
|
||||
t.Placeholder = "Group tags (e.g. gr1|gr2)"
|
||||
t.SetValue(strings.Join(user.Groups, "|"))
|
||||
}
|
||||
|
||||
m.inputs[i] = t
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func EntryGHAuthorModel(user utils.User) {
|
||||
model := createGHAuthorModel(&model{}, user)
|
||||
|
||||
print(model.inputs[0].Value())
|
||||
|
||||
if _, err := tea.NewProgram(model).Run(); err != nil {
|
||||
fmt.Println("Error running program:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func tempAuthorModel(old_m *model) model_ca {
|
||||
parent_m = old_m
|
||||
|
||||
m := model_ca{
|
||||
inputs: make([]textinput.Model, 2),
|
||||
errorModel: intitialErrorModel(),
|
||||
}
|
||||
|
||||
var t textinput.Model
|
||||
@@ -110,13 +225,35 @@ func (m model_ca) Init() tea.Cmd {
|
||||
return textinput.Blink
|
||||
}
|
||||
|
||||
func updateErrorPopup(m model_ca, msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "enter", "esc", "ctrl+c":
|
||||
m.errorModel.missing = []string{}
|
||||
m.errorModel.visible = false
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m model_ca) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.errorModel.visible {
|
||||
return updateErrorPopup(m, msg)
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "esc":
|
||||
m.inputs = nil
|
||||
return nil, nil
|
||||
if parent_m.keys != nil {
|
||||
return nil, nil
|
||||
}
|
||||
return m, tea.Quit
|
||||
|
||||
// Set focus to next input
|
||||
case "tab", "shift+tab", "enter", "up", "down":
|
||||
@@ -126,8 +263,17 @@ func (m model_ca) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if !tempAuthorToggle {
|
||||
if s == "enter" && m.focusIndex == len(m.inputs)+1 {
|
||||
m.quitting = true
|
||||
m.AddAuthor()
|
||||
return model{list: parent_m.list}, tea.ClearScreen
|
||||
m.errorModel.visible = m.AddAuthor()
|
||||
if m.errorModel.visible {
|
||||
m.quitting = false
|
||||
return m, nil
|
||||
}
|
||||
if parent_m.keys != nil {
|
||||
return model{list: parent_m.list}, tea.ClearScreen
|
||||
} else {
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
} else if s == "enter" && m.focusIndex == len(m.inputs) {
|
||||
// toggle exclude
|
||||
m.exclude = !m.exclude
|
||||
@@ -136,8 +282,17 @@ func (m model_ca) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
} else {
|
||||
if s == "enter" && m.focusIndex == len(m.inputs) {
|
||||
m.quitting = true
|
||||
m.TempAddAuthor()
|
||||
return model{list: parent_m.list}, tea.ClearScreen
|
||||
m.errorModel.visible = m.TempAddAuthor()
|
||||
if m.errorModel.visible {
|
||||
m.quitting = false
|
||||
return m, nil
|
||||
}
|
||||
if parent_m.keys != nil {
|
||||
return model{list: parent_m.list}, tea.ClearScreen
|
||||
} else {
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +347,13 @@ func (m *model_ca) updateInputs(msg tea.Msg) tea.Cmd {
|
||||
}
|
||||
|
||||
func (m model_ca) View() string {
|
||||
if m.errorModel.visible {
|
||||
if len(m.errorModel.missing) == 0 {
|
||||
errorGetMissingFields(m)
|
||||
}
|
||||
return m.errorModel.View()
|
||||
}
|
||||
|
||||
if m.quitting {
|
||||
return ""
|
||||
}
|
||||
@@ -237,19 +399,13 @@ func (m model_ca) View() string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m *model_ca) AddAuthor() {
|
||||
func (m *model_ca) AddAuthor() bool {
|
||||
if len(m.inputs) > 0 &&
|
||||
m.inputs[0].Value() != "" &&
|
||||
m.inputs[1].Value() != "" &&
|
||||
m.inputs[2].Value() != "" &&
|
||||
m.inputs[3].Value() != "" {
|
||||
author_file := utils.Find_authorfile()
|
||||
f, err := os.OpenFile(author_file, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
var groups []string
|
||||
if m.inputs[4].Value() == "" {
|
||||
groups = []string{}
|
||||
@@ -257,9 +413,6 @@ func (m *model_ca) AddAuthor() {
|
||||
groups = strings.Split(m.inputs[4].Value(), "|")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// create and add the user to the users map
|
||||
usr := utils.User{
|
||||
Shortname: m.inputs[0].Value(),
|
||||
@@ -270,42 +423,29 @@ func (m *model_ca) AddAuthor() {
|
||||
Groups: groups,
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
}
|
||||
|
||||
// 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())
|
||||
utils.CreateAuthor(usr)
|
||||
|
||||
author := m.inputs[0].Value()
|
||||
|
||||
item_str := utils.Users[author].Username + " - " + utils.Users[author].Email
|
||||
dupProtect[item_str] = author
|
||||
parent_m.list.InsertItem(len(parent_m.list.Items())+1, item(item_str))
|
||||
|
||||
if parent_m.keys != nil {
|
||||
item_str := utils.Users[author].Username + " - " + utils.Users[author].Email
|
||||
dupProtect[item_str] = author
|
||||
parent_m.list.InsertItem(len(parent_m.list.Items())+1, item(item_str))
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *model_ca) TempAddAuthor() {
|
||||
func (m *model_ca) TempAddAuthor() bool {
|
||||
if len(m.inputs) > 1 && m.inputs[0].Value() != "" && m.inputs[1].Value() != "" {
|
||||
item_str := m.inputs[0].Value() + " - " + m.inputs[1].Value()
|
||||
dupProtect[item_str] = m.inputs[0].Value() + ":" + m.inputs[1].Value()
|
||||
i := item(item_str)
|
||||
parent_m.list.InsertItem(len(parent_m.list.Items())+1, item(item_str))
|
||||
selectToggle(i)
|
||||
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -73,6 +73,38 @@ func CheckAuthorFile() string {
|
||||
return authorfile
|
||||
}
|
||||
|
||||
func CreateAuthor(user User) {
|
||||
Users[user.Shortname] = user
|
||||
Users[user.Longname] = user
|
||||
|
||||
// Specifically for the json file
|
||||
Authors.Authors[user.Longname] = user
|
||||
|
||||
data, err := json.MarshalIndent(Authors, "", " ")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error marshalling json: %v", err))
|
||||
|
||||
}
|
||||
|
||||
// open author_file
|
||||
author_file := Find_authorfile()
|
||||
f, err := os.OpenFile(author_file, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
// 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
|
||||
Define_users(Find_authorfile())
|
||||
}
|
||||
|
||||
func DeleteOneAuthor(author string) {
|
||||
author_file := Find_authorfile()
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type GithubProfile struct {
|
||||
Login string `json:"login"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func FetchGithubProfile(username string) User {
|
||||
// Fetch the github profile and create a user with everything except the email
|
||||
|
||||
url := fmt.Sprintf("https://api.github.com/users/%s", username)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
panic(fmt.Sprint("Error fetching github profile: ", err))
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Parse the response and create a user
|
||||
var profile GithubProfile
|
||||
err = json.NewDecoder(resp.Body).Decode(&profile)
|
||||
if err != nil {
|
||||
panic(fmt.Sprint("Error parsing github profile: ", err))
|
||||
}
|
||||
|
||||
// Create a user with the github profile
|
||||
return User{
|
||||
Shortname: strings.ToLower(profile.Name[:2]),
|
||||
Longname: profile.Name,
|
||||
Username: profile.Login,
|
||||
Email: "",
|
||||
Ex: false,
|
||||
Groups: []string{},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -41,6 +41,13 @@ func ContainsUser(users []User, user User) bool {
|
||||
})
|
||||
}
|
||||
|
||||
func CheckUserFields(user User) bool {
|
||||
if user.Shortname == "" || user.Longname == "" || user.Username == "" || user.Email == "" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func Define_users(author_file string) {
|
||||
// wipe the users map
|
||||
Users = map[string]User{}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"github.com/Slug-Boi/cocommit/src/cmd/utils"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Slug-Boi/cocommit/src/cmd/utils"
|
||||
)
|
||||
|
||||
const author_data = `
|
||||
@@ -77,6 +79,45 @@ func Test_DeleteAuthor(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_CreateAuthor(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
|
||||
// Test CreateAuthor
|
||||
author := utils.User{
|
||||
Shortname: "epic",
|
||||
Longname: "Test",
|
||||
Username: "TestUser",
|
||||
Email: "bestemailever@github.io",
|
||||
Ex: false,
|
||||
Groups: []string{"test"},
|
||||
}
|
||||
utils.CreateAuthor(author)
|
||||
// Check if author was added
|
||||
_, ok := utils.Users["epic"]
|
||||
if !ok {
|
||||
t.Errorf("CreateAuthor() did not add author")
|
||||
}
|
||||
|
||||
// Check if author was added to the file
|
||||
author_file := utils.Find_authorfile()
|
||||
author_data, err := os.ReadFile(author_file)
|
||||
if err != nil {
|
||||
t.Errorf("Error reading file: %v", err)
|
||||
}
|
||||
|
||||
//unmarshal the data
|
||||
var authors utils.Author
|
||||
err = json.Unmarshal(author_data, &authors)
|
||||
if err != nil {
|
||||
t.Errorf("Error unmarshalling file: %v", err)
|
||||
}
|
||||
if authors.Authors["Test"].Shortname != "epic" {
|
||||
t.Errorf("CreateAuthor() did not add author to file: %v", authors.Authors)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Author tests END
|
||||
|
||||
// User tests BEGIN
|
||||
@@ -140,3 +181,31 @@ func Test_Commit(t* testing.T) {
|
||||
}
|
||||
}
|
||||
// Commit tests END
|
||||
|
||||
// Github tests BEGIN
|
||||
func Test_FetchGHProfile(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
// Test FetchGithubProfile
|
||||
profile := utils.FetchGithubProfile("Slug-Boi")
|
||||
if profile.Username != "Slug-Boi" {
|
||||
t.Errorf("FetchGithubProfile() = %v; want Slug-Boi", profile.Username)
|
||||
}
|
||||
if profile.Email != "" {
|
||||
t.Errorf("FetchGithubProfile() = %v; want empty email", profile.Email)
|
||||
}
|
||||
if profile.Shortname != "th" {
|
||||
t.Errorf("FetchGithubProfile() = %v; want th", profile.Shortname)
|
||||
}
|
||||
if profile.Longname != "Theis" {
|
||||
t.Errorf("FetchGithubProfile() = %v; want Theis", profile.Longname)
|
||||
}
|
||||
if profile.Ex != false {
|
||||
t.Errorf("FetchGithubProfile() = %v; want false", profile.Ex)
|
||||
}
|
||||
if len(profile.Groups) != 0 {
|
||||
t.Errorf("FetchGithubProfile() = %v; want 0", len(profile.Groups))
|
||||
}
|
||||
}
|
||||
// Github tests END
|
||||
|
||||
|
||||
Reference in New Issue
Block a user