mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
* feat: custom node command execution models and methods
* feat: implement custom node command handlers for HTTP server and Wails
* chore: expose GetNodeCommands API methods in HTTP and Wails
* chore: add sample custom node command implementation for Cashu restore
* feat: add frontend for custom node commands
* chore: consistent naming of custom node command entities
* chore: consistent naming of custom node command entities
* chore: return interface{} as custom node command execution result
* test: add tests for ParseCommandLine
* chore: add extra ParseCommandLine tests for json
* test: add API tests and mocks
* fix: stabilize order of arguments when invoking custom node commands
* test: add test for unsupported custom node command argument
* chore: add the mockery configuration file and move mocks to tests/mocks
* docs: document mockery usage
---------
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
95 lines
1.7 KiB
Go
95 lines
1.7 KiB
Go
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
func ReadFileTail(filePath string, maxLen int) (data []byte, err error) {
|
|
f, err := os.Open(filePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open file: %w", err)
|
|
}
|
|
defer func() {
|
|
err = f.Close()
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to close file: %w", err)
|
|
data = nil
|
|
}
|
|
}()
|
|
|
|
var dataReader io.Reader = f
|
|
|
|
if maxLen > 0 {
|
|
stat, err := f.Stat()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to stat file: %w", err)
|
|
}
|
|
|
|
if stat.Size() > int64(maxLen) {
|
|
_, err = f.Seek(-int64(maxLen), io.SeekEnd)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to seek file: %w", err)
|
|
}
|
|
}
|
|
|
|
dataReader = io.LimitReader(f, int64(maxLen))
|
|
}
|
|
|
|
data, err = io.ReadAll(dataReader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read file: %w", err)
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// filters values from a slice
|
|
func Filter[T any](s []T, f func(T) bool) []T {
|
|
var r []T
|
|
for _, v := range s {
|
|
if f(v) {
|
|
r = append(r, v)
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
|
|
func ParseCommandLine(s string) ([]string, error) {
|
|
args := make([]string, 0)
|
|
var currentArg strings.Builder
|
|
inQuotes := false
|
|
escaped := false
|
|
|
|
for _, r := range s {
|
|
switch {
|
|
case escaped:
|
|
currentArg.WriteRune(r)
|
|
escaped = false
|
|
case r == '\\':
|
|
escaped = true
|
|
case r == '"':
|
|
inQuotes = !inQuotes
|
|
case unicode.IsSpace(r) && !inQuotes:
|
|
if currentArg.Len() > 0 {
|
|
args = append(args, currentArg.String())
|
|
currentArg.Reset()
|
|
}
|
|
default:
|
|
currentArg.WriteRune(r)
|
|
}
|
|
}
|
|
|
|
if escaped || inQuotes {
|
|
return nil, fmt.Errorf("unexpected end of string")
|
|
}
|
|
|
|
if currentArg.Len() > 0 {
|
|
args = append(args, currentArg.String())
|
|
}
|
|
|
|
return args, nil
|
|
}
|