Project refactor, add anthropic API support
- Split pkg/cli/cmd.go into new pkg/cmd package - Split pkg/cli/functions.go into pkg/lmcli/tools package - Refactor pkg/cli/openai.go to pkg/lmcli/provider/openai Other changes: - Made models configurable - Slight config reorganization
This commit is contained in:
179
pkg/util/util.go
Normal file
179
pkg/util/util.go
Normal file
@@ -0,0 +1,179 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"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, content string) (string, error) {
|
||||
msgFile, _ := os.CreateTemp("/tmp", pattern)
|
||||
defer os.Remove(msgFile.Name())
|
||||
|
||||
os.WriteFile(msgFile.Name(), []byte(placeholder + content), 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 defined by
|
||||
// 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's 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.Float32:
|
||||
floatValue, _ := strconv.ParseFloat(defaultTag, 32)
|
||||
field.Set(reflect.New(e))
|
||||
field.Elem().SetFloat(floatValue)
|
||||
case reflect.Float64:
|
||||
floatValue, _ := strconv.ParseFloat(defaultTag, 64)
|
||||
field.Set(reflect.New(e))
|
||||
field.Elem().SetFloat(floatValue)
|
||||
case reflect.Bool:
|
||||
boolValue := defaultTag == "true"
|
||||
field.Set(reflect.ValueOf(&boolValue))
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
// ReadFileContents returns the string contents of the given file.
|
||||
func ReadFileContents(file string) (string, error) {
|
||||
path := filepath.Clean(file)
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Trim(string(content), "\n\t "), nil
|
||||
}
|
||||
Reference in New Issue
Block a user