From 5f71b4c779e8ed6fa43832922ae3694d3ea59ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Sat, 2 Nov 2024 20:14:45 +0200 Subject: [PATCH 01/14] feat: add current state --- config.go | 11 ++-- main.go | 20 +++++++ runtime.go | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 runtime.go diff --git a/config.go b/config.go index 9561764..7d019b1 100644 --- a/config.go +++ b/config.go @@ -22,6 +22,7 @@ type config struct { TotalInputCharLimit int `json:"totalInputCharLimit"` ScopeCompletionOrder string `json:"scopeCompletionOrder"` FindAllCommitMessages bool `json:"findAllCommitMessages"` + ShowRuntime bool `json:"showRuntime"` } func (i prefix) Title() string { return i.T } @@ -75,7 +76,7 @@ var defaultPrefixes = []list.Item{ }, } -const applicationName = "cometary" +const ApplicationName = "cometary" func loadConfig() ([]list.Item, bool, *config, error) { nonXdgConfigFile := ".comet.json" @@ -94,7 +95,7 @@ func loadConfig() ([]list.Item, bool, *config, error) { } // Check for configuration file according to XDG Base Directory Specification - if cfgDir, err := getConfigDir(); err == nil { + if cfgDir, err := GetConfigDir(); err == nil { path := filepath.Join(cfgDir, "config.json") if _, err := os.Stat(path); err == nil { return loadConfigFile(path) @@ -104,7 +105,7 @@ func loadConfig() ([]list.Item, bool, *config, error) { return defaultPrefixes, false, nil, nil } -func getConfigDir() (string, error) { +func GetConfigDir() (string, error) { configDir := os.Getenv("XDG_CONFIG_HOME") // If the value of the environment variable is unset, empty, or not an absolute path, use the default @@ -113,11 +114,11 @@ func getConfigDir() (string, error) { if err != nil { return "", err } - return filepath.Join(homeDir, ".config", applicationName), nil + return filepath.Join(homeDir, ".config", ApplicationName), nil } // The value of the environment variable is valid; use it - return filepath.Join(configDir, applicationName), nil + return filepath.Join(configDir, ApplicationName), nil } func loadConfigFile(path string) ([]list.Item, bool, *config, error) { diff --git a/main.go b/main.go index 63dadde..49a955d 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "os" @@ -36,6 +37,13 @@ func main() { commitSearchTerm = os.Args[2] } + tracker, err := NewRuntimeTracker("") + if err != nil { + fail("error creating tracker: %s", err) + } + + tracker.Start() + m := newModel(prefixes, config, stagedFiles, config.ScopeCompletionOrder, commitSearchTerm, config.FindAllCommitMessages) if _, err := tea.NewProgram(m).Run(); err != nil { fail(err.Error()) @@ -51,6 +59,18 @@ func main() { if err := commit(msg, withBody, signOff); err != nil { fail("error committing: %s", err) } + + runtime, err := tracker.Stop() + if err != nil { + fail("error stopping tracker: %s", err) + } + + fmt.Printf("Program ran for %.2f seconds\n", runtime) + + // Print current statistics + stats := tracker.GetStats() + statsJSON, _ := json.MarshalIndent(stats, "", " ") + fmt.Printf("\nCurrent statistics:\n%s\n", string(statsJSON)) } func fail(format string, args ...interface{}) { diff --git a/runtime.go b/runtime.go new file mode 100644 index 0000000..c63e684 --- /dev/null +++ b/runtime.go @@ -0,0 +1,168 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "time" +) + +// Stats holds the runtime statistics for different time periods. +type Stats struct { + Daily map[string]float32 `json:"daily"` + Weekly map[string]float32 `json:"weekly"` + Monthly map[string]float32 `json:"monthly"` + Yearly map[string]float32 `json:"yearly"` + LastUpdate string `json:"last_update"` +} + +// RuntimeTracker handles program runtime tracking. +type RuntimeTracker struct { + statsFilePath string + startTime time.Time + stats Stats +} + +// NewRuntimeTracker creates a new RuntimeTracker instance. +func NewRuntimeTracker(filename string) (*RuntimeTracker, error) { + cfgDir, err := GetConfigDir() + if err != nil { + return nil, err + } + + if filename == "" { + filename = "stats.json" + } + path := filepath.Join(cfgDir, filename) + + rt := &RuntimeTracker{ + statsFilePath: path, + stats: Stats{ + Daily: make(map[string]float32), + Weekly: make(map[string]float32), + Monthly: make(map[string]float32), + Yearly: make(map[string]float32), + }, + } + + if _, err := os.Stat(rt.statsFilePath); errors.Is(err, os.ErrNotExist) { + rt.saveStats() + } + + if err := rt.loadStats(); err != nil { + return nil, fmt.Errorf("failed to load stats: %w", err) + } + + return rt, nil +} + +// loadStats loads existing statistics. +func (rt *RuntimeTracker) loadStats() error { + data, err := os.ReadFile(rt.statsFilePath) + if err != nil { + return fmt.Errorf("failed to read config file: %w", err) + } + + return json.Unmarshal(data, &rt.stats) +} + +// saveStats saves current statistics. +func (rt *RuntimeTracker) saveStats() error { + if err := os.MkdirAll(filepath.Dir(rt.statsFilePath), 0755); err != nil { + return fmt.Errorf("failed to create directory: %w", err) + } + + data, err := json.MarshalIndent(rt.stats, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal stats: %w", err) + } + + return os.WriteFile(rt.statsFilePath, data, 0644) +} + +// Start begins tracking runtime. +func (rt *RuntimeTracker) Start() { + rt.startTime = time.Now() +} + +// Stop ends tracking runtime and updates statistics. +func (rt *RuntimeTracker) Stop() (float32, error) { + if rt.startTime.IsZero() { + return 0, fmt.Errorf("tracking was not started") + } + + endTime := time.Now() + runtime := float32(endTime.Sub(rt.startTime).Seconds()) + + // Format time periods + date := endTime.Format("2006-01-02") + + // Get ISO week number + y, week := endTime.ISOWeek() + weekStr := fmt.Sprintf("%d-W%02d", y, week) + + month := endTime.Format("2006-01") + year := endTime.Format("2006") + + // Update statistics + rt.stats.Daily[date] += runtime + rt.stats.Weekly[weekStr] += runtime + rt.stats.Monthly[month] += runtime + rt.stats.Yearly[year] += runtime + rt.stats.LastUpdate = endTime.Format(time.RFC3339) + + // Save updated stats + if err := rt.saveStats(); err != nil { + return runtime, fmt.Errorf("failed to save stats: %w", err) + } + + // Reset start time + rt.startTime = time.Time{} + + return runtime, nil +} + +// CleanupOldData removes data older than the specified number of days +func (rt *RuntimeTracker) CleanupOldData(daysToKeep int) error { + cutoff := time.Now().AddDate(0, 0, -daysToKeep) + + // Helper function to check if a date string is before cutoff + isOld := func(dateStr string) bool { + t, err := time.Parse("2006-01-02", dateStr[:10]) + if err != nil { + return false + } + return t.Before(cutoff) + } + + // Clean up each period + for date := range rt.stats.Daily { + if isOld(date) { + delete(rt.stats.Daily, date) + } + } + for week := range rt.stats.Weekly { + if isOld(week) { + delete(rt.stats.Weekly, week) + } + } + for month := range rt.stats.Monthly { + if isOld(month) { + delete(rt.stats.Monthly, month) + } + } + for year := range rt.stats.Yearly { + if isOld(year) { + delete(rt.stats.Yearly, year) + } + } + + return rt.saveStats() +} + +// GetStats returns the current statistics +func (rt *RuntimeTracker) GetStats() Stats { + return rt.stats +} From 413d1c720df1029e999f5d255ae29c4e91cc3be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Sat, 9 Nov 2024 18:28:25 +0200 Subject: [PATCH 02/14] feat: add current state --- config.go | 2 ++ gui.go | 23 +++++++++++++++++++++++ main.go | 33 ++++++++++++++++++++++----------- runtime.go | 18 +++++++++++++----- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/config.go b/config.go index 7d019b1..96d6913 100644 --- a/config.go +++ b/config.go @@ -22,7 +22,9 @@ type config struct { TotalInputCharLimit int `json:"totalInputCharLimit"` ScopeCompletionOrder string `json:"scopeCompletionOrder"` FindAllCommitMessages bool `json:"findAllCommitMessages"` + StoreRuntime bool `json:"storeRuntime"` ShowRuntime bool `json:"showRuntime"` + ShowStats bool `json:"showStats"` } func (i prefix) Title() string { return i.T } diff --git a/gui.go b/gui.go index 302313b..9c962d0 100644 --- a/gui.go +++ b/gui.go @@ -14,6 +14,7 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/olekukonko/tablewriter" "golang.org/x/exp/maps" ) @@ -534,3 +535,25 @@ func pkgVersion() string { } return version } + +func showTable(data [][]string) { + tableString := &strings.Builder{} + table := tablewriter.NewWriter(tableString) + table.SetHeader([]string{"Period", "Time"}) + table.SetHeaderColor(tablewriter.Colors{tablewriter.FgGreenColor}, tablewriter.Colors{tablewriter.FgGreenColor}) + table.SetAutoWrapText(false) + table.SetAutoFormatHeaders(false) + table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + table.SetAlignment(tablewriter.ALIGN_LEFT) + table.SetCenterSeparator("-") + table.SetColumnSeparator("") + table.SetRowSeparator("") + table.SetTablePadding("\t") + table.SetNoWhiteSpace(true) + table.AppendBulk(data) + table.Render() + lines := strings.Split(tableString.String(), "\n") + for i := 0; i < len(lines); i++ { + fmt.Printf(" %s\n", lines[i]) + } +} diff --git a/main.go b/main.go index 49a955d..df28094 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "encoding/json" "fmt" "os" @@ -42,7 +41,9 @@ func main() { fail("error creating tracker: %s", err) } - tracker.Start() + if config.StoreRuntime || config.ShowRuntime { + tracker.Start() + } m := newModel(prefixes, config, stagedFiles, config.ScopeCompletionOrder, commitSearchTerm, config.FindAllCommitMessages) if _, err := tea.NewProgram(m).Run(); err != nil { @@ -60,17 +61,27 @@ func main() { fail("error committing: %s", err) } - runtime, err := tracker.Stop() - if err != nil { - fail("error stopping tracker: %s", err) - } + if config.StoreRuntime || config.ShowRuntime { + runtime, err := tracker.Stop() + if err != nil { + fail("error stopping tracker: %s", err) + } - fmt.Printf("Program ran for %.2f seconds\n", runtime) + if config.ShowRuntime { + fmt.Println() + showTable([][]string{{"Session", fmt.Sprintf("%f", runtime)}}) + } + } - // Print current statistics - stats := tracker.GetStats() - statsJSON, _ := json.MarshalIndent(stats, "", " ") - fmt.Printf("\nCurrent statistics:\n%s\n", string(statsJSON)) + if config.ShowStats { + stats := tracker.GetStats() + showTable([][]string{ + {"Daily", fmt.Sprintf("%f", stats.Daily[stats.CurrentDay])}, + {"Weekly", fmt.Sprintf("%f", stats.Weekly[stats.CurrentWeek])}, + {"Monthly", fmt.Sprintf("%f", stats.Monthly[stats.CurrentMonth])}, + {"Yearly", fmt.Sprintf("%f", stats.Yearly[stats.CurrentYear])}, + }) + } } func fail(format string, args ...interface{}) { diff --git a/runtime.go b/runtime.go index c63e684..8d8c1df 100644 --- a/runtime.go +++ b/runtime.go @@ -11,11 +11,15 @@ import ( // Stats holds the runtime statistics for different time periods. type Stats struct { - Daily map[string]float32 `json:"daily"` - Weekly map[string]float32 `json:"weekly"` - Monthly map[string]float32 `json:"monthly"` - Yearly map[string]float32 `json:"yearly"` - LastUpdate string `json:"last_update"` + Daily map[string]float32 `json:"daily"` + CurrentDay string + Weekly map[string]float32 `json:"weekly"` + CurrentWeek string + Monthly map[string]float32 `json:"monthly"` + CurrentMonth string + Yearly map[string]float32 `json:"yearly"` + CurrentYear string + LastUpdate string `json:"lastUpdate"` } // RuntimeTracker handles program runtime tracking. @@ -108,9 +112,13 @@ func (rt *RuntimeTracker) Stop() (float32, error) { // Update statistics rt.stats.Daily[date] += runtime + rt.stats.CurrentDay = date rt.stats.Weekly[weekStr] += runtime + rt.stats.CurrentWeek = weekStr rt.stats.Monthly[month] += runtime + rt.stats.CurrentMonth = month rt.stats.Yearly[year] += runtime + rt.stats.CurrentYear = year rt.stats.LastUpdate = endTime.Format(time.RFC3339) // Save updated stats From 9d1af8de681b4de0af31311a2fee8ef6151586e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Thu, 5 Dec 2024 22:26:06 +0200 Subject: [PATCH 03/14] refactor(git.go): remove function and simplify return --- git.go | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/git.go b/git.go index d5d5898..4215c6f 100644 --- a/git.go +++ b/git.go @@ -10,7 +10,6 @@ import ( func filesInStaging() ([]string, error) { cmd := exec.Command("git", "diff", "--no-ext-diff", "--cached", "--name-only") output, err := cmd.CombinedOutput() - if err != nil { return []string{}, fmt.Errorf(string(output)) } @@ -21,22 +20,13 @@ func filesInStaging() ([]string, error) { return strings.Split(lines, "\n"), nil } -func checkGitInPath() error { - if _, err := exec.LookPath("git"); err != nil { - return fmt.Errorf("cannot find git in PATH: %w", err) - } - return nil -} - -func findGitDir() (string, error) { +func findGitDir() error { cmd := exec.Command("git", "rev-parse", "--show-toplevel") output, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf(string(output)) + return fmt.Errorf(string(output)) } - - return strings.TrimSpace(string(output)), nil + return nil } func commit(msg string, body bool, signOff bool) error { From 9e8178c269ac8e2477cbe24e523cfb078bbd5894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Thu, 5 Dec 2024 22:27:32 +0200 Subject: [PATCH 04/14] refactor(gui.go): decrease indent for title text this slightly dedents everything to align properly --- gui.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gui.go b/gui.go index 9c962d0..921e0bc 100644 --- a/gui.go +++ b/gui.go @@ -30,6 +30,7 @@ var ( // #5e81ac: nord10 // #8fbcbb: nord7 filterCursorStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#5e81ac", Dark: "#8fbcbb"}) + titleTextStyle = lipgloss.NewStyle() titleStyle = lipgloss.NewStyle().MarginLeft(2) itemStyle = lipgloss.NewStyle().PaddingLeft(4) characterCountColors = lipgloss.AdaptiveColor{Light: "#8dacb6", Dark: "240"} @@ -113,7 +114,7 @@ func newModel(prefixes []list.Item, config *config, stagedFiles []string, scopeC prefixList.Title = "What are you committing?" prefixList.SetShowStatusBar(false) prefixList.SetFilteringEnabled(true) - prefixList.Styles.Title = titleStyle + prefixList.Styles.Title = titleTextStyle prefixList.Styles.PaginationStyle = paginationStyle prefixList.Styles.HelpStyle = helpStyle prefixList.FilterInput.PromptStyle = filterPromptStyle From 48cb78fd364e15fbc7ec8dc03d5e12b76bfd5de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Thu, 5 Dec 2024 22:29:22 +0200 Subject: [PATCH 05/14] refactor(gui.go): change deprecated methods --- gui.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui.go b/gui.go index 921e0bc..77bd1bd 100644 --- a/gui.go +++ b/gui.go @@ -38,7 +38,7 @@ var ( // #a3be8c: nord13 selectedItemColors = lipgloss.AdaptiveColor{Light: "#d08770", Dark: "#a3be8c"} selectedItemStyle = lipgloss.NewStyle().Foreground(selectedItemColors) - selectedItemPadded = selectedItemStyle.Copy().PaddingLeft(2) + selectedItemPadded = lipgloss.NewStyle().Foreground(selectedItemColors).PaddingLeft(2) itemDescriptionStyle = lipgloss.NewStyle().PaddingLeft(2).Faint(true) paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4) helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1) @@ -118,7 +118,7 @@ func newModel(prefixes []list.Item, config *config, stagedFiles []string, scopeC prefixList.Styles.PaginationStyle = paginationStyle prefixList.Styles.HelpStyle = helpStyle prefixList.FilterInput.PromptStyle = filterPromptStyle - prefixList.FilterInput.CursorStyle = filterCursorStyle + prefixList.FilterInput.Cursor.Style = filterCursorStyle scopeInput := textinput.New() scopeInput.Placeholder = "Scope" From 6f63bf347bf14237ba466160c03f8bf415c488af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Thu, 5 Dec 2024 22:31:59 +0200 Subject: [PATCH 06/14] refactor(gui.go): change selected item's prefix indicator this now matches the other input fields --- gui.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui.go b/gui.go index 77bd1bd..8f9b416 100644 --- a/gui.go +++ b/gui.go @@ -66,7 +66,7 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list var output string if index == m.Index() { - output = selectedItemPadded.Render("ยป " + str) + output = selectedItemPadded.Render("> " + str) } else { output = itemStyle.Render(str) } From 447deaa122296578fa0c8c7e1c640acd98f7ff0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Thu, 5 Dec 2024 22:35:12 +0200 Subject: [PATCH 07/14] refactor: clean up how the configuration is used it was a real mess before --- config.go | 76 +++++++++++++++++++++++++++---------------------------- go.mod | 1 + go.sum | 11 +++----- gui.go | 33 ++++++++++++++---------- main.go | 29 ++++++++------------- 5 files changed, 72 insertions(+), 78 deletions(-) diff --git a/config.go b/config.go index 96d6913..10968bf 100644 --- a/config.go +++ b/config.go @@ -2,11 +2,8 @@ package main import ( "encoding/json" - "fmt" "os" "path/filepath" - - "github.com/charmbracelet/bubbles/list" ) type prefix struct { @@ -31,56 +28,56 @@ func (i prefix) Title() string { return i.T } func (i prefix) Description() string { return i.D } func (i prefix) FilterValue() string { return i.T } -var defaultPrefixes = []list.Item{ - prefix{ +var defaultPrefixes = []prefix{ + { T: "feat", D: "Introduces a new feature", }, - prefix{ + { T: "fix", D: "Patches a bug", }, - prefix{ + { T: "docs", D: "Documentation changes only", }, - prefix{ + { T: "test", D: "Adding missing tests or correcting existing tests", }, - prefix{ + { T: "build", D: "Changes that affect the build system", }, - prefix{ + { T: "ci", D: "Changes to CI configuration files and scripts", }, - prefix{ + { T: "perf", D: "A code change that improves performance", }, - prefix{ + { T: "refactor", D: "A code change that neither fixes a bug nor adds a feature", }, - prefix{ + { T: "revert", D: "Reverts a previous change", }, - prefix{ + { T: "style", D: "Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)", }, - prefix{ + { T: "chore", D: "A minor change which does not fit into any other category", }, } -const ApplicationName = "cometary" +const applicationName = "cometary" -func loadConfig() ([]list.Item, bool, *config, error) { +func loadConfig() *config { nonXdgConfigFile := ".comet.json" // Check for configuration file local to current directory @@ -104,45 +101,48 @@ func loadConfig() ([]list.Item, bool, *config, error) { } } - return defaultPrefixes, false, nil, nil + return newConfig() +} + +func newConfig() *config { + return &config{ + Prefixes: defaultPrefixes, + SignOffCommits: false, + ScopeInputCharLimit: 16, + CommitInputCharLimit: 100, + TotalInputCharLimit: 0, + ScopeCompletionOrder: "descending", + FindAllCommitMessages: false, + StoreRuntime: false, + ShowRuntime: false, + ShowStats: false, + } } func GetConfigDir() (string, error) { configDir := os.Getenv("XDG_CONFIG_HOME") - // If the value of the environment variable is unset, empty, or not an absolute path, use the default if configDir == "" || configDir[0:1] != "/" { homeDir, err := os.UserHomeDir() if err != nil { return "", err } - return filepath.Join(homeDir, ".config", ApplicationName), nil + return filepath.Join(homeDir, ".config", applicationName), nil } - // The value of the environment variable is valid; use it - return filepath.Join(configDir, ApplicationName), nil + return filepath.Join(configDir, applicationName), nil } -func loadConfigFile(path string) ([]list.Item, bool, *config, error) { +func loadConfigFile(path string) *config { + var c config data, err := os.ReadFile(path) if err != nil { - return nil, false, nil, fmt.Errorf("failed to read config file: %w", err) + return &c } - var c config + if err := json.Unmarshal(data, &c); err != nil { - return nil, false, nil, fmt.Errorf("invalid json in config file '%s': %w", path, err) + return &c } - return convertPrefixes(c.Prefixes), c.SignOffCommits, &c, nil -} - -func convertPrefixes(prefixes []prefix) []list.Item { - var output []list.Item - for _, prefix := range prefixes { - output = append(output, prefix) - } - if len(output) == 0 { - return defaultPrefixes - } - return output + return &c } diff --git a/go.mod b/go.mod index c892b83..f68380c 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/charmbracelet/bubbles v0.16.1 github.com/charmbracelet/bubbletea v0.24.2 github.com/charmbracelet/lipgloss v0.8.0 + github.com/olekukonko/tablewriter v0.0.5 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 ) diff --git a/go.sum b/go.sum index af1183e..92b6424 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5 github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY= github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg= -github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= -github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU= github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= @@ -19,6 +17,7 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -30,6 +29,8 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= @@ -42,15 +43,9 @@ golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= diff --git a/gui.go b/gui.go index 8f9b416..eb905a9 100644 --- a/gui.go +++ b/gui.go @@ -109,7 +109,8 @@ type model struct { messageInputIndex int } -func newModel(prefixes []list.Item, config *config, stagedFiles []string, scopeCompletionOrder, commitSearchTerm string, findAllCommitMessages bool) *model { +func newModel(c *config, stagedFiles []string, commitSearchTerm string) *model { + prefixes := convertPrefixes(c.Prefixes) prefixList := list.New(prefixes, itemDelegate{}, defaultWidth, listHeight) prefixList.Title = "What are you committing?" prefixList.SetShowStatusBar(false) @@ -123,25 +124,23 @@ func newModel(prefixes []list.Item, config *config, stagedFiles []string, scopeC scopeInput := textinput.New() scopeInput.Placeholder = "Scope" - // when no limit was defined a default of 0 is used - if config == nil || config.ScopeInputCharLimit == 0 { + if c == nil || c.ScopeInputCharLimit == 0 { scopeInput.CharLimit = 16 scopeInput.Width = 20 } else { - scopeInput.CharLimit = config.ScopeInputCharLimit - scopeInput.Width = config.ScopeInputCharLimit + scopeInput.CharLimit = c.ScopeInputCharLimit + scopeInput.Width = c.ScopeInputCharLimit } commitInput := textinput.New() commitInput.Placeholder = "Commit message" - // when no limit was defined a default of 0 is used - if config == nil || config.CommitInputCharLimit == 0 { + if c == nil || c.CommitInputCharLimit == 0 { commitInput.CharLimit = 100 commitInput.Width = 50 } else { - commitInput.CharLimit = config.CommitInputCharLimit - commitInput.Width = config.CommitInputCharLimit + commitInput.CharLimit = c.CommitInputCharLimit + commitInput.Width = c.CommitInputCharLimit } bodyConfirmation := textinput.New() @@ -149,11 +148,11 @@ func newModel(prefixes []list.Item, config *config, stagedFiles []string, scopeC bodyConfirmation.CharLimit = 1 bodyConfirmation.Width = 20 - if config == nil || config.TotalInputCharLimit == 0 { + if c == nil || c.TotalInputCharLimit == 0 { constrainInput = false } else { constrainInput = true - totalInputCharLimit = config.TotalInputCharLimit + totalInputCharLimit = c.TotalInputCharLimit } bindings := []key.Binding{ @@ -170,12 +169,20 @@ func newModel(prefixes []list.Item, config *config, stagedFiles []string, scopeC constrainInput: constrainInput, totalInputCharLimit: totalInputCharLimit, stagedFiles: stagedFiles, - scopeCompletionOrder: scopeCompletionOrder, + scopeCompletionOrder: c.ScopeCompletionOrder, commitSearchTerm: commitSearchTerm, - findAllCommitMessages: findAllCommitMessages, + findAllCommitMessages: c.FindAllCommitMessages, } } +func convertPrefixes(prefixes []prefix) []list.Item { + var output []list.Item + for _, prefix := range prefixes { + output = append(output, prefix) + } + return output +} + func (m *model) Init() tea.Cmd { return tea.Batch( formUniquePaths(m.stagedFiles, m.scopeCompletionOrder), diff --git a/main.go b/main.go index df28094..6b55f1f 100644 --- a/main.go +++ b/main.go @@ -3,39 +3,28 @@ package main import ( "fmt" "os" + "strings" tea "github.com/charmbracelet/bubbletea" ) func main() { - if err := checkGitInPath(); err != nil { + if err := findGitDir(); err != nil { fail(err.Error()) } - gitRoot, err := findGitDir() - if err != nil { - fail(err.Error()) - } - - if err := os.Chdir(gitRoot); err != nil { - fail("error changing directory: %s", err) - } - stagedFiles, err := filesInStaging() if err != nil { fail(err.Error()) } - prefixes, signOff, config, err := loadConfig() - if err != nil { - fail(err.Error()) - } - commitSearchTerm := "" if len(os.Args) > 1 && os.Args[1] == "-m" { commitSearchTerm = os.Args[2] } + config := loadConfig() + tracker, err := NewRuntimeTracker("") if err != nil { fail("error creating tracker: %s", err) @@ -45,19 +34,18 @@ func main() { tracker.Start() } - m := newModel(prefixes, config, stagedFiles, config.ScopeCompletionOrder, commitSearchTerm, config.FindAllCommitMessages) + m := newModel(config, stagedFiles, commitSearchTerm) if _, err := tea.NewProgram(m).Run(); err != nil { fail(err.Error()) } fmt.Println("") - if !m.Finished() { fail("terminated") } msg, withBody := m.CommitMessage() - if err := commit(msg, withBody, signOff); err != nil { + if err := commit(msg, withBody, config.SignOffCommits); err != nil { fail("error committing: %s", err) } @@ -85,6 +73,9 @@ func main() { } func fail(format string, args ...interface{}) { - _, _ = fmt.Fprintf(os.Stderr, format+"\n", args...) + if !strings.HasSuffix(format, "\n") { + format = format + "\n" + } + _, _ = fmt.Fprintf(os.Stderr, format, args...) os.Exit(1) } From 3c810f81eee188dbba2bb9d7cba71869b57b0bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Thu, 5 Dec 2024 22:36:19 +0200 Subject: [PATCH 08/14] feat: add session as statistics field --- main.go | 8 ++++++-- runtime.go | 21 ++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index 6b55f1f..5c3d8c0 100644 --- a/main.go +++ b/main.go @@ -50,20 +50,24 @@ func main() { } if config.StoreRuntime || config.ShowRuntime { - runtime, err := tracker.Stop() + err := tracker.Stop() if err != nil { fail("error stopping tracker: %s", err) } if config.ShowRuntime { + stats := tracker.GetStats() fmt.Println() - showTable([][]string{{"Session", fmt.Sprintf("%f", runtime)}}) + showTable([][]string{ + {"Session", fmt.Sprintf("%f", stats.Session)}, + }) } } if config.ShowStats { stats := tracker.GetStats() showTable([][]string{ + {"Session", fmt.Sprintf("%f", stats.Session)}, {"Daily", fmt.Sprintf("%f", stats.Daily[stats.CurrentDay])}, {"Weekly", fmt.Sprintf("%f", stats.Weekly[stats.CurrentWeek])}, {"Monthly", fmt.Sprintf("%f", stats.Monthly[stats.CurrentMonth])}, diff --git a/runtime.go b/runtime.go index 8d8c1df..6289558 100644 --- a/runtime.go +++ b/runtime.go @@ -11,15 +11,16 @@ import ( // Stats holds the runtime statistics for different time periods. type Stats struct { + Session float32 `json:"session"` Daily map[string]float32 `json:"daily"` - CurrentDay string + CurrentDay string `json:"currentDay"` Weekly map[string]float32 `json:"weekly"` - CurrentWeek string + CurrentWeek string `json:"currentWeek"` Monthly map[string]float32 `json:"monthly"` - CurrentMonth string + CurrentMonth string `json:"currentMonth"` Yearly map[string]float32 `json:"yearly"` - CurrentYear string - LastUpdate string `json:"lastUpdate"` + CurrentYear string `json:"currentYear"` + LastUpdate string `json:"lastUpdate"` } // RuntimeTracker handles program runtime tracking. @@ -44,6 +45,7 @@ func NewRuntimeTracker(filename string) (*RuntimeTracker, error) { rt := &RuntimeTracker{ statsFilePath: path, stats: Stats{ + Session: 0.0, Daily: make(map[string]float32), Weekly: make(map[string]float32), Monthly: make(map[string]float32), @@ -92,9 +94,9 @@ func (rt *RuntimeTracker) Start() { } // Stop ends tracking runtime and updates statistics. -func (rt *RuntimeTracker) Stop() (float32, error) { +func (rt *RuntimeTracker) Stop() error { if rt.startTime.IsZero() { - return 0, fmt.Errorf("tracking was not started") + return fmt.Errorf("tracking was not started") } endTime := time.Now() @@ -111,6 +113,7 @@ func (rt *RuntimeTracker) Stop() (float32, error) { year := endTime.Format("2006") // Update statistics + rt.stats.Session = runtime rt.stats.Daily[date] += runtime rt.stats.CurrentDay = date rt.stats.Weekly[weekStr] += runtime @@ -123,13 +126,13 @@ func (rt *RuntimeTracker) Stop() (float32, error) { // Save updated stats if err := rt.saveStats(); err != nil { - return runtime, fmt.Errorf("failed to save stats: %w", err) + return fmt.Errorf("failed to save stats: %w", err) } // Reset start time rt.startTime = time.Time{} - return runtime, nil + return nil } // CleanupOldData removes data older than the specified number of days From 53caf6b12c8b4207e542f61ff22927ffb5786eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Thu, 5 Dec 2024 22:59:56 +0200 Subject: [PATCH 09/14] refactor(gui.go): remove colons they kind of don't make sense in the location they're at --- gui.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gui.go b/gui.go index eb905a9..0a7cf0b 100644 --- a/gui.go +++ b/gui.go @@ -417,7 +417,7 @@ func (m *model) View() string { } return titleStyle.Render(fmt.Sprintf( - "%s%s (Enter to skip / Esc to cancel) %s:\n%s", + "%s%s (Enter to skip / Esc to cancel) %s\n%s", m.previousInputTexts, scopeInputText, limit, @@ -436,7 +436,7 @@ func (m *model) View() string { } return titleStyle.Render(fmt.Sprintf( - "%s%s (Esc to cancel) %s:\n%s", + "%s%s (Esc to cancel) %s\n%s", m.previousInputTexts, msgInputText, limit, @@ -444,7 +444,7 @@ func (m *model) View() string { )) case !m.chosenBody: return titleStyle.Render(fmt.Sprintf( - "%s%s (Esc to cancel):\n%s", + "%s%s (Esc to cancel)\n%s", m.previousInputTexts, bodyInputText, m.ynInput.View(), From ca29bf12a502baba017995101a80d2fa21fcd77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Fri, 6 Dec 2024 09:58:39 +0200 Subject: [PATCH 10/14] refactor(gui.go): remove filtering didn't work anyway --- gui.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/gui.go b/gui.go index 0a7cf0b..8d66154 100644 --- a/gui.go +++ b/gui.go @@ -24,12 +24,6 @@ const ( ) var ( - // #81a1c1: nord9 - // #88c0d0: nord8 - filterPromptStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#81a1c1", Dark: "#88c0d0"}) - // #5e81ac: nord10 - // #8fbcbb: nord7 - filterCursorStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#5e81ac", Dark: "#8fbcbb"}) titleTextStyle = lipgloss.NewStyle() titleStyle = lipgloss.NewStyle().MarginLeft(2) itemStyle = lipgloss.NewStyle().PaddingLeft(4) @@ -114,12 +108,10 @@ func newModel(c *config, stagedFiles []string, commitSearchTerm string) *model { prefixList := list.New(prefixes, itemDelegate{}, defaultWidth, listHeight) prefixList.Title = "What are you committing?" prefixList.SetShowStatusBar(false) - prefixList.SetFilteringEnabled(true) + prefixList.SetFilteringEnabled(false) prefixList.Styles.Title = titleTextStyle prefixList.Styles.PaginationStyle = paginationStyle prefixList.Styles.HelpStyle = helpStyle - prefixList.FilterInput.PromptStyle = filterPromptStyle - prefixList.FilterInput.Cursor.Style = filterCursorStyle scopeInput := textinput.New() scopeInput.Placeholder = "Scope" From ee300107a40b2c092ec26380424efa6166618e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Fri, 6 Dec 2024 15:54:26 +0200 Subject: [PATCH 11/14] feat: simplify stat display and add options --- config.go | 4 ++++ go.mod | 1 - go.sum | 3 --- gui.go | 23 ----------------------- main.go | 37 ++++++++++++++++++++++++++----------- 5 files changed, 30 insertions(+), 38 deletions(-) diff --git a/config.go b/config.go index 10968bf..ebe8656 100644 --- a/config.go +++ b/config.go @@ -22,6 +22,8 @@ type config struct { StoreRuntime bool `json:"storeRuntime"` ShowRuntime bool `json:"showRuntime"` ShowStats bool `json:"showStats"` + ShowStatsFormat string `json:"showStatsFormat"` + SessionStatAsSeconds bool `json:"sessionStatAsSeconds"` } func (i prefix) Title() string { return i.T } @@ -116,6 +118,8 @@ func newConfig() *config { StoreRuntime: false, ShowRuntime: false, ShowStats: false, + ShowStatsFormat: "seconds", + SessionStatAsSeconds: true, } } diff --git a/go.mod b/go.mod index f68380c..c892b83 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/charmbracelet/bubbles v0.16.1 github.com/charmbracelet/bubbletea v0.24.2 github.com/charmbracelet/lipgloss v0.8.0 - github.com/olekukonko/tablewriter v0.0.5 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 ) diff --git a/go.sum b/go.sum index 92b6424..949cb76 100644 --- a/go.sum +++ b/go.sum @@ -17,7 +17,6 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -29,8 +28,6 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= diff --git a/gui.go b/gui.go index 8d66154..5486e59 100644 --- a/gui.go +++ b/gui.go @@ -14,7 +14,6 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/olekukonko/tablewriter" "golang.org/x/exp/maps" ) @@ -535,25 +534,3 @@ func pkgVersion() string { } return version } - -func showTable(data [][]string) { - tableString := &strings.Builder{} - table := tablewriter.NewWriter(tableString) - table.SetHeader([]string{"Period", "Time"}) - table.SetHeaderColor(tablewriter.Colors{tablewriter.FgGreenColor}, tablewriter.Colors{tablewriter.FgGreenColor}) - table.SetAutoWrapText(false) - table.SetAutoFormatHeaders(false) - table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - table.SetAlignment(tablewriter.ALIGN_LEFT) - table.SetCenterSeparator("-") - table.SetColumnSeparator("") - table.SetRowSeparator("") - table.SetTablePadding("\t") - table.SetNoWhiteSpace(true) - table.AppendBulk(data) - table.Render() - lines := strings.Split(tableString.String(), "\n") - for i := 0; i < len(lines); i++ { - fmt.Printf(" %s\n", lines[i]) - } -} diff --git a/main.go b/main.go index 5c3d8c0..9252bc9 100644 --- a/main.go +++ b/main.go @@ -49,31 +49,46 @@ func main() { fail("error committing: %s", err) } + format := config.ShowStatsFormat + if config.SessionStatAsSeconds { + format = "seconds" + } + if config.StoreRuntime || config.ShowRuntime { err := tracker.Stop() if err != nil { fail("error stopping tracker: %s", err) } - if config.ShowRuntime { + if config.ShowRuntime && !config.ShowStats { stats := tracker.GetStats() fmt.Println() - showTable([][]string{ - {"Session", fmt.Sprintf("%f", stats.Session)}, - }) + fmt.Println(formatStat("Session", stats.Session, format)) } } if config.ShowStats { stats := tracker.GetStats() - showTable([][]string{ - {"Session", fmt.Sprintf("%f", stats.Session)}, - {"Daily", fmt.Sprintf("%f", stats.Daily[stats.CurrentDay])}, - {"Weekly", fmt.Sprintf("%f", stats.Weekly[stats.CurrentWeek])}, - {"Monthly", fmt.Sprintf("%f", stats.Monthly[stats.CurrentMonth])}, - {"Yearly", fmt.Sprintf("%f", stats.Yearly[stats.CurrentYear])}, - }) + fmt.Println() + fmt.Println(formatStat("Session", stats.Session, format)) + fmt.Println(formatStat("Daily", stats.Daily[stats.CurrentDay], config.ShowStatsFormat)) + fmt.Println(formatStat("Weekly", stats.Weekly[stats.CurrentWeek], config.ShowStatsFormat)) + fmt.Println(formatStat("Monthly", stats.Monthly[stats.CurrentMonth], config.ShowStatsFormat)) + fmt.Println(formatStat("Yearly", stats.Yearly[stats.CurrentYear], config.ShowStatsFormat)) + } +} + +func formatStat(stat string, seconds float32, format string) string { + var value float32 + switch format { + case "minutes": + value = seconds / 60.0 + case "hours": + value = seconds / 3600.0 + default: + value = seconds } + return fmt.Sprintf(" > %s: %.2f %s", stat, value, format) } func fail(format string, args ...interface{}) { From 66a5683603de57193cd11fa61e4cefd7975c37fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Fri, 6 Dec 2024 18:13:40 +0200 Subject: [PATCH 12/14] docs: describe new configuration options --- README.md | 26 +++++++++++++++++++------- comet.json | 15 ++++++++++----- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0896741..4593850 100644 --- a/README.md +++ b/README.md @@ -42,14 +42,26 @@ rm -rf "${GOPATH}/pkg/mod/github.com/usrme/cometary*" There is an additional `comet.json` file that includes the prefixes and descriptions that I most prefer myself, which can be added to either the root of a repository, to one's home directory as `.comet.json` or to `${XDG_CONFIG_HOME}/cometary/config.json`. Omitting this means that the same defaults are used as in the original. -- To adjust the character limit of the scope, add the key `scopeInputCharLimit` into the configuration file with the desired limit - - Omitting the key uses a default value of 16 characters -- To adjust the character limit of the message, add the key `commitInputCharLimit` into the configuration file with the desired limit - - Omitting the key uses a default value of 100 characters -- To adjust the total limit of characters in the *resulting* commit message, add the key `totalInputCharLimit` into the configuration file with the desired limit +- To adjust the character limit of the scope, add the key `scopeInputCharLimit` with the desired limit + - Default: 16 +- To adjust the character limit of the message, add the key `commitInputCharLimit` with the desired limit + - Default: 100 +- To adjust the total limit of characters in the *resulting* commit message, add the key `totalInputCharLimit` with the desired limit - Adding this key overrides scope- and message-specific limits -- To adjust the order of the scope completion values (i.e. longer or shorter strings first), then add the key `scopeOrderCompletion` into the configuration file with either `ascending` or `descending` as the values - - Omitting the key uses a default order of descending +- To adjust the order of the scope completion values (i.e. longer or shorter strings first), add the key `scopeOrderCompletion` with either `"ascending"` or `"descending"` + - Default: `"descending"` +- To enable the storing of runtime statistics, add the key `storeRuntime` with the value `true` + - Default: `false` + - This will create a `stats.json` file next to the configuration file with aggregated statistics across days, weeks, months, and years +- To show the session runtime statistics after each commit, add the key `showRuntime` with the value `true` + - Default: `false` + - This will show `> Session: N seconds` after the commit was successful +- To show the all-time runtime statistics after each commit, add the key `showStats` with the value `true` + - Default: `false` +- To adjust the format of the statistics from seconds to hours or minutes, add the key `showStatsFormat` with either `"minutes"` or `"hours"` + - Default: `"seconds"` +- To always show session runtime statistics as seconds but keep everything else as defined by `showStatsFormat`, add the key `sessionStatAsSeconds` with the value `true` + - Default: `false` There is also a `-m` flag that takes a string that will be used as the basis for a search among all commit messages. For example: if you're committing something of a chore and always just use the message "update dependencies", you can do `cometary -m update` (use quotation marks if argument to `-m` includes spaces) and Cometary will populate the list of possible messages with those that include "update", which can then be cycled through with the Tab key. This is similar to the search you could make with `git log --grep="update"`. diff --git a/comet.json b/comet.json index efb4144..5ab046f 100644 --- a/comet.json +++ b/comet.json @@ -1,10 +1,15 @@ { "signOffCommits": false, - "scopeInputCharLimit": 100, - "commitInputCharLimit": 120, - "totalInputCharLimit": 50, - "scopeCompletionOrder": "ascending", - "findAllCommitMessages": true, + "scopeInputCharLimit": 16, + "commitInputCharLimit": 100, + "totalInputCharLimit": 0, + "scopeCompletionOrder": "descending", + "findAllCommitMessages": false, + "storeRuntime": true, + "showRuntime": true, + "showStats": true, + "showStatsFormat": "minutes", + "sessionStatAsSeconds": false, "prefixes": [ { "title": "fix", From 6310a2efea779f2c93aa2b37f841340fcd910e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Fri, 6 Dec 2024 18:18:11 +0200 Subject: [PATCH 13/14] feat(main.go): add '-s' flag to just show statistics --- main.go | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/main.go b/main.go index 9252bc9..3bb5b42 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,27 @@ import ( ) func main() { + config := loadConfig() + + format := config.ShowStatsFormat + if config.SessionStatAsSeconds { + format = "seconds" + } + + tracker, err := NewRuntimeTracker("") + if err != nil { + fail("error creating tracker: %s", err) + } + + if len(os.Args) > 1 && os.Args[1] == "-s" { + stats := tracker.GetStats() + fmt.Println(formatStat("Daily", stats.Daily[stats.CurrentDay], config.ShowStatsFormat)) + fmt.Println(formatStat("Weekly", stats.Weekly[stats.CurrentWeek], config.ShowStatsFormat)) + fmt.Println(formatStat("Monthly", stats.Monthly[stats.CurrentMonth], config.ShowStatsFormat)) + fmt.Println(formatStat("Yearly", stats.Yearly[stats.CurrentYear], config.ShowStatsFormat)) + os.Exit(0) + } + if err := findGitDir(); err != nil { fail(err.Error()) } @@ -23,13 +44,6 @@ func main() { commitSearchTerm = os.Args[2] } - config := loadConfig() - - tracker, err := NewRuntimeTracker("") - if err != nil { - fail("error creating tracker: %s", err) - } - if config.StoreRuntime || config.ShowRuntime { tracker.Start() } @@ -49,11 +63,6 @@ func main() { fail("error committing: %s", err) } - format := config.ShowStatsFormat - if config.SessionStatAsSeconds { - format = "seconds" - } - if config.StoreRuntime || config.ShowRuntime { err := tracker.Stop() if err != nil { From 751344434d7ee7a9ad6a853446644030061e5453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Cllar=20Seerme?= Date: Fri, 6 Dec 2024 18:19:40 +0200 Subject: [PATCH 14/14] docs(README.md): add note about new '-s' flag --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4593850..68ccaee 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ There is an additional `comet.json` file that includes the prefixes and descript - This will show `> Session: N seconds` after the commit was successful - To show the all-time runtime statistics after each commit, add the key `showStats` with the value `true` - Default: `false` + - To just show the all-time runtime statistics and quit the program, run the program with the `-s` flag - To adjust the format of the statistics from seconds to hours or minutes, add the key `showStatsFormat` with either `"minutes"` or `"hours"` - Default: `"seconds"` - To always show session runtime statistics as seconds but keep everything else as defined by `showStatsFormat`, add the key `sessionStatAsSeconds` with the value `true`