Skip to content

Commit 76f9cbd

Browse files
committed
feat: implement MergeMove, WriteJSON and Truncate methods
1 parent 9e30339 commit 76f9cbd

File tree

4 files changed

+298
-6
lines changed

4 files changed

+298
-6
lines changed

go.mod

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
module github.com/maa3x/ppath
22

3-
go 1.23.2
3+
go 1.23.5
4+
5+
toolchain go1.24.1
46

57
require github.com/shirou/gopsutil/v4 v4.24.12
68

9+
require github.com/mattn/go-isatty v0.0.20 // indirect
10+
711
require (
812
github.com/ebitengine/purego v0.8.1 // indirect
913
github.com/go-ole/go-ole v1.3.0 // indirect
14+
github.com/maa3x/errz v0.3.0
1015
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
1116
github.com/yusufpapurcu/wmi v1.2.4 // indirect
1217
golang.org/x/sys v0.29.0 // indirect

go.sum

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI
33
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
44
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
55
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
6+
github.com/maa3x/errz v0.3.0 h1:89/hGWDjs2PT9iLiPwS2F95PbEHNHZ8MDKuci/ltR8w=
7+
github.com/maa3x/errz v0.3.0/go.mod h1:G99s9Whr67XaXjGRS8cpu4faOBIJcTQEDkfcyBS6ZB8=
8+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
9+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
610
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
711
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
812
github.com/shirou/gopsutil/v4 v4.24.12 h1:qvePBOk20e0IKA1QXrIIU+jmk+zEiYVVx06WjBRlZo4=
@@ -12,5 +16,6 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
1216
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1317
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1418
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
19+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1520
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
1621
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

path.go

+96-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/sha1"
66
"crypto/sha256"
77
"encoding/hex"
8+
"encoding/json"
89
"errors"
910
"fmt"
1011
"hash"
@@ -17,6 +18,7 @@ import (
1718
"strings"
1819
"time"
1920

21+
"github.com/maa3x/errz"
2022
"github.com/shirou/gopsutil/v4/disk"
2123
)
2224

@@ -182,6 +184,70 @@ func (p Path) Copy(dst Path) error {
182184
return err
183185
}
184186

187+
// MergeMove moves a file or directory from path p to dst.
188+
// - If dst doesn't exist: performs a straight move
189+
// - If p is a file and dst is a directory: moves p into dst
190+
// - If p is a file and dst is a file: replaces dst with p
191+
// - If p is a directory and dst is a directory: recursively merges contents
192+
func (p Path) MergeMove(dst Path) error {
193+
if !p.Exists() {
194+
return errz.E("source file does not exist")
195+
}
196+
197+
if !dst.Exists() {
198+
if err := dst.Dir().MkdirIfNotExist(); err != nil {
199+
return errz.E(err, "create parent directory")
200+
}
201+
if err := os.Rename(string(p), string(dst)); err != nil {
202+
return errz.E(err, "rename file")
203+
}
204+
return nil
205+
}
206+
207+
if p.IsRegular() {
208+
if dst.IsDir() {
209+
dst = dst.JoinPath(p.Base())
210+
if err := os.Rename(string(p), string(dst)); err != nil {
211+
return errz.E(err, "rename file")
212+
}
213+
return nil
214+
}
215+
if !dst.IsRegular() {
216+
return errz.E("destination is not a regular file")
217+
}
218+
219+
if err := dst.Delete(); err != nil {
220+
return errz.E(err, "delete old file")
221+
}
222+
if err := os.Rename(string(p), string(dst)); err != nil {
223+
return errz.E(err, "rename file")
224+
}
225+
return nil
226+
}
227+
228+
if !p.IsDir() {
229+
return errz.E("source must be a regular file or directory")
230+
}
231+
if !dst.IsDir() {
232+
return errz.E("destination is not a directory")
233+
}
234+
235+
entries, err := p.ReadDir()
236+
if err != nil {
237+
return errz.E(err, "reading directory entries")
238+
}
239+
for i := range entries {
240+
entryName := entries[i].Name()
241+
srcPath := p.Join(entryName)
242+
dstPath := dst.Join(entryName)
243+
if err := srcPath.MergeMove(dstPath); err != nil {
244+
return errz.E(err, "move file", "name", entryName)
245+
}
246+
}
247+
248+
return p.Delete()
249+
}
250+
185251
func (p Path) Move(dst Path) error {
186252
if !p.IsExist() {
187253
return errors.New("source file does not exist")
@@ -194,6 +260,21 @@ func (p Path) Move(dst Path) error {
194260
return p.Rename(dst.String())
195261
}
196262

263+
func (p Path) Truncate() error {
264+
if p.IsRegular() {
265+
return errz.If(os.Truncate(string(p), 0), "truncate file")
266+
}
267+
268+
if p.IsDir() {
269+
if err := p.Delete(); err != nil {
270+
return errz.E(err, "delete directory")
271+
}
272+
return errz.If(p.MkdirIfNotExist(), "recreate directory")
273+
}
274+
275+
return errz.E("unsupported target")
276+
}
277+
197278
func (p Path) OpenFile(flag int, perm os.FileMode) (*os.File, error) {
198279
if p.IsDir() {
199280
return nil, errors.New("can not open a directory")
@@ -283,15 +364,24 @@ func (p Path) WriteFile(data []byte) error {
283364
return os.WriteFile(string(p), data, 0o644)
284365
}
285366

286-
func (p Path) WriteTo(w io.Writer) error {
367+
func (p Path) WriteJSON(v any) error {
368+
f, err := p.OpenFile(os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
369+
if err != nil {
370+
return errz.E(err, "open file")
371+
}
372+
defer f.Close()
373+
374+
return json.NewEncoder(f).Encode(v)
375+
}
376+
377+
func (p Path) WriteTo(w io.Writer) (int64, error) {
287378
src, err := p.Open()
288379
if err != nil {
289-
return err
380+
return 0, err
290381
}
291382
defer src.Close()
292383

293-
_, err = src.WriteTo(w)
294-
return err
384+
return src.WriteTo(w)
295385
}
296386

297387
func (p Path) WriteToPath(p2 Path) error {
@@ -301,7 +391,8 @@ func (p Path) WriteToPath(p2 Path) error {
301391
}
302392
defer dest.Close()
303393

304-
return p.WriteTo(dest)
394+
_, err = p.WriteTo(dest)
395+
return err
305396
}
306397

307398
func (p Path) IsAbs() bool {

path_test.go

+191
Original file line numberDiff line numberDiff line change
@@ -1326,3 +1326,194 @@ func TestHasQuery(t *testing.T) {
13261326
}
13271327
}
13281328
}
1329+
1330+
func TestMergeMove_SourceDoesNotExist(t *testing.T) {
1331+
src := New("nonexistent.txt")
1332+
dst := New("dst.txt")
1333+
err := src.MergeMove(dst)
1334+
if err == nil {
1335+
t.Fatal("expected error for non-existent source, got nil")
1336+
}
1337+
}
1338+
1339+
func TestMergeMove_MoveFileToNonExistentDst(t *testing.T) {
1340+
// Create a temporary file as source
1341+
tempDir := t.TempDir()
1342+
srcPath := New(filepath.Join(tempDir, "src.txt"))
1343+
dstPath := New(filepath.Join(tempDir, "dst.txt"))
1344+
1345+
// Write some content to the source file
1346+
if err := srcPath.WriteFile([]byte("merge move test")); err != nil {
1347+
t.Fatalf("WriteFile: %v", err)
1348+
}
1349+
1350+
// Ensure destination does not exist
1351+
os.Remove(dstPath.String())
1352+
1353+
// Call MergeMove, should perform a rename
1354+
if err := srcPath.MergeMove(dstPath); err != nil {
1355+
t.Fatalf("MergeMove: %v", err)
1356+
}
1357+
1358+
// Source should no longer exist and destination file should now exist
1359+
if srcPath.Exists() {
1360+
t.Errorf("expected source file to be moved (non-existent)")
1361+
}
1362+
if !dstPath.Exists() {
1363+
t.Errorf("expected destination file to exist")
1364+
}
1365+
}
1366+
1367+
func TestMergeMove_MoveFileToExistingDirectory(t *testing.T) {
1368+
// Create a temporary file as source and a directory as destination
1369+
tempDir := t.TempDir()
1370+
srcPath := New(filepath.Join(tempDir, "src.txt"))
1371+
dstDir := New(filepath.Join(tempDir, "destDir"))
1372+
1373+
// Write content to the source file
1374+
if err := srcPath.WriteFile([]byte("file to dir")); err != nil {
1375+
t.Fatalf("WriteFile: %v", err)
1376+
}
1377+
// Create the destination directory
1378+
if err := dstDir.MkdirIfNotExist(); err != nil {
1379+
t.Fatalf("MkdirIfNotExist: %v", err)
1380+
}
1381+
1382+
// Call MergeMove; it should move src file inside dst directory
1383+
if err := srcPath.MergeMove(dstDir); err != nil {
1384+
t.Fatalf("MergeMove: %v", err)
1385+
}
1386+
1387+
// The moved file should now be at dstDir joined with base name of srcPath.
1388+
movedFile := dstDir.JoinPath(srcPath.Base())
1389+
if srcPath.Exists() {
1390+
t.Errorf("expected source file to be moved")
1391+
}
1392+
if !movedFile.Exists() {
1393+
t.Errorf("expected moved file (%s) to exist", movedFile.String())
1394+
}
1395+
}
1396+
1397+
func TestMergeMove_MoveFileToExistingFile(t *testing.T) {
1398+
tempDir := t.TempDir()
1399+
// Create a source and destination file paths
1400+
srcPath := New(filepath.Join(tempDir, "src.txt"))
1401+
dstPath := New(filepath.Join(tempDir, "dst.txt"))
1402+
1403+
// Write different contents to source and destination
1404+
srcContent := []byte("source content")
1405+
dstContent := []byte("old destination")
1406+
1407+
if err := srcPath.WriteFile(srcContent); err != nil {
1408+
t.Fatalf("WriteFile src: %v", err)
1409+
}
1410+
if err := dstPath.WriteFile(dstContent); err != nil {
1411+
t.Fatalf("WriteFile dst: %v", err)
1412+
}
1413+
1414+
// Call MergeMove: it should delete the destination and rename the source to dstPath.
1415+
if err := srcPath.MergeMove(dstPath); err != nil {
1416+
t.Fatalf("MergeMove: %v", err)
1417+
}
1418+
1419+
// Source should not exist, and destination should have source content.
1420+
if srcPath.Exists() {
1421+
t.Errorf("expected source file to be moved")
1422+
}
1423+
if !dstPath.Exists() {
1424+
t.Errorf("expected destination file to exist")
1425+
}
1426+
result, err := dstPath.ReadFile()
1427+
if err != nil {
1428+
t.Fatalf("ReadFile: %v", err)
1429+
}
1430+
if string(result) != string(srcContent) {
1431+
t.Errorf("expected destination file content %q, got %q", srcContent, result)
1432+
}
1433+
}
1434+
1435+
func TestMergeMove_MergeMoveDirectory(t *testing.T) {
1436+
tempDir := t.TempDir()
1437+
// Create source directory with multiple files
1438+
srcDir := New(filepath.Join(tempDir, "srcDir"))
1439+
if err := srcDir.MkdirIfNotExist(); err != nil {
1440+
t.Fatalf("MkdirIfNotExist srcDir: %v", err)
1441+
}
1442+
1443+
// Create a couple of files inside source directory
1444+
file1 := srcDir.Join("file1.txt")
1445+
file2 := srcDir.Join("file2.txt")
1446+
if err := file1.WriteFile([]byte("file1 content")); err != nil {
1447+
t.Fatalf("WriteFile file1: %v", err)
1448+
}
1449+
if err := file2.WriteFile([]byte("file2 content")); err != nil {
1450+
t.Fatalf("WriteFile file2: %v", err)
1451+
}
1452+
1453+
// Create destination directory where the merge will occur; it already exists
1454+
dstDir := New(filepath.Join(tempDir, "dstDir"))
1455+
if err := dstDir.MkdirIfNotExist(); err != nil {
1456+
t.Fatalf("MkdirIfNotExist dstDir: %v", err)
1457+
}
1458+
1459+
// MergeMove srcDir to dstDir; expect the files to be moved into dstDir
1460+
if err := srcDir.MergeMove(dstDir); err != nil {
1461+
t.Fatalf("MergeMove directory: %v", err)
1462+
}
1463+
1464+
// Source directory should be deleted.
1465+
if srcDir.Exists() {
1466+
t.Errorf("expected source directory to be deleted")
1467+
}
1468+
1469+
// Files should now exist in dstDir.
1470+
movedFile1 := dstDir.Join("file1.txt")
1471+
movedFile2 := dstDir.Join("file2.txt")
1472+
if !movedFile1.Exists() {
1473+
t.Errorf("expected moved file1 (%s) to exist", movedFile1.String())
1474+
}
1475+
if !movedFile2.Exists() {
1476+
t.Errorf("expected moved file2 (%s) to exist", movedFile2.String())
1477+
}
1478+
1479+
// Verify contents
1480+
f1Content, err := movedFile1.ReadFile()
1481+
if err != nil {
1482+
t.Fatalf("ReadFile file1: %v", err)
1483+
}
1484+
if string(f1Content) != "file1 content" {
1485+
t.Errorf("expected file1 content %q, got %q", "file1 content", f1Content)
1486+
}
1487+
f2Content, err := movedFile2.ReadFile()
1488+
if err != nil {
1489+
t.Fatalf("ReadFile file2: %v", err)
1490+
}
1491+
if string(f2Content) != "file2 content" {
1492+
t.Errorf("expected file2 content %q, got %q", "file2 content", f2Content)
1493+
}
1494+
}
1495+
1496+
func TestMergeMove_DirectoryToNonDirectory(t *testing.T) {
1497+
tempDir := t.TempDir()
1498+
// Create source directory with one file
1499+
srcDir := New(filepath.Join(tempDir, "srcDir"))
1500+
if err := srcDir.MkdirIfNotExist(); err != nil {
1501+
t.Fatalf("MkdirIfNotExist srcDir: %v", err)
1502+
}
1503+
srcFile := srcDir.Join("file.txt")
1504+
if err := srcFile.WriteFile([]byte("content")); err != nil {
1505+
t.Fatalf("WriteFile: %v", err)
1506+
}
1507+
1508+
// Create a destination regular file
1509+
dstPath := New(filepath.Join(tempDir, "dst.txt"))
1510+
if err := dstPath.WriteFile([]byte("destination")); err != nil {
1511+
t.Fatalf("WriteFile dst: %v", err)
1512+
}
1513+
1514+
// Attempting to merge move a directory into a non-directory should error.
1515+
err := srcDir.MergeMove(dstPath)
1516+
if err == nil {
1517+
t.Fatal("expected error when moving directory to non-directory, got nil")
1518+
}
1519+
}

0 commit comments

Comments
 (0)