Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
kardolus
GitHub Repository: kardolus/chatgpt-cli
Path: blob/main/agent/tools/files.go
3436 views
1
package tools
2
3
import (
4
"bytes"
5
"fmt"
6
"github.com/kardolus/chatgpt-cli/agent/utils"
7
8
"github.com/kardolus/chatgpt-cli/internal/fsio"
9
)
10
11
type PatchResult struct {
12
Hunks int
13
}
14
15
type ReplaceResult struct {
16
OccurrencesFound int
17
Replaced int
18
}
19
20
type Files interface {
21
ReadFile(path string) ([]byte, error)
22
WriteFile(path string, data []byte) error
23
PatchFile(path string, unifiedDiff []byte) (PatchResult, error)
24
ReplaceBytesInFile(path string, old, new []byte, n int) (ReplaceResult, error)
25
}
26
27
type FSIOFileOps struct {
28
r fsio.Reader
29
w fsio.Writer
30
}
31
32
func NewFSIOFileOps(r fsio.Reader, w fsio.Writer) FSIOFileOps {
33
return FSIOFileOps{r: r, w: w}
34
}
35
36
func (f FSIOFileOps) ReadFile(path string) ([]byte, error) {
37
return f.r.ReadFile(path)
38
}
39
40
func (f FSIOFileOps) WriteFile(path string, data []byte) error {
41
file, err := f.w.Create(path)
42
if err != nil {
43
return err
44
}
45
defer func() { _ = file.Close() }()
46
47
if err := f.w.Write(file, data); err != nil {
48
return err
49
}
50
51
if err := file.Close(); err != nil {
52
return fmt.Errorf("close %s: %w", path, err)
53
}
54
55
return nil
56
}
57
58
func (f FSIOFileOps) PatchFile(path string, unifiedDiff []byte) (PatchResult, error) {
59
// parse once for stats (and early validation)
60
hunks, err := utils.ParseUnifiedDiff(unifiedDiff)
61
if err != nil {
62
return PatchResult{}, err
63
}
64
65
orig, err := f.ReadFile(path)
66
if err != nil {
67
return PatchResult{}, err
68
}
69
70
patched, err := utils.ApplyUnifiedDiff(orig, unifiedDiff)
71
if err != nil {
72
return PatchResult{Hunks: len(hunks)}, fmt.Errorf("apply patch %s: %w", path, err)
73
}
74
75
if bytes.Equal(orig, patched) {
76
return PatchResult{Hunks: len(hunks)}, nil
77
}
78
79
if err := f.WriteFile(path, patched); err != nil {
80
return PatchResult{Hunks: len(hunks)}, err
81
}
82
83
return PatchResult{Hunks: len(hunks)}, nil
84
}
85
86
func (f FSIOFileOps) ReplaceBytesInFile(path string, old, new []byte, n int) (ReplaceResult, error) {
87
if len(old) == 0 {
88
return ReplaceResult{}, fmt.Errorf("replace %s: old pattern must be non-empty", path)
89
}
90
91
orig, err := f.ReadFile(path)
92
if err != nil {
93
return ReplaceResult{}, err
94
}
95
96
found := bytes.Count(orig, old)
97
if found == 0 {
98
return ReplaceResult{OccurrencesFound: 0, Replaced: 0}, fmt.Errorf("replace %s: pattern not found", path)
99
}
100
101
limit := n
102
if n <= 0 {
103
limit = -1 // replace all
104
}
105
106
updated := bytes.Replace(orig, old, new, limit)
107
if bytes.Equal(orig, updated) {
108
return ReplaceResult{OccurrencesFound: found, Replaced: 0}, fmt.Errorf("replace %s: no changes applied", path)
109
}
110
111
if err := f.WriteFile(path, updated); err != nil {
112
return ReplaceResult{OccurrencesFound: found, Replaced: 0}, err
113
}
114
115
replaced := found
116
if n > 0 && n < found {
117
replaced = n
118
}
119
120
return ReplaceResult{OccurrencesFound: found, Replaced: replaced}, nil
121
}
122
123