Matt Low
dce62e7748
Add new SetStructDefaults function to handle the "defaults" struct tag. Only works on struct fields which are pointers (in order to be able to distinguish between not set (nil) and zero values). So, the Config struct has been updated to use pointer fields and we now need to dereference those pointers to use them.
161 lines
3.9 KiB
Go
161 lines
3.9 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// InputFromEditor retrieves user input by opening an editor (one specified by
|
|
// $EDITOR or 'vim' if $EDITOR is not set) on a temporary file. Once the editor
|
|
// closes, the contents of the file are read and the file is deleted. If the
|
|
// contents of the file exactly match the value of placeholder (no edits to the
|
|
// file were made), then an empty string is returned. Otherwise, the contents
|
|
// are returned. Example patten: message.*.md
|
|
func InputFromEditor(placeholder string, pattern string) (string, error) {
|
|
msgFile, _ := os.CreateTemp("/tmp", pattern)
|
|
defer os.Remove(msgFile.Name())
|
|
|
|
os.WriteFile(msgFile.Name(), []byte(placeholder), os.ModeAppend)
|
|
|
|
editor := os.Getenv("EDITOR")
|
|
if editor == "" {
|
|
editor = "vim" // default to vim if no EDITOR env variable
|
|
}
|
|
|
|
execCmd := exec.Command(editor, msgFile.Name())
|
|
execCmd.Stdin = os.Stdin
|
|
execCmd.Stdout = os.Stdout
|
|
execCmd.Stderr = os.Stderr
|
|
|
|
if err := execCmd.Run(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
bytes, _ := os.ReadFile(msgFile.Name())
|
|
content := string(bytes)
|
|
|
|
if placeholder != "" {
|
|
if content == placeholder {
|
|
return "", nil
|
|
}
|
|
|
|
// strip placeholder if content begins with it
|
|
if strings.HasPrefix(content, placeholder) {
|
|
content = content[len(placeholder):]
|
|
}
|
|
}
|
|
|
|
return strings.Trim(content, "\n \t"), nil
|
|
}
|
|
|
|
// humanTimeElapsedSince returns a human-friendly "in the past" representation
|
|
// of the given duration.
|
|
func humanTimeElapsedSince(d time.Duration) string {
|
|
seconds := d.Seconds()
|
|
minutes := seconds / 60
|
|
hours := minutes / 60
|
|
days := hours / 24
|
|
weeks := days / 7
|
|
months := days / 30
|
|
years := days / 365
|
|
|
|
switch {
|
|
case seconds < 60:
|
|
return "seconds ago"
|
|
case minutes < 2:
|
|
return "1 minute ago"
|
|
case minutes < 60:
|
|
return fmt.Sprintf("%d minutes ago", int64(minutes))
|
|
case hours < 2:
|
|
return "1 hour ago"
|
|
case hours < 24:
|
|
return fmt.Sprintf("%d hours ago", int64(hours))
|
|
case days < 2:
|
|
return "1 day ago"
|
|
case days < 7:
|
|
return fmt.Sprintf("%d days ago", int64(days))
|
|
case weeks < 2:
|
|
return "1 week ago"
|
|
case weeks <= 4:
|
|
return fmt.Sprintf("%d weeks ago", int64(weeks))
|
|
case months < 2:
|
|
return "1 month ago"
|
|
case months < 12:
|
|
return fmt.Sprintf("%d months ago", int64(months))
|
|
case years < 2:
|
|
return "1 year ago"
|
|
default:
|
|
return fmt.Sprintf("%d years ago", int64(years))
|
|
}
|
|
}
|
|
|
|
// SetStructDefaultValues checks for any nil ptr fields within the passed
|
|
// struct, and sets the values of those fields to the value that is contained
|
|
// within their "default" struct tag. Handles setting string, int, and bool
|
|
// values. Returns whether any changes were made to the struct.
|
|
func SetStructDefaults(data interface{}) bool {
|
|
v := reflect.ValueOf(data).Elem()
|
|
changed := false
|
|
|
|
for i := 0; i < v.NumField(); i++ {
|
|
field := v.Field(i)
|
|
|
|
// Check if we can set the field value
|
|
if !field.CanSet() {
|
|
continue
|
|
}
|
|
|
|
// We won't bother with non-pointer fields
|
|
if field.Kind() != reflect.Ptr {
|
|
continue
|
|
}
|
|
|
|
t := field.Type() // type of pointer
|
|
e := t.Elem() // type of value of pointer
|
|
|
|
// Handle nested structs recursively
|
|
if e.Kind() == reflect.Struct {
|
|
if field.IsNil() {
|
|
field.Set(reflect.New(e))
|
|
changed = true
|
|
}
|
|
result := SetStructDefaults(field.Interface())
|
|
if result {
|
|
changed = true
|
|
}
|
|
continue
|
|
}
|
|
|
|
if !field.IsNil() {
|
|
continue
|
|
}
|
|
|
|
// Get the "default" struct tag
|
|
defaultTag := v.Type().Field(i).Tag.Get("default")
|
|
if defaultTag == "" {
|
|
continue
|
|
}
|
|
|
|
// Set nil pointer fields to their defined defaults
|
|
switch e.Kind() {
|
|
case reflect.String:
|
|
defaultValue := defaultTag
|
|
field.Set(reflect.ValueOf(&defaultValue))
|
|
case reflect.Int, reflect.Int32, reflect.Int64:
|
|
intValue, _ := strconv.ParseInt(defaultTag, 10, 64)
|
|
field.Set(reflect.New(e))
|
|
field.Elem().SetInt(intValue)
|
|
case reflect.Bool:
|
|
boolValue := defaultTag == "true"
|
|
field.Set(reflect.ValueOf(&boolValue))
|
|
}
|
|
changed = true
|
|
}
|
|
return changed
|
|
}
|