diff --git a/cmd/config.go b/cmd/config.go index 2386dfc..e4c03f0 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -6,9 +6,10 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" - "github.com/cocoide/commitify/internal/entity" "github.com/fatih/color" "github.com/spf13/cobra" + + "github.com/cocoide/commitify/internal/entity" ) var ( @@ -27,7 +28,7 @@ var ( } ) -type configModel struct { +type configCmdModel struct { configKeyIndex int configOptionIndex int configKeySelected bool @@ -35,21 +36,21 @@ type configModel struct { textInput textinput.Model } -func initConfigModel() configModel { +func initConfigModel() configCmdModel { ti := textinput.New() ti.Focus() - return configModel{ + return configCmdModel{ textInput: ti, err: nil, } } -func (cm configModel) Init() tea.Cmd { +func (cm configCmdModel) Init() tea.Cmd { return textinput.Blink } -func (cm configModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (cm configCmdModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch cm.configKeySelected { // 設定項目を選択する case false: @@ -82,7 +83,7 @@ func (cm configModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: switch msg.Type { case tea.KeyEnter: - saveConfig(cm) + entity.SaveConfig(cm.configKeyIndex, -1, cm.textInput.Value()) return cm, tea.Quit case tea.KeyCtrlC, tea.KeyEsc: return cm, tea.Quit @@ -109,7 +110,7 @@ func (cm configModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cm.configOptionIndex++ } case tea.KeyEnter: - saveConfig(cm) + entity.SaveConfig(cm.configKeyIndex, configOption[cm.configKeyIndex][cm.configOptionIndex], "") return cm, tea.Quit case tea.KeyCtrlC, tea.KeyEsc: return cm, tea.Quit @@ -121,7 +122,7 @@ func (cm configModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return cm, nil } -func (cm configModel) View() string { +func (cm configCmdModel) View() string { var b strings.Builder switch cm.configKeySelected { @@ -180,26 +181,3 @@ var configCmd = &cobra.Command{ func init() { rootCmd.AddCommand(configCmd) } - -func saveConfig(cm configModel) { - currentConfig, err := entity.ReadConfig() - if err != nil { - fmt.Println(err) - } - - switch cm.configKeyIndex { - case 0: - currentConfig.ChatGptApiKey = cm.textInput.Value() - case 1: - currentConfig.UseLanguage = configOption[cm.configKeyIndex][cm.configOptionIndex] - case 2: - currentConfig.CommitFormat = configOption[cm.configKeyIndex][cm.configOptionIndex] - case 3: - currentConfig.AISource = configOption[cm.configKeyIndex][cm.configOptionIndex] - } - - err = entity.WriteConfig(currentConfig) - if err != nil { - fmt.Println(err) - } -} diff --git a/cmd/docs.go b/cmd/docs.go index 7e34f08..09fb5f4 100644 --- a/cmd/docs.go +++ b/cmd/docs.go @@ -17,7 +17,9 @@ var docsCmd = &cobra.Command{ Short: "Document of commitify", Run: func(cmd *cobra.Command, args []string) { b, _ := static.Logo.ReadFile("logo.txt") - fmt.Print(color.CyanString(string(b)) + "\n\n ・Languageは日本語と英語が選択できます\n\n ・CodeFormatはPrefix (例: feat: A)とEmoji (例: 🐛 Bugix), Normal (例: Feat A)が選べます") + fmt.Println(color.CyanString(string(b))) + fmt.Println("\n ・Languageは日本語と英語が選択できます") + fmt.Println(" ・CodeFormatはPrefix (例: feat: A)とEmoji (例: 🐛 Bugix), Normal (例: Feat A)が選べます") }, } diff --git a/cmd/suggest.go b/cmd/suggest.go index 1ae9c60..13d6e16 100644 --- a/cmd/suggest.go +++ b/cmd/suggest.go @@ -10,20 +10,18 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/cocoide/commitify/internal/entity" - "github.com/cocoide/commitify/internal/gateway" - "github.com/cocoide/commitify/util" "github.com/fatih/color" "github.com/spf13/cobra" + + "github.com/cocoide/commitify/internal/service" ) var ( textStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("252")).Render spinnerStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("69")) - helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241")).Render ) -type model struct { +type suggestModel struct { choices []string currentIdx int errorMsg string @@ -31,101 +29,83 @@ type model struct { isEditing bool spinner spinner.Model textInput textinput.Model + scs *service.SuggestCmdService } -func (m *model) Init() tea.Cmd { - conf, err := entity.ReadConfig() - if err != nil { - log.Fatal("設定情報の取得に失敗: ", err) - } - - var gi gateway.GatewayInterface - switch conf.AISource { - case int(entity.WrapServer): - gi = gateway.NewGrpcServeGateway() - default: - gi = gateway.NewGrpcServeGateway() - } - +func (sm *suggestModel) Init() tea.Cmd { go func() { - messages, err := gi.FetchCommitMessages() + messages, err := sm.scs.GenerateCommitMessages() if err != nil { log.Fatal("コミットメッセージの生成に失敗: ", err) os.Exit(-1) } - m.choices = messages - m.isLoading = false + sm.choices = messages + sm.isLoading = false }() return textinput.Blink } -func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (sm *suggestModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd - m.textInput, cmd = m.textInput.Update(msg) + sm.textInput, cmd = sm.textInput.Update(msg) switch msg := msg.(type) { case tea.KeyMsg: switch msg.Type { case tea.KeyTab: - m.isEditing = true - m.textInput.Focus() - m.textInput.SetValue(m.choices[m.currentIdx]) - m.textInput.CharLimit = 100 - m.textInput.Width = 100 - return m, cmd + sm.isEditing = true + sm.textInput.Focus() + sm.textInput.SetValue(sm.choices[sm.currentIdx]) + sm.textInput.CharLimit = 100 + sm.textInput.Width = 100 + return sm, cmd case tea.KeyUp: - if m.currentIdx > 0 { - m.currentIdx-- + if sm.currentIdx > 0 { + sm.currentIdx-- } case tea.KeyDown: - if m.currentIdx < len(m.choices)-1 { - m.currentIdx++ + if sm.currentIdx < len(sm.choices)-1 { + sm.currentIdx++ } case tea.KeyEnter: - if err := util.ExecCommitMessage(m.choices[m.currentIdx]); err != nil { - m.errorMsg = "コミットに失敗: " + err.Error() - return m, tea.Quit + if err := sm.scs.SubmitCommit(sm.choices[sm.currentIdx]); err != nil { + sm.errorMsg = "コミットに失敗: " + err.Error() + return sm, tea.Quit } - return m, tea.Quit + return sm, tea.Quit case tea.KeyCtrlC, tea.KeyEsc: - return m, tea.Quit + return sm, tea.Quit } case spinner.TickMsg: var cmd tea.Cmd - m.spinner, cmd = m.spinner.Update(msg) - return m, cmd + sm.spinner, cmd = sm.spinner.Update(msg) + return sm, cmd } - return m, m.spinner.Tick + return sm, sm.spinner.Tick } -func (m *model) resetSpinner() { - m.spinner = spinner.New() - m.spinner.Style = spinnerStyle - m.spinner.Spinner = spinner.Globe -} - -func (m *model) View() string { - if m.errorMsg != "" { - return color.RedString(m.errorMsg) +func (sm *suggestModel) View() string { + if sm.errorMsg != "" { + return color.RedString(sm.errorMsg) } - if m.isLoading { - s := fmt.Sprintf("\n %s %s\n\n", m.spinner.View(), textStyle("コミットメッセージ生成中")) + if sm.isLoading { + s := fmt.Sprintf("\n %s %s\n\n", sm.spinner.View(), textStyle("コミットメッセージ生成中")) return s } var b strings.Builder - if m.errorMsg != "" { - b.WriteString(color.RedString(m.errorMsg) + "\n\n") + if sm.errorMsg != "" { + b.WriteString(color.RedString(sm.errorMsg) + "\n\n") } - if m.isEditing { - return m.textInput.View() + if sm.isEditing { + return sm.textInput.View() } b.WriteString(color.WhiteString("🍕 Please select and enter to commit")) b.WriteString(color.WhiteString("\n Use arrow ↑↓ to navigate and press Enter to select.")) b.WriteString(color.WhiteString("\n ( enter Tab key to edit message )\n\n")) - for i, choice := range m.choices { - if i == m.currentIdx { + for i, choice := range sm.choices { + if i == sm.currentIdx { b.WriteString(fmt.Sprintf(color.HiCyanString("➡️ %s\n"), choice)) } else { b.WriteString(fmt.Sprintf(color.CyanString(" %s\n"), choice)) @@ -134,28 +114,44 @@ func (m *model) View() string { return b.String() } -func initialModel() model { +// モデルの初期化処理 +func NewSuggestModel() *suggestModel { ti := textinput.New() ti.Focus() - return model{ + // suggestコマンドのサービスの取得 + scs, err := service.NewSuggestCmdService() + if err != nil { + log.Fatal(err) + os.Exit(-1) + } + + return &suggestModel{ choices: []string{""}, currentIdx: 0, errorMsg: "", isLoading: true, isEditing: false, textInput: ti, + scs: scs, } } +// スピナーの初期化 +func (sm *suggestModel) initSpinner() { + sm.spinner = spinner.New() + sm.spinner.Style = spinnerStyle + sm.spinner.Spinner = spinner.Globe +} + var suggestCmd = &cobra.Command{ Use: "suggest", Short: "Suggestion of commit message for staging repository", Aliases: []string{"s", "suggest"}, Run: func(cmd *cobra.Command, args []string) { - m := initialModel() - m.resetSpinner() - p := tea.NewProgram(&m) + sm := NewSuggestModel() + sm.initSpinner() + p := tea.NewProgram(sm) p.Run() }, } diff --git a/internal/entity/config.go b/internal/entity/config.go index cbbfe26..519e183 100644 --- a/internal/entity/config.go +++ b/internal/entity/config.go @@ -3,11 +3,13 @@ package entity import ( "encoding/json" "fmt" + "os" pb "github.com/cocoide/commitify/pkg/grpc" "github.com/spf13/viper" ) +// コミットメッセージの言語の列挙型 type Language int const ( @@ -15,6 +17,7 @@ const ( JP ) +// コミットメッセージの形式の列挙型 type CodeFormat int const ( @@ -23,6 +26,7 @@ const ( PrefixFormat ) +// AIのソースの列挙型 type AISource int const ( @@ -61,8 +65,12 @@ func (c *Config) Config2PbVars() (pb.CodeFormatType, pb.LanguageType) { func ReadConfig() (Config, error) { var result Config + homePath, err := os.UserHomeDir() + if err != nil { + return result, err + } - viper.AddConfigPath("$HOME/.commitify") + viper.AddConfigPath(homePath + "/.commitify") viper.SetConfigName("config") viper.SetConfigType("yaml") if err := viper.ReadInConfig(); err != nil { @@ -75,7 +83,12 @@ func ReadConfig() (Config, error) { } func WriteConfig(config Config) error { - viper.AddConfigPath("$HOME/.commitify") + homePath, err := os.UserHomeDir() + if err != nil { + return err + } + + viper.AddConfigPath(homePath + "/.commitify") viper.SetConfigName("config") viper.SetConfigType("yaml") configMap := make(map[string]interface{}) @@ -95,3 +108,28 @@ func WriteConfig(config Config) error { } return nil } + +func SaveConfig(configIndex, updateConfigParamInt int, updateConfigParamStr string) error { + currentConfig, err := ReadConfig() + if err != nil { + return err + } + + switch configIndex { + case 0: + currentConfig.ChatGptApiKey = updateConfigParamStr + case 1: + currentConfig.UseLanguage = updateConfigParamInt + case 2: + currentConfig.CommitFormat = updateConfigParamInt + case 3: + currentConfig.AISource = updateConfigParamInt + } + + err = WriteConfig(currentConfig) + if err != nil { + return err + } + + return nil +} diff --git a/internal/gateway/grpc_serve.go b/internal/gateway/ai_source_grpc.go similarity index 79% rename from internal/gateway/grpc_serve.go rename to internal/gateway/ai_source_grpc.go index b76bdd2..612c542 100644 --- a/internal/gateway/grpc_serve.go +++ b/internal/gateway/ai_source_grpc.go @@ -8,7 +8,6 @@ import ( "os" "github.com/cocoide/commitify/internal/entity" - "github.com/cocoide/commitify/internal/service" pb "github.com/cocoide/commitify/pkg/grpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -41,7 +40,7 @@ func NewGrpcServeGateway() *grpcServeGateway { return gsg } -func (gsg grpcServeGateway) FetchCommitMessages() ([]string, error) { +func (gsg grpcServeGateway) FetchCommitMessages(fileDiffStr string) ([]string, error) { // 設定情報を取得 conf, err := entity.ReadConfig() if err != nil { @@ -49,16 +48,8 @@ func (gsg grpcServeGateway) FetchCommitMessages() ([]string, error) { } cft, lt := conf.Config2PbVars() - fds := service.NewFileDiffService() - - diffStr, err := fds.CreateFileDiffStr() - if err != nil { - log.Fatal("差分の取得に失敗: ", err) - return nil, err - } - req := &pb.CommitMessageRequest{ - InputCode: diffStr, + InputCode: fileDiffStr, CodeFormat: cft, Language: lt, } diff --git a/internal/gateway/ai_source_interface.go b/internal/gateway/ai_source_interface.go new file mode 100644 index 0000000..3dcd9a4 --- /dev/null +++ b/internal/gateway/ai_source_interface.go @@ -0,0 +1,5 @@ +package gateway + +type AISourceGatewayInterface interface { + FetchCommitMessages(fileDiffStr string) ([]string, error) +} diff --git a/internal/gateway/openai.go b/internal/gateway/ai_source_openai.go similarity index 100% rename from internal/gateway/openai.go rename to internal/gateway/ai_source_openai.go diff --git a/internal/gateway/gateway_interface.go b/internal/gateway/gateway_interface.go deleted file mode 100644 index 8d2f8fd..0000000 --- a/internal/gateway/gateway_interface.go +++ /dev/null @@ -1,5 +0,0 @@ -package gateway - -type GatewayInterface interface { - FetchCommitMessages() ([]string, error) -} diff --git a/internal/service/file_diff_service.go b/internal/service/file_diff_service.go index c171a90..e9e2405 100644 --- a/internal/service/file_diff_service.go +++ b/internal/service/file_diff_service.go @@ -5,12 +5,12 @@ import "os/exec" type fileDiffService struct { } -func NewFileDiffService() *fileDiffService { - ps := new(fileDiffService) - return ps +func NewFileDiffService() fileDiffService { + fds := fileDiffService{} + return fds } -func (ps fileDiffService) CreateFileDiffStr() (string, error) { +func (fds *fileDiffService) createFileDiffStr() (string, error) { diff, err := exec.Command("git", "diff", "--staged").Output() return string(diff), err diff --git a/internal/service/suggest_cmd.go b/internal/service/suggest_cmd.go new file mode 100644 index 0000000..389f4c3 --- /dev/null +++ b/internal/service/suggest_cmd.go @@ -0,0 +1,53 @@ +package service + +import ( + "log" + "os/exec" + + "github.com/cocoide/commitify/internal/entity" + "github.com/cocoide/commitify/internal/gateway" +) + +type SuggestCmdService struct { + ais gateway.AISourceGatewayInterface + fds fileDiffService +} + +func NewSuggestCmdService() (*SuggestCmdService, error) { + conf, err := entity.ReadConfig() + if err != nil { + return nil, err + } + + var aigi gateway.AISourceGatewayInterface + switch conf.AISource { + case int(entity.WrapServer): + aigi = gateway.NewGrpcServeGateway() + case int(entity.OpenAiAPI): + log.Fatal("現在、非対応の機能です。") + return nil, err + default: + aigi = gateway.NewGrpcServeGateway() + } + + fds := NewFileDiffService() + + return &SuggestCmdService{ais: aigi, fds: fds}, nil +} + +func (scs *SuggestCmdService) GenerateCommitMessages() ([]string, error) { + fileDiffStr, err := scs.fds.createFileDiffStr() + if err != nil { + return nil, err + } + + return scs.ais.FetchCommitMessages(fileDiffStr) +} + +func (scs *SuggestCmdService) SubmitCommit(commitMessage string) error { + cmd := exec.Command("git", "commit", "-m", commitMessage) + if err := cmd.Run(); err != nil { + return err + } + return nil +} diff --git a/main.go b/main.go index 18a6557..1c5eee1 100644 --- a/main.go +++ b/main.go @@ -9,9 +9,13 @@ import ( func main() { // configファイルがあるかどうかを確認 - homePath := os.Getenv("HOME") + homePath, err := os.UserHomeDir() + if err != nil { + fmt.Printf("error of find user home dir, %v", err) + return + } - _, err := os.Stat(homePath + "/.commitify/config.yaml") + _, err = os.Stat(homePath + "/.commitify/config.yaml") if os.IsNotExist(err) { if err := os.MkdirAll(homePath+"/.commitify", 0755); err != nil { fmt.Printf("error of make directory, %v", err) diff --git a/util/exec.go b/util/exec.go deleted file mode 100644 index 5d5fba3..0000000 --- a/util/exec.go +++ /dev/null @@ -1,24 +0,0 @@ -package util - -import ( - "fmt" - "log" - "os/exec" -) - -func ExecGetStagingCode() string { - code, err := exec.Command("git", "diff", "--staged").Output() - if err != nil { - fmt.Printf("Gitでエラーが発生") - log.Fatal(err.Error()) - } - return string(code) -} - -func ExecCommitMessage(msg string) error { - cmd := exec.Command("git", "commit", "-m", msg) - if err := cmd.Run(); err != nil { - return err - } - return nil -}