From 5b1767465409ce17e342fa680bb82c5dbf5d03f5 Mon Sep 17 00:00:00 2001 From: Matthew Jaffee Date: Tue, 14 Nov 2023 23:05:31 -0600 Subject: [PATCH] .aicli directory containing history and config now you can set a system message in the config file so it gets set at startup also fixd races in the tests, using pipe instead of buffer --- pkg/aicli/cmd.go | 55 +++++++++++++++++++++++++++++++++++++++---- pkg/aicli/cmd_test.go | 24 ++++++++++--------- 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/pkg/aicli/cmd.go b/pkg/aicli/cmd.go index 06a5ccd..93853b7 100644 --- a/pkg/aicli/cmd.go +++ b/pkg/aicli/cmd.go @@ -1,6 +1,7 @@ package aicli import ( + "bufio" "fmt" "io" "os" @@ -27,6 +28,7 @@ type Cmd struct { stdout io.Writer stderr io.Writer + dotAICLIDir string historyPath string client AI @@ -56,6 +58,12 @@ func (cmd *Cmd) Run() error { if err := cmd.checkConfig(); err != nil { return errors.Wrap(err, "checking config") } + if err := cmd.setupConfigDir(); err != nil { + return errors.Wrap(err, "setting up config dir") + } + if err := cmd.readConfigFile(); err != nil { + return errors.Wrap(err, "reading config file") + } rl, err := readline.NewEx(&readline.Config{ Prompt: "> ", @@ -207,14 +215,51 @@ func (cmd *Cmd) printConfig() { fmt.Fprintf(cmd.stderr, "Verbose: %v\n", cmd.Verbose) } +func (cmd *Cmd) setupConfigDir() error { + if cmd.dotAICLIDir != "" { + return nil + } + home, err := os.UserHomeDir() + if err != nil { + return err + } + path := filepath.Join(home, ".aicli") + if err := os.MkdirAll(path, 0755); err != nil { + return err + } + cmd.dotAICLIDir = path + return nil +} + func (cmd *Cmd) getHistoryFilePath() string { if cmd.historyPath != "" { return cmd.historyPath } - home, err := os.UserHomeDir() - if err != nil { - // if we can't get a home dir, we'll use the local directory - return ".aicli_history" + return filepath.Join(cmd.dotAICLIDir, "history") +} + +func (cmd *Cmd) readConfigFile() error { + path := filepath.Join(cmd.dotAICLIDir, "config") + f, err := os.Open(path) + if os.IsNotExist(err) { + return nil + } else if err != nil { + return errors.Wrap(err, "opening file") } - return filepath.Join(home, ".aicli_history") + sc := bufio.NewScanner(f) + for sc.Scan() { + line := sc.Text() + if len(line) == 0 { + continue + } + if isMeta(line) { + cmd.handleMeta(line) + continue + } else if line[0] == '#' { + continue + } else { + return err + } + } + return nil } diff --git a/pkg/aicli/cmd_test.go b/pkg/aicli/cmd_test.go index 45aeb2f..8729a95 100644 --- a/pkg/aicli/cmd_test.go +++ b/pkg/aicli/cmd_test.go @@ -1,7 +1,6 @@ package aicli import ( - "bytes" "io" "testing" "time" @@ -12,13 +11,13 @@ import ( func TestCmd(t *testing.T) { cmd := NewCmd(&Echo{}) stdinr, stdinw := io.Pipe() - stdout := &bytes.Buffer{} - stderr := &bytes.Buffer{} + stdout, stdoutw := io.Pipe() + stderr, stderrw := io.Pipe() cmd.stdin = stdinr - cmd.stdout = stdout - cmd.stderr = stderr - cmd.historyPath = t.TempDir() + "/.aicli_history" + cmd.stdout = stdoutw + cmd.stderr = stderrw + cmd.dotAICLIDir = t.TempDir() cmd.OpenAIAPIKey = "blah" done := make(chan struct{}) @@ -27,8 +26,9 @@ func TestCmd(t *testing.T) { runErr = cmd.Run() close(done) }() + + time.Sleep(time.Millisecond) require.NoError(t, runErr) - // expect(t, stdout, []byte{0x20, 0x08, 0x1b, 0x5b, 0x36, 0x6e, 0x3e, 0x20}) _, _ = stdinw.Write([]byte("blah\n")) require.NoError(t, runErr) expect(t, stdout, []byte("blah\n")) @@ -64,22 +64,24 @@ func expect(t *testing.T, r io.Reader, exp []byte) { t.Helper() buffer := make([]byte, len(exp)*20) i := 0 + var tot int var n int var err error for { i++ - n, err = r.Read(buffer) + n, err = r.Read(buffer[tot:]) if err != nil && err.Error() != "EOF" { require.NoError(t, err) } - if n > 0 { + tot += n + if tot >= len(exp) { break } - if i > 100 { + if i > 20 { t.Fatal("spent too long waiting for output") } time.Sleep(time.Millisecond) } - require.Equal(t, exp, buffer[:n]) + require.Equal(t, exp, buffer[:tot]) }