Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 239 additions & 0 deletions cmd/cli/cm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
/*
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
*/
package cli

import (
"errors"
"fmt"
"os"
"os/exec"
"regexp"

"github.com/Tomelin/go-convetional-commit/internal/entity"
"github.com/Tomelin/go-convetional-commit/internal/service"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
)

type commitStruct struct{}

// cmCmd represents the cm command
var cmCmd = &cobra.Command{
Use: "cm",
Short: "Create a new commit and push",
Long: `
cli is a tool to create conventional commits
You can create a commit with a message and a body message
You can also create a commit with a task id and a task status
You can also create a commit with an emoji in the message
You can also create a commit with a type of commit
You can also create a commit in interactive mode
You can also push the commit to the remote repository
You can also reset the commit to the head
`,
Run: func(cmd *cobra.Command, args []string) {

cStruct := commitStruct{}
if err := cStruct.checkGitCommand(); err != nil {
fmt.Println(err)
os.Exit(0)
}

Check warning on line 41 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L35-L41

Added lines #L35 - L41 were not covered by tests

shortDesc, _ := cmd.Flags().GetBool("short")

interactive, _ := cmd.Flags().GetBool("interactive")
if interactive {
interactive, err := cStruct.interactiveMode(shortDesc)
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}

Check warning on line 51 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L43-L51

Added lines #L43 - L51 were not covered by tests

sc := service.NewServiceCommit()
err = sc.Commit(interactive)
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}

Check warning on line 58 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L53-L58

Added lines #L53 - L58 were not covered by tests

} else {
cStruct.nonInteractiveMode()
}

Check warning on line 62 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L60-L62

Added lines #L60 - L62 were not covered by tests

push, _ := cmd.Flags().GetBool("push")
if push {
sc := service.NewServiceCommit()
err := sc.Push()
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}

Check warning on line 71 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L64-L71

Added lines #L64 - L71 were not covered by tests
}

reset, _ := cmd.Flags().GetUint("reset")
if reset > 0 {

sc := service.NewServiceCommit()

err := sc.Reset(uint(reset))
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}

Check warning on line 83 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L74-L83

Added lines #L74 - L83 were not covered by tests
}
},
}

func init() {
rootCmd.AddCommand(cmCmd)
cmCmd.Flags().BoolP("push", "p", false, "Push the commit")
cmCmd.Flags().UintP("reset", "r", 0, "Number of head to reaset")
cmCmd.Flags().StringP("subject", "s", "", "Subject message of commit")
cmCmd.Flags().String("type", "", "Commit type (feat, fix, chore, docs, style, refactor, perf, test, ci, build, revert)")
cmCmd.Flags().String("taskId", "", "Task id")
cmCmd.Flags().String("body", "", "Body message of commit")
cmCmd.Flags().String("emoji", "", "Put emoji in commit message")
cmCmd.Flags().BoolP("short", "c", false, "Short description")

Check warning on line 97 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L88-L97

Added lines #L88 - L97 were not covered by tests
}

func (c *commitStruct) interactiveMode(short bool) (*entity.Commit, error) {

opts := entity.Commit{}

// STARTS Commit type
items := opts.Option.OptionsList()

prompt := promptui.Select{
Label: "Select kind of commit",
Items: items,
IsVimMode: false,
}

_, result, err := prompt.Run()
if result == "" {
return nil, errors.New("please select a commit type")
}

Check warning on line 116 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L100-L116

Added lines #L100 - L116 were not covered by tests

if err != nil {
return nil, fmt.Errorf("select prompt failed %v", err)
}

Check warning on line 120 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L118-L120

Added lines #L118 - L120 were not covered by tests

opts.Option = opts.Option.FromString(result)
opts.Choice = result
// ENDS Commit type

// STARTS Commit message
validate := func(input string) error {
if input == "" {
return errors.New("commit message is required")
}

Check warning on line 130 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L122-L130

Added lines #L122 - L130 were not covered by tests

if len(input) > 40 {
return errors.New("commit message must be at most 40 characters long")
}

Check warning on line 134 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L132-L134

Added lines #L132 - L134 were not covered by tests

matched, err := regexp.MatchString(`^[a-zA-Z][a-zA-Z0-9].{0,39}$`, input)
if err != nil {
return err
}
if !matched {
return errors.New("commit message must start with a letter and be at most 40 characters long")
}

Check warning on line 142 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L136-L142

Added lines #L136 - L142 were not covered by tests

return nil

Check warning on line 144 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L144

Added line #L144 was not covered by tests
}

prompt2 := promptui.Prompt{
Label: "Write the commit message",
Validate: validate,
}

commitMessage, err := prompt2.Run()
if commitMessage == "" {
return nil, errors.New("commit message is required")
}

Check warning on line 155 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L147-L155

Added lines #L147 - L155 were not covered by tests

if err != nil {
return nil, fmt.Errorf("input prompt failed %v", err)
}
opts.Subject = commitMessage
// ENDS Commit message

// STARTS body message
commitBody := promptui.Prompt{
Label: "write a message in the commit body",
}

commitBodyResult, err := commitBody.Run()
if err != nil {
return nil, fmt.Errorf("input prompt failed %v", err)
}
opts.Comment = commitBodyResult
// ENDS body message

// Long description
if short {
return &opts, nil
}

Check warning on line 178 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L157-L178

Added lines #L157 - L178 were not covered by tests

// STARTS task status
promptStatus := promptui.Select{
Label: "Select task status",
Items: opts.StatusType.StatusList(),
IsVimMode: false,
}

_, result, err = promptStatus.Run()
if err != nil {
return nil, fmt.Errorf("select prompt failed %v", err)
}
opts.StatusType = opts.StatusType.FromString(result)
// ENDS task status

// STARTS task id
taskID := promptui.Prompt{
Label: "Write the task id",
}

taskIdMessage, err := taskID.Run()
if err != nil {
return nil, fmt.Errorf("input prompt failed %v", err)
}
opts.TaskID = taskIdMessage
// ENDS task id

// STARTS enable emoji
emoji := promptui.Select{
Label: "Enable emoji in commit message? (y/n)",
Items: opts.EnableEmoji(),
}

_, enabledEmoji, err := emoji.Run()
if err != nil {
return nil, fmt.Errorf("input prompt failed %v", err)
}

Check warning on line 215 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L181-L215

Added lines #L181 - L215 were not covered by tests

if enabledEmoji == "true" {
opts.Emoji = opts.Option.Emoji()
}

Check warning on line 219 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L217-L219

Added lines #L217 - L219 were not covered by tests
// ENDS enable emoji

if err := opts.Validate(); err != nil {
return nil, err
}

Check warning on line 224 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L222-L224

Added lines #L222 - L224 were not covered by tests

return &opts, nil

Check warning on line 226 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L226

Added line #L226 was not covered by tests
}

func (c *commitStruct) nonInteractiveMode() {
fmt.Println("Non interactive mode")

Check warning on line 230 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L229-L230

Added lines #L229 - L230 were not covered by tests
}

func (c *commitStruct) checkGitCommand() error {
if _, err := exec.LookPath("git"); err != nil {
return errors.New("git is not installed")
}

Check warning on line 236 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L233-L236

Added lines #L233 - L236 were not covered by tests

return nil

Check warning on line 238 in cmd/cli/cm.go

View check run for this annotation

Codecov / codecov/patch

cmd/cli/cm.go#L238

Added line #L238 was not covered by tests
}
Loading