Skip to content

Commit e57f409

Browse files
committed
Refactor internal APIs to use File struct
1 parent 5156178 commit e57f409

2 files changed

Lines changed: 103 additions & 99 deletions

File tree

find_replace.go

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ package main
33
import (
44
"errors"
55
"io"
6-
"io/fs"
76
"log"
87
"math/rand"
98
"os"
9+
"path/filepath"
1010
"strings"
1111
"time"
1212

@@ -20,6 +20,44 @@ type findReplace struct {
2020
replace string
2121
}
2222

23+
type File struct {
24+
Path string
25+
info os.FileInfo
26+
file os.File
27+
}
28+
29+
func NewFile(path string) *File {
30+
absPath, err := filepath.Abs(path)
31+
if err != nil {
32+
log.Fatalf("Unable to resolve absolute path of %v: %v", path, err)
33+
}
34+
println("got abs: " + absPath)
35+
return &File{Path: absPath}
36+
}
37+
38+
func (f *File) Base() string {
39+
return filepath.Base(f.Path)
40+
}
41+
42+
func (f *File) Dir() string {
43+
return filepath.Dir(f.Path)
44+
}
45+
46+
func (f *File) Info() os.FileInfo {
47+
if f.info == nil {
48+
stat, err := os.Stat(f.Path)
49+
if err != nil {
50+
log.Fatalf("Failed to stat %v: %v", f.Path, err)
51+
}
52+
f.info = stat
53+
}
54+
return f.info
55+
}
56+
57+
func (f *File) Mode() os.FileMode {
58+
return f.Info().Mode()
59+
}
60+
2361
// main processes command line arguments, builds the context struct, and begins
2462
// the process of walking the current working directory.
2563
//
@@ -47,21 +85,22 @@ func main() {
4785
// path.filepath.WalkDir() won't work here because it walks files
4886
// alphabetically, breadth-first (and you'd be renaming files that you
4987
// haven't explored yet).
50-
fr.WalkDir(".")
88+
89+
fr.WalkDir(NewFile("."))
5190
}
5291

5392
// Walks files in the directory given by dirName, which is a relative path to a
5493
// directory. Calls HandleFile for each file it finds, if it's not ignored.
55-
func (fr *findReplace) WalkDir(dirName string) {
94+
func (fr *findReplace) WalkDir(f *File) {
5695
// List the files in this directory.
57-
files, err := os.ReadDir(dirName)
96+
files, err := os.ReadDir(f.Info().Name())
5897
if err != nil {
5998
log.Fatalf("Unable to read directory: %v", err)
6099
}
61100

62101
for _, file := range files {
63102
if file.Name() != ".git" {
64-
fr.HandleFile(dirName, file)
103+
fr.HandleFile(NewFile(f.Path + string(os.PathSeparator) + file.Name()))
65104
}
66105
}
67106
}
@@ -70,48 +109,45 @@ func (fr *findReplace) WalkDir(dirName string) {
70109
// otherwise calls ReplaceContents for regular files. When either operation is
71110
// complete, the file is renamed (if necessary) since no subsequent operations
72111
// will need to access it again.
73-
func (fr *findReplace) HandleFile(dirName string, file fs.DirEntry) {
112+
func (fr *findReplace) HandleFile(f *File) {
74113
// If file is a directory, recurse immediately (depth-first).
75-
if file.IsDir() {
76-
fr.WalkDir(dirName + string(os.PathSeparator) + file.Name())
114+
if f.Info().IsDir() {
115+
fr.WalkDir(f)
77116
} else {
78117
// Replace the contents of regular files
79-
fr.ReplaceContents(dirName, file)
118+
fr.ReplaceContents(f)
80119
}
81120

82121
// Rename the file now that we're otherwise done with it
83-
fr.RenameFile(dirName, file)
122+
fr.RenameFile(f)
84123
}
85124

86125
// RenameFile renames a file if the destination file name does not already
87126
// exist.
88-
func (fr *findReplace) RenameFile(dirName string, file fs.DirEntry) {
89-
oldPath := dirName + string(os.PathSeparator) + file.Name()
90-
newBaseName := strings.Replace(file.Name(), fr.find, fr.replace, -1)
91-
newPath := dirName + string(os.PathSeparator) + newBaseName
127+
func (fr *findReplace) RenameFile(f *File) {
128+
newBaseName := strings.Replace(f.Base(), fr.find, fr.replace, -1)
129+
newPath := f.Dir() + string(os.PathSeparator) + newBaseName
92130

93-
if file.Name() != newBaseName {
131+
if f.Base() != newBaseName {
94132
if _, err := os.Stat(newPath); errors.Is(err, os.ErrNotExist) {
95-
log.Printf("Renaming %v to %v", oldPath, newBaseName)
96-
if err := os.Rename(oldPath, newPath); err != nil {
97-
log.Fatalf("Unable to rename %v to %v: %v", oldPath, newBaseName, err)
133+
log.Printf("Renaming %v to %v", f.Path, newBaseName)
134+
if err := os.Rename(f.Path, newPath); err != nil {
135+
log.Fatalf("Unable to rename %v to %v: %v", f.Path, newBaseName, err)
98136
}
99137
} else {
100-
log.Fatalf("Refusing to rename %v to %v: %v already exists", oldPath, newBaseName, newPath)
138+
log.Fatalf("Refusing to rename %v to %v: %v already exists", f.Path, newBaseName, newPath)
101139
}
102140
}
103141
}
104142

105143
// Replaces the contents of the given file, using the find & replace values in
106144
// context.
107-
func (fr *findReplace) ReplaceContents(dirName string, file fs.DirEntry) {
108-
path := dirName + string(os.PathSeparator) + file.Name()
109-
145+
func (fr *findReplace) ReplaceContents(f *File) {
110146
// Find & replace the contents of file.
111-
content := readFile(path)
147+
content := readFile(f.Path)
112148
if util.IsText([]byte(content)) && strings.Contains(content, fr.find) {
113149
newContent := strings.Replace(content, fr.find, fr.replace, -1)
114-
writeFile(dirName, file, newContent)
150+
writeFile(f, newContent)
115151
}
116152
}
117153

@@ -131,22 +167,15 @@ func readFile(path string) string {
131167

132168
// writeFile atomically write content to file by writing it to a temporary file
133169
// first, and then moving it to the destination, overwriting the original.
134-
func writeFile(dirName string, file fs.DirEntry, content string) {
135-
path := dirName + string(os.PathSeparator) + file.Name()
136-
137-
info, err := os.Stat(path)
138-
if err != nil {
139-
log.Fatalf("Error getting stats on %v: %v", path, err)
140-
}
141-
142-
tempName := dirName + string(os.PathSeparator) + randomString(20)
143-
if err := os.WriteFile(tempName, []byte(content), info.Mode()); err != nil {
144-
log.Fatalf("Error creating tempfile in %v: %v", dirName, err)
170+
func writeFile(f *File, content string) {
171+
tempName := f.Dir() + string(os.PathSeparator) + randomString(20)
172+
if err := os.WriteFile(tempName, []byte(content), f.Mode()); err != nil {
173+
log.Fatalf("Error creating tempfile in %v: %v", f.Dir(), err)
145174
}
146175

147-
log.Printf("Rewriting %v", path)
148-
if err := os.Rename(tempName, path); err != nil {
149-
log.Fatalf("Unable to atomically move temp file %v to %v: %v", tempName, path, err)
176+
log.Printf("Rewriting %v", f.Path)
177+
if err := os.Rename(tempName, f.Path); err != nil {
178+
log.Fatalf("Unable to atomically move temp file %v to %v: %v", tempName, f.Path, err)
150179
}
151180
}
152181

find_replace_test.go

Lines changed: 36 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ package main
22

33
import (
44
"errors"
5-
"io/fs"
65
"log"
76
"os"
8-
"path/filepath"
97
"strings"
108
"testing"
119
)
@@ -15,18 +13,11 @@ import (
1513
// used. If a baseName is not provided, a random file name is generated.
1614
// Returns the directory where the file was created, the file's directory
1715
// entry, and the actual name of the file.
18-
func createTestFile(path string, baseName string, content string) (string, fs.DirEntry, string) {
16+
func createTestFile(path string, baseName string, content string) *File {
1917
f, err := os.CreateTemp(path, baseName)
2018
if err != nil {
2119
log.Fatal(err)
2220
}
23-
24-
fileInfo, err := f.Stat()
25-
if err != nil {
26-
defer os.Remove(f.Name())
27-
log.Fatal(err)
28-
}
29-
3021
if _, err := f.Write([]byte(content)); err != nil {
3122
defer os.Remove(f.Name())
3223
log.Fatal(err)
@@ -36,23 +27,7 @@ func createTestFile(path string, baseName string, content string) (string, fs.Di
3627
log.Fatal(err)
3728
}
3829

39-
dirName := filepath.Dir(f.Name())
40-
41-
// There has to be a better way to get `fInfo` directly for `f`?
42-
files, err := os.ReadDir(dirName)
43-
if err != nil {
44-
defer os.Remove(f.Name())
45-
log.Fatal(err)
46-
}
47-
fInfo := files[0]
48-
for _, file := range files {
49-
if file.Name() == fileInfo.Name() {
50-
fInfo = file
51-
break
52-
}
53-
}
54-
55-
return dirName, fInfo, f.Name()
30+
return NewFile(f.Name())
5631
}
5732

5833
// assertPathExistsBeforeRename ensures that the file at the given path exists
@@ -81,16 +56,16 @@ func TestHandleFileWithFile(t *testing.T) {
8156
replace := "f"
8257
want := "alfa"
8358

84-
dirName, fInfo, path := createTestFile("", initial, initial)
85-
defer os.Remove(path)
86-
expectedName := strings.Replace(fInfo.Name(), find, replace, -1)
87-
expectedPath := dirName + string(os.PathSeparator) + expectedName
59+
f := createTestFile("", initial, initial)
60+
defer os.Remove(f.Path)
61+
expectedName := strings.Replace(f.Base(), find, replace, -1)
62+
expectedPath := f.Dir() + string(os.PathSeparator) + expectedName
8863
defer os.Remove(expectedPath)
8964
fr := findReplace{find: find, replace: replace}
9065

91-
assertPathExistsBeforeRename(t, path)
92-
fr.HandleFile(dirName, fInfo)
93-
assertPathExistsAfterRename(t, path, expectedPath)
66+
assertPathExistsBeforeRename(t, f.Path)
67+
fr.HandleFile(f)
68+
assertPathExistsAfterRename(t, f.Path, expectedPath)
9469

9570
got := readFile(expectedPath)
9671
if got != want {
@@ -103,16 +78,16 @@ func TestRenameFile(t *testing.T) {
10378
find := "ph"
10479
replace := "f"
10580

106-
dirName, fInfo, path := createTestFile("", initial, "")
107-
defer os.Remove(path)
108-
expectedName := strings.Replace(fInfo.Name(), find, replace, -1)
109-
expectedPath := dirName + string(os.PathSeparator) + expectedName
81+
f := createTestFile("", initial, "")
82+
defer os.Remove(f.Path)
83+
expectedName := strings.Replace(f.Base(), find, replace, -1)
84+
expectedPath := f.Dir() + string(os.PathSeparator) + expectedName
11085
defer os.Remove(expectedPath)
11186
fr := findReplace{find: find, replace: replace}
11287

113-
assertPathExistsBeforeRename(t, path)
114-
fr.RenameFile(dirName, fInfo)
115-
assertPathExistsAfterRename(t, path, expectedPath)
88+
assertPathExistsBeforeRename(t, f.Path)
89+
fr.RenameFile(f)
90+
assertPathExistsAfterRename(t, f.Path, expectedPath)
11691
}
11792

11893
// assertNewContentsOfFile ensures that the contents of the file at the given
@@ -130,11 +105,11 @@ func TestReplaceContents(t *testing.T) {
130105
replace := "f"
131106
want := "alfa"
132107

133-
dirName, fInfo, path := createTestFile("", "*", initial)
134-
defer os.Remove(path)
108+
f := createTestFile("", "*", initial)
109+
defer os.Remove(f.Path)
135110
fr := findReplace{find: find, replace: replace}
136-
fr.ReplaceContents(dirName, fInfo)
137-
assertNewContentsOfFile(t, path, initial, find, replace, want)
111+
fr.ReplaceContents(f)
112+
assertNewContentsOfFile(t, f.Path, initial, find, replace, want)
138113
}
139114

140115
func TestReplaceContentsEntireFile(t *testing.T) {
@@ -143,11 +118,11 @@ func TestReplaceContentsEntireFile(t *testing.T) {
143118
replace := "beta"
144119
want := "beta"
145120

146-
dirName, fInfo, path := createTestFile("", "*", initial)
147-
defer os.Remove(path)
121+
f := createTestFile("", "*", initial)
122+
defer os.Remove(f.Path)
148123
fr := findReplace{find: find, replace: replace}
149-
fr.ReplaceContents(dirName, fInfo)
150-
assertNewContentsOfFile(t, path, initial, find, replace, want)
124+
fr.ReplaceContents(f)
125+
assertNewContentsOfFile(t, f.Path, initial, find, replace, want)
151126
}
152127

153128
func TestReplaceContentsMultipleMatchesSingleLine(t *testing.T) {
@@ -156,11 +131,11 @@ func TestReplaceContentsMultipleMatchesSingleLine(t *testing.T) {
156131
replace := "f"
157132
want := "alfaalfa"
158133

159-
dirName, fInfo, path := createTestFile("", "*", initial)
160-
defer os.Remove(path)
134+
f := createTestFile("", "*", initial)
135+
defer os.Remove(f.Path)
161136
fr := findReplace{find: find, replace: replace}
162-
fr.ReplaceContents(dirName, fInfo)
163-
assertNewContentsOfFile(t, path, initial, find, replace, want)
137+
fr.ReplaceContents(f)
138+
assertNewContentsOfFile(t, f.Path, initial, find, replace, want)
164139
}
165140

166141
func TestReplaceContentsMultipleMatchesMultipleLines(t *testing.T) {
@@ -169,11 +144,11 @@ func TestReplaceContentsMultipleMatchesMultipleLines(t *testing.T) {
169144
replace := "f"
170145
want := "alfa\nalfa"
171146

172-
dirName, fInfo, path := createTestFile("", "*", initial)
173-
defer os.Remove(path)
147+
f := createTestFile("", "*", initial)
148+
defer os.Remove(f.Path)
174149
fr := findReplace{find: find, replace: replace}
175-
fr.ReplaceContents(dirName, fInfo)
176-
assertNewContentsOfFile(t, path, initial, find, replace, want)
150+
fr.ReplaceContents(f)
151+
assertNewContentsOfFile(t, f.Path, initial, find, replace, want)
177152
}
178153

179154
func TestReplaceContentsNoMatches(t *testing.T) {
@@ -182,11 +157,11 @@ func TestReplaceContentsNoMatches(t *testing.T) {
182157
replace := "xyz"
183158
want := "alpha"
184159

185-
dirName, fInfo, path := createTestFile("", "*", initial)
186-
defer os.Remove(path)
160+
f := createTestFile("", "*", initial)
161+
defer os.Remove(f.Path)
187162
fr := findReplace{find: find, replace: replace}
188-
fr.ReplaceContents(dirName, fInfo)
189-
assertNewContentsOfFile(t, path, initial, find, replace, want)
163+
fr.ReplaceContents(f)
164+
assertNewContentsOfFile(t, f.Path, initial, find, replace, want)
190165
}
191166

192167
// assertRandomStringLength ensures that the generated string matches the

0 commit comments

Comments
 (0)