lmcli/pkg/cli/util.go
Matt Low dce62e7748 Set config defaults using a "default" struct tag
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.
2023-11-19 04:27:57 +00:00

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
}