Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add optional statistics #13

Merged
merged 14 commits into from
Dec 6, 2024
Merged
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,27 @@ 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 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`
- 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"`.

Expand Down
15 changes: 10 additions & 5 deletions comet.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
81 changes: 44 additions & 37 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ package main

import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/charmbracelet/bubbles/list"
)

type prefix struct {
Expand All @@ -22,62 +19,67 @@ 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"`
ShowStatsFormat string `json:"showStatsFormat"`
SessionStatAsSeconds bool `json:"sessionStatAsSeconds"`
}

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"

func loadConfig() ([]list.Item, bool, *config, error) {
func loadConfig() *config {
nonXdgConfigFile := ".comet.json"

// Check for configuration file local to current directory
Expand All @@ -94,20 +96,36 @@ 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)
}
}

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,
ShowStatsFormat: "seconds",
SessionStatAsSeconds: true,
}
}

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
if configDir == "" || configDir[0:1] != "/" {
homeDir, err := os.UserHomeDir()
if err != nil {
Expand All @@ -116,30 +134,19 @@ func getConfigDir() (string, error) {
return filepath.Join(homeDir, ".config", applicationName), nil
}

// The value of the environment variable is valid; use it
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
}
16 changes: 3 additions & 13 deletions git.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand All @@ -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 {
Expand Down
8 changes: 0 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -42,15 +40,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=
Loading