1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Paul Buetow
package repl
import (
"bufio"
"fmt"
"os"
"path/filepath"
)
// HistoryManager handles history file operations for the REPL.
// It provides methods to load, save, and manage command history with a maximum entry limit.
type HistoryManager struct {
historyFile string
baseDir string // override for testing; empty means use os.UserHomeDir()
maxEntries int
}
// NewHistoryManager creates a new history manager with the given file name.
// The history manager will store up to maxEntries (default: 1000) in the history file.
// The file is stored in the user's home directory.
//
// historyFile: the filename to use for history (without path)
// Returns a new HistoryManager instance
func NewHistoryManager(historyFile string) *HistoryManager {
return &HistoryManager{
historyFile: historyFile,
maxEntries: 1000, // Default max history entries
}
}
// WithBaseDir sets a custom base directory for the history file path.
// When baseDir is empty, Path() uses os.UserHomeDir().
// This is primarily useful for testing.
//
// baseDir: the directory to use instead of the home directory
// Returns the same HistoryManager for chaining
func (h *HistoryManager) WithBaseDir(baseDir string) *HistoryManager {
h.baseDir = baseDir
return h
}
// Path returns the absolute path to the history file.
// The history file is stored in the user's home directory, or in baseDir if set.
//
// Returns the full path to the history file, or empty string if the directory cannot be determined
func (h *HistoryManager) Path() string {
base := h.baseDir
if base == "" {
var err error
base, err = os.UserHomeDir()
if err != nil {
return ""
}
}
return filepath.Join(base, h.historyFile)
}
// Load reads history from the history file.
// It returns all entries from the file, or nil if the file doesn't exist.
//
// Returns a slice of history entries (each line is one entry), or nil on error
func (h *HistoryManager) Load() []string {
path := h.Path()
if path == "" {
return nil
}
file, err := os.Open(path)
if err != nil {
return nil
}
defer func() {
_ = file.Close()
}()
var history []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
history = append(history, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil
}
return history
}
// Save writes history to the history file, keeping only the most recent entries.
// It ensures the file doesn't grow unlimited by keeping only the last maxEntries.
// The function creates the file if it doesn't exist and truncates it if needed.
//
// history: the slice of history entries to save
// Returns an error if the file cannot be written
func (h *HistoryManager) Save(history []string) error {
path := h.Path()
if path == "" {
return nil
}
// Keep only last maxEntries entries to prevent unlimited growth
if len(history) > h.maxEntries {
history = history[len(history)-h.maxEntries:]
}
file, err := os.Create(path)
if err != nil {
return err
}
defer func() {
_ = file.Close()
}()
writer := bufio.NewWriter(file)
for _, entry := range history {
if _, err := writer.WriteString(entry + "\n"); err != nil {
return fmt.Errorf("failed to write history entry: %w", err)
}
}
if err := writer.Flush(); err != nil {
return fmt.Errorf("failed to flush history writer: %w", err)
}
return nil
}
|