Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions internal/postprocessing/fileops.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"slices"
"strings"

"github.com/googleapis/librarian/internal/config"
"github.com/googleapis/librarian/internal/filesystem"
)

Expand All @@ -38,14 +39,10 @@ var (

// errEmptyPattern is returned when the regex pattern to replace is empty.
errEmptyPattern = errors.New("regex pattern cannot be empty")
)

// CopyFile copies a single file from the src path to the dst path.
// It acts as a wrapper around filesystem.CopyFile to provide a unified
// interface for all postprocessing file operations.
func CopyFile(src, dst string) error {
return filesystem.CopyFile(src, dst)
}
// errSameSourceAndDestination is returned when Src and Dst resolve to the same path.
errSameSourceAndDestination = errors.New("src and dst must be different")
)

// Replace finds and replaces exact text in a file.
// It returns an error if the target file does not exist or if the text is not found.
Expand Down Expand Up @@ -90,6 +87,24 @@ func ReplaceRegex(path, pattern, replacement string) error {
return os.WriteFile(path, newContent, 0644)
}

// CopyFiles copies files specified by copyConfigs from src to dst inside outDir.
func CopyFiles(outDir string, copyConfigs []config.CopyConfig) error {
for _, c := range copyConfigs {
srcAbs := filepath.Join(outDir, c.Src)
dstAbs := filepath.Join(outDir, c.Dst)
if srcAbs == dstAbs {
return fmt.Errorf("invalid copy config for %s: %w", c.Src, errSameSourceAndDestination)
}
if err := os.MkdirAll(filepath.Dir(dstAbs), 0755); err != nil {
return fmt.Errorf("failed to create directory for %s: %w", c.Dst, err)
}
if err := filesystem.CopyFile(srcAbs, dstAbs); err != nil {
return fmt.Errorf("failed to copy file from %s to %s: %w", c.Src, c.Dst, err)
}
}
return nil
}
Comment thread
yangyzs marked this conversation as resolved.

// RemoveFiles removes all files in outDir matching the given patterns (exact paths or globs).
func RemoveFiles(outDir string, removePatterns []string) error {
for _, rem := range removePatterns {
Expand Down
113 changes: 82 additions & 31 deletions internal/postprocessing/fileops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,39 +24,9 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/googleapis/librarian/internal/config"
)

func TestCopyFile(t *testing.T) {
dir := t.TempDir()
srcPath := filepath.Join(dir, "src.txt")
dstPath := filepath.Join(dir, "dst.txt")
content := "hello copy"
if err := os.WriteFile(srcPath, []byte(content), 0644); err != nil {
t.Fatal(err)
}
if err := CopyFile(srcPath, dstPath); err != nil {
t.Fatal(err)
}
gotBytes, err := os.ReadFile(dstPath)
if err != nil {
t.Fatal(err)
}
got := string(gotBytes)
if diff := cmp.Diff(content, got); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
}
}

func TestCopyFile_Error(t *testing.T) {
dir := t.TempDir()
srcPath := filepath.Join(dir, "nonexistent.txt")
dstPath := filepath.Join(dir, "dst.txt")
err := CopyFile(srcPath, dstPath)
if !errors.Is(err, fs.ErrNotExist) {
t.Errorf("CopyFile() returned unexpected error: got %v, want %v", err, fs.ErrNotExist)
}
}

func TestReplace(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.txt")
Expand Down Expand Up @@ -408,3 +378,84 @@ func readDirFiles(t *testing.T, dir string) map[string]string {
}
return gotFiles
}

func TestCopyFiles(t *testing.T) {
for _, test := range []struct {
name string
files map[string]string
configs []config.CopyConfig
wantFiles map[string]string
}{
{
name: "single file copy",
files: map[string]string{"src.txt": "hello"},
configs: []config.CopyConfig{
{Src: "src.txt", Dst: "dst.txt"},
},
wantFiles: map[string]string{"src.txt": "hello", "dst.txt": "hello"},
},
{
name: "multiple copies of same source",
files: map[string]string{"src.txt": "hello"},
configs: []config.CopyConfig{
{Src: "src.txt", Dst: "copied1.txt"},
{Src: "src.txt", Dst: "copied2.txt"},
},
wantFiles: map[string]string{"src.txt": "hello", "copied1.txt": "hello", "copied2.txt": "hello"},
},
{
name: "nested directory copy",
files: map[string]string{"sub/src.txt": "nested content"},
configs: []config.CopyConfig{
{Src: "sub/src.txt", Dst: "out/dst.txt"},
},
wantFiles: map[string]string{"sub/src.txt": "nested content", "out/dst.txt": "nested content"},
},
} {
t.Run(test.name, func(t *testing.T) {
dir := t.TempDir()
createFiles(t, dir, test.files)
if err := CopyFiles(dir, test.configs); err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(test.wantFiles, readDirFiles(t, dir)); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
}
})
}
}

func TestCopyFiles_Error(t *testing.T) {
for _, test := range []struct {
name string
files map[string]string
configs []config.CopyConfig
wantErr error
}{
{
name: "nonexistent source file",
files: map[string]string{},
configs: []config.CopyConfig{
{Src: "nonexistent.txt", Dst: "dst.txt"},
},
wantErr: fs.ErrNotExist,
},
{
name: "same source and destination",
files: map[string]string{"foo.txt": "hello"},
configs: []config.CopyConfig{
{Src: "foo.txt", Dst: "foo.txt"},
},
wantErr: errSameSourceAndDestination,
},
} {
t.Run(test.name, func(t *testing.T) {
dir := t.TempDir()
createFiles(t, dir, test.files)
err := CopyFiles(dir, test.configs)
if !errors.Is(err, test.wantErr) {
t.Errorf("CopyFiles() error = %v, want %v", err, test.wantErr)
}
})
}
}
Comment thread
yangyzs marked this conversation as resolved.
Loading