refactor environment utilities and file tools

This commit is contained in:
Ollie Agent 2026-08-16 18:20:50 +02:00
parent 81933e5281
commit aca5546ea5
12 changed files with 214 additions and 103 deletions

View File

@ -13,9 +13,9 @@ import (
"ollie/cmd/olliesrv/internal/agent"
"ollie/cmd/olliesrv/internal/fs"
"ollie/env"
olog "ollie/log"
"ollie/paths"
"ollie/util"
"9fans.net/go/plan9/client"
@ -41,7 +41,7 @@ func main() {
}
func runServer(sockPath string) {
env.EnsureDefaults()
util.EnsureEnv()
// Remove stale sockets from this and prior runs
if _, err := os.Stat(sockPath); err == nil {

View File

@ -28,8 +28,8 @@ import (
"ollie/cmd/toolsrv/internal/registry"
"ollie/cmd/toolsrv/internal/server"
"ollie/env"
lib9p "ollie/lib9p/client"
"ollie/util"
olog "ollie/log"
"ollie/paths"
@ -73,7 +73,7 @@ func main() {
defer sink.Flush()
logger = sink.NewLogger("toolsrv")
env.EnsureDefaults()
util.EnsureEnv()
// Prepend our bin dir to PATH so landrun is found (deployed alongside us).
home, _ := os.UserHomeDir()

54
env/env.go vendored
View File

@ -1,52 +1,8 @@
// Package env manages the daemon-global environment for ollie.
// It loads $XDG_CONFIG_HOME/ollie/env and provides defaults.
// Package env is retained for backward compatibility.
// New code should import ollie/util.
package env
import (
"bufio"
"os"
"path/filepath"
"strings"
import "ollie/util"
"ollie/paths"
)
// EnsureDefaults loads $XDG_CONFIG_HOME/ollie/env and sets default values for any
// OLLIE_* vars not already present in the process environment.
func EnsureDefaults() {
loadFile(filepath.Join(paths.CfgDir(), "env"))
}
// loadFile reads KEY=VALUE pairs from path and sets any key not already
// present in the environment. Lines beginning with # and blank lines are
// ignored. Missing file is silently ignored.
func loadFile(path string) {
f, err := os.Open(path)
if err != nil {
return
}
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
k, v, ok := strings.Cut(line, "=")
if !ok {
continue
}
k = strings.TrimSpace(k)
if i := strings.Index(v, " #"); i >= 0 {
v = v[:i]
}
v = strings.TrimSpace(v)
if len(v) >= 2 && (v[0] == '"' && v[len(v)-1] == '"' || v[0] == '\'' && v[len(v)-1] == '\'') {
v = v[1 : len(v)-1]
}
if k != "" && os.Getenv(k) == "" {
os.Setenv(k, paths.ExpandHome(os.ExpandEnv(v))) //nolint:errcheck
}
}
}
// EnsureDefaults loads the configured environment file and sets defaults.
func EnsureDefaults() { util.EnsureEnv() }

22
env/env_test.go vendored
View File

@ -1,22 +0,0 @@
package env
import (
"os"
"testing"
)
func TestEnsureDefaults(t *testing.T) {
EnsureDefaults()
}
func TestLoadFile(t *testing.T) {
dir := t.TempDir()
f := dir + "/env"
os.WriteFile(f, []byte("OLLIE_TEST_LOAD=testval\n"), 0600)
t.Setenv("OLLIE_TEST_LOAD", "")
os.Unsetenv("OLLIE_TEST_LOAD")
loadFile(f)
if got := os.Getenv("OLLIE_TEST_LOAD"); got != "testval" {
t.Errorf("loadFile: OLLIE_TEST_LOAD = %q, want testval", got)
}
}

View File

@ -36,12 +36,12 @@ type fidHandle struct {
var (
handleMu sync.Mutex
handles = make(map[C.int]*connHandle)
handles = make(map[C.int]*connHandle)
nextHandle C.int = 1
fidMu sync.Mutex
fids = make(map[C.int]*fidHandle)
nextFid C.int = 1
fidMu sync.Mutex
fids = make(map[C.int]*fidHandle)
nextFid C.int = 1
)
func allocHandle(h *connHandle) C.int {

View File

@ -8,7 +8,7 @@ import (
// NewUUID generates a random UUIDv4 string.
func NewUUID() string {
var b [16]byte
rand.Read(b[:]) //nolint:errcheck
rand.Read(b[:]) //nolint:errcheck
b[6] = (b[6] & 0x0f) | 0x40 // version 4
b[8] = (b[8] & 0x3f) | 0x80 // variant 10xx
return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",

View File

@ -9,7 +9,8 @@ import (
)
type args struct {
Path, Query string `json:"path"`
Path string `json:"path"`
Query string `json:"query"`
}
func main() {

View File

@ -162,19 +162,126 @@ func Read(m map[string]any) (string, error) {
}
return status + "\n" + strings.TrimSuffix(b.String(), "\n"), nil
}
type diffOp struct {
kind byte
line string
}
func Diff(path, old, new string) string {
a := strings.SplitAfter(old, "\n")
b := strings.SplitAfter(new, "\n")
oldLines := diffLines(old)
newLines := diffLines(new)
ops := diffOps(oldLines, newLines)
var changed []int
for i, op := range ops {
if op.kind != ' ' {
changed = append(changed, i)
}
}
if len(changed) == 0 {
return ""
}
const context = 3
type hunk struct{ start, end int }
var hunks []hunk
for _, i := range changed {
start := i - context
if start < 0 {
start = 0
}
end := i + context + 1
if len(hunks) > 0 && start <= hunks[len(hunks)-1].end {
if end > hunks[len(hunks)-1].end {
hunks[len(hunks)-1].end = end
}
continue
}
hunks = append(hunks, hunk{start: start, end: end})
}
for i := range hunks {
if hunks[i].end > len(ops) {
hunks[i].end = len(ops)
}
}
var out strings.Builder
fmt.Fprintf(&out, "--- %s\n+++ %s\n", path, path)
for _, x := range a {
fmt.Fprintf(&out, "-%s", x)
}
for _, x := range b {
fmt.Fprintf(&out, "+%s", x)
for _, h := range hunks {
oldStart, newStart := 1, 1
for _, op := range ops[:h.start] {
if op.kind != '+' {
oldStart++
}
if op.kind != '-' {
newStart++
}
}
oldCount, newCount := 0, 0
for _, op := range ops[h.start:h.end] {
if op.kind != '+' {
oldCount++
}
if op.kind != '-' {
newCount++
}
}
fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", oldStart, oldCount, newStart, newCount)
for _, op := range ops[h.start:h.end] {
out.WriteByte(op.kind)
out.WriteString(op.line)
if !strings.HasSuffix(op.line, "\n") {
out.WriteByte('\n')
}
}
}
return out.String()
}
func diffLines(s string) []string {
if s == "" {
return nil
}
lines := strings.SplitAfter(s, "\n")
if lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
}
return lines
}
func diffOps(a, b []string) []diffOp {
lcs := make([][]int, len(a)+1)
for i := range lcs {
lcs[i] = make([]int, len(b)+1)
}
for i := len(a) - 1; i >= 0; i-- {
for j := len(b) - 1; j >= 0; j-- {
if a[i] == b[j] {
lcs[i][j] = lcs[i+1][j+1] + 1
} else if lcs[i+1][j] >= lcs[i][j+1] {
lcs[i][j] = lcs[i+1][j]
} else {
lcs[i][j] = lcs[i][j+1]
}
}
}
ops := make([]diffOp, 0, len(a)+len(b)-lcs[0][0])
for i, j := 0, 0; i < len(a) || j < len(b); {
if i < len(a) && j < len(b) && a[i] == b[j] {
ops = append(ops, diffOp{' ', a[i]})
i++
j++
} else if j == len(b) || (i < len(a) && lcs[i+1][j] >= lcs[i][j+1]) {
ops = append(ops, diffOp{'-', a[i]})
i++
} else {
ops = append(ops, diffOp{'+', b[j]})
j++
}
}
return ops
}
func Edit(m map[string]any) (string, error) {
p, e := Arg(m, "path")
if e != nil {

View File

@ -45,6 +45,27 @@ func TestWriteAndEdit(t *testing.T) {
t.Fatal(err)
}
}
func TestDiffUsesContextHunks(t *testing.T) {
old := "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n"
new := "one\ntwo\nTHREE\nfour\nfive\nsix\nseven\nEIGHT\n"
got := Diff("x", old, new)
want := "--- x\n+++ x\n@@ -1,8 +1,8 @@\n one\n two\n-three\n+THREE\n four\n five\n six\n seven\n-eight\n+EIGHT\n"
if got != want {
t.Fatalf("diff = %q, want %q", got, want)
}
}
func TestDiffOmitsUnchangedFileContent(t *testing.T) {
old := "line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\nline 10\n"
new := "line 1\nline 2\nline 3\nline 4\nchanged\nline 6\nline 7\nline 8\nline 9\nline 10\n"
got := Diff("x", old, new)
if strings.Contains(got, "line 1\n") || strings.Contains(got, "line 10\n") {
t.Fatalf("diff contains distant unchanged content: %q", got)
}
if !strings.Contains(got, "-line 5\n+changed\n line 6\n") {
t.Fatalf("diff lacks changed hunk: %q", got)
}
}
func TestGlobAndGrep(t *testing.T) {
d := t.TempDir()
os.MkdirAll(filepath.Join(d, "sub"), 0755)

View File

@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"io"
"strings"
"syscall"
"9fans.net/go/plan9"
@ -158,18 +159,19 @@ func (c *Conn) CallTool(ctx context.Context, name string, args json.RawMessage)
defer fid.Close()
// Convert JSON args to key=value format
var argMap map[string]interface{}
var argMap map[string]any
if err := json.Unmarshal(args, &argMap); err != nil {
argMap = make(map[string]interface{})
argMap = make(map[string]any)
}
payload := fmt.Sprintf("token=%s\ntool=%s\nagent=%s\n", c.token, name, c.agentID)
var payload strings.Builder
fmt.Fprintf(&payload, "token=%s\ntool=%s\nagent=%s\n", c.token, name, c.agentID)
for k, v := range argMap {
escaped := escapeValue(fmt.Sprintf("%v", v))
payload += fmt.Sprintf("%s=%s\n", k, escaped)
fmt.Fprintf(&payload, "%s=%s\n", k, escaped)
}
if _, err := fid.Write([]byte(payload)); err != nil {
if _, err := fid.Write([]byte(payload.String())); err != nil {
if ctx.Err() != nil {
return nil, ctx.Err()
}
@ -209,18 +211,19 @@ func (c *Conn) CallToolBackground(name string, args json.RawMessage) (int, error
}
defer fid.Close()
var argMap map[string]interface{}
var argMap map[string]any
if err := json.Unmarshal(args, &argMap); err != nil {
argMap = make(map[string]interface{})
argMap = make(map[string]any)
}
payload := fmt.Sprintf("token=%s\ntool=%s\nagent=%s\n", c.token, name, c.agentID)
var payload strings.Builder
fmt.Fprintf(&payload, "token=%s\ntool=%s\nagent=%s\n", c.token, name, c.agentID)
for k, v := range argMap {
escaped := escapeValue(fmt.Sprintf("%v", v))
payload += fmt.Sprintf("%s=%s\n", k, escaped)
fmt.Fprintf(&payload, "%s=%s\n", k, escaped)
}
if _, err := fid.Write([]byte(payload)); err != nil {
if _, err := fid.Write([]byte(payload.String())); err != nil {
return 0, fmt.Errorf("write: %w", err)
}
@ -319,7 +322,7 @@ func (c *Conn) SetEnv(key, value string) error {
return err
}
defer fid.Close()
_, err = fid.Write([]byte(fmt.Sprintf("env %s=%s\n", key, value)))
_, err = fmt.Fprintf(fid, "env %s=%s\n", key, value)
return err
}
@ -330,7 +333,7 @@ func (c *Conn) SetCWD(dir string) error {
return err
}
defer fid.Close()
_, err = fid.Write([]byte(fmt.Sprintf("cwd %s\n", dir)))
_, err = fmt.Fprintf(fid, "cwd %s\n", dir)
return err
}
@ -447,7 +450,7 @@ func (c *Conn) SignalDetached(pid int, sig syscall.Signal) error {
return err
}
defer fid.Close()
_, err = fid.Write([]byte(fmt.Sprintf("signal %d\n", sig)))
_, err = fmt.Fprintf(fid, "signal %d\n", sig)
return err
}

45
util/env.go Normal file
View File

@ -0,0 +1,45 @@
// Package util manages the daemon-global environment for ollie.
// It loads $XDG_CONFIG_HOME/ollie/env and provides defaults.
package util
import (
"bufio"
"os"
"path/filepath"
"strings"
"ollie/paths"
)
// EnsureEnv loads $XDG_CONFIG_HOME/ollie/env and sets default values for any
// OLLIE_* vars not already present in the process environment.
func EnsureEnv() {
f, err := os.Open(filepath.Join(paths.CfgDir(), "env"))
if err != nil {
return
}
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
k, v, ok := strings.Cut(line, "=")
if !ok {
continue
}
k = strings.TrimSpace(k)
if i := strings.Index(v, " #"); i >= 0 {
v = v[:i]
}
v = strings.TrimSpace(v)
if len(v) >= 2 && (v[0] == '"' && v[len(v)-1] == '"' || v[0] == '\'' && v[len(v)-1] == '\'') {
v = v[1 : len(v)-1]
}
if k != "" && os.Getenv(k) == "" {
os.Setenv(k, paths.ExpandHome(os.ExpandEnv(v))) //nolint:errcheck
}
}
}

View File

@ -52,8 +52,8 @@ type FsNodeDecl struct {
Stat func() os.FileInfo
// Twalk — children (implies directory; at most one set).
Children []FsNodeDecl // static, known at compile time
Bindings func() ([]FsNodeDecl, error) // Each: dynamic children
Children []FsNodeDecl // static, known at compile time
Bindings func() ([]FsNodeDecl, error) // Each: dynamic children
// Tread (non-blocking) + Twrite.
Read func() ([]byte, error)