mirror of
https://github.com/onyx-and-iris/exclude.git
synced 2026-04-16 14:13:39 +00:00
simplify del/reset commands by using readWriteTruncater interface.
remove the type assertions update the tests: the tests now check output as well as file contents separately.
This commit is contained in:
@@ -7,43 +7,50 @@ import (
|
|||||||
|
|
||||||
func TestRunAddCommand(t *testing.T) {
|
func TestRunAddCommand(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
existing string
|
existing string
|
||||||
args []string
|
args []string
|
||||||
expectedOutput string
|
expectedOut string
|
||||||
|
expectedContent string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Add new patterns",
|
name: "Add new patterns",
|
||||||
existing: "",
|
existing: "",
|
||||||
args: []string{"*.log", "temp/"},
|
args: []string{"*.log", "temp/"},
|
||||||
expectedOutput: "Added pattern '*.log' to the exclude file.\nAdded pattern 'temp/' to the exclude file.\n",
|
expectedOut: "Added pattern '*.log' to the exclude file.\nAdded pattern 'temp/' to the exclude file.\n",
|
||||||
|
expectedContent: "*.log\ntemp/\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Add duplicate patterns",
|
name: "Add duplicate patterns",
|
||||||
existing: "*.log\ntemp/\n",
|
existing: "*.log\ntemp/\n",
|
||||||
args: []string{"*.log", "temp/"},
|
args: []string{"*.log", "temp/"},
|
||||||
expectedOutput: "Pattern '*.log' already exists in the exclude file. Skipping.\nPattern 'temp/' already exists in the exclude file. Skipping.\n",
|
expectedOut: "Pattern '*.log' already exists in the exclude file. Skipping.\nPattern 'temp/' already exists in the exclude file. Skipping.\n",
|
||||||
|
expectedContent: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Add mix of new and duplicate patterns",
|
name: "Add mix of new and duplicate patterns",
|
||||||
existing: "*.log\n",
|
existing: "*.log\n",
|
||||||
args: []string{"*.log", "temp/"},
|
args: []string{"*.log", "temp/"},
|
||||||
expectedOutput: "Pattern '*.log' already exists in the exclude file. Skipping.\nAdded pattern 'temp/' to the exclude file.\n",
|
expectedOut: "Pattern '*.log' already exists in the exclude file. Skipping.\nAdded pattern 'temp/' to the exclude file.\n",
|
||||||
|
expectedContent: "temp/\n",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var out bytes.Buffer
|
||||||
f := bytes.NewBufferString(tt.existing)
|
f := bytes.NewBufferString(tt.existing)
|
||||||
|
|
||||||
err := runAddCommand(&buf, f, tt.args)
|
err := runAddCommand(&out, f, tt.args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("runAddCommand returned an error: %v", err)
|
t.Fatalf("runAddCommand returned an error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if buf.String() != tt.expectedOutput {
|
if out.String() != tt.expectedOut {
|
||||||
t.Errorf("Expected output:\n%s\nGot:\n%s", tt.expectedOutput, buf.String())
|
t.Errorf("Expected output:\n%s\nGot:\n%s", tt.expectedOut, out.String())
|
||||||
|
}
|
||||||
|
if f.String() != tt.expectedContent {
|
||||||
|
t.Errorf("Expected file content:\n%s\nGot:\n%s", tt.expectedContent, f.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
34
cmd/del.go
34
cmd/del.go
@@ -32,13 +32,8 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// runDelCommand deletes the specified pattern from the exclude file and writes the updated content back
|
// runDelCommand deletes the specified pattern from the exclude file and writes the updated content back
|
||||||
// It handles both file and in-memory buffer cases for testing
|
func runDelCommand(out io.Writer, f readWriteTruncater, pattern string) error {
|
||||||
func runDelCommand(out io.Writer, f any, pattern string) error {
|
existingPatterns, err := readExistingPatterns(f)
|
||||||
r, ok := f.(io.Reader)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("provided file does not support Reader")
|
|
||||||
}
|
|
||||||
existingPatterns, err := readExistingPatterns(r)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error reading existing patterns: %v", err)
|
return fmt.Errorf("error reading existing patterns: %v", err)
|
||||||
}
|
}
|
||||||
@@ -55,29 +50,18 @@ func runDelCommand(out io.Writer, f any, pattern string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var w io.Writer
|
if err := f.Truncate(0); err != nil {
|
||||||
if t, ok := f.(truncater); ok {
|
return fmt.Errorf("error truncating exclude file: %w", err)
|
||||||
if err := t.Truncate(0); err != nil {
|
}
|
||||||
return fmt.Errorf("error truncating exclude file: %w", err)
|
if _, err := f.Seek(0, 0); err != nil {
|
||||||
}
|
return fmt.Errorf("error seeking to the beginning of exclude file: %w", err)
|
||||||
if s, ok := f.(io.Seeker); ok {
|
|
||||||
if _, err := s.Seek(0, 0); err != nil {
|
|
||||||
return fmt.Errorf("error seeking to the beginning of exclude file: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w, _ = f.(io.Writer)
|
|
||||||
} else if buf, ok := f.(interface{ Reset() }); ok {
|
|
||||||
buf.Reset()
|
|
||||||
w, _ = f.(io.Writer)
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("provided file does not support writing")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeDefaultExcludeContent(w); err != nil {
|
if err := writeDefaultExcludeContent(f); err != nil {
|
||||||
return fmt.Errorf("error writing default exclude content: %w", err)
|
return fmt.Errorf("error writing default exclude content: %w", err)
|
||||||
}
|
}
|
||||||
for _, p := range updatedPatterns {
|
for _, p := range updatedPatterns {
|
||||||
if _, err := fmt.Fprintln(w, p); err != nil {
|
if _, err := fmt.Fprintln(f, p); err != nil {
|
||||||
return fmt.Errorf("error writing updated patterns to exclude file: %v", err)
|
return fmt.Errorf("error writing updated patterns to exclude file: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,40 +10,43 @@ func TestRunDelCommand(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
initialContent string
|
initialContent string
|
||||||
patternToDelete string
|
patternToDelete string
|
||||||
expectedOutput string
|
expectedOut string
|
||||||
expectedContent string
|
expectedContent string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Delete existing pattern",
|
name: "Delete existing pattern",
|
||||||
initialContent: defaultExcludeFileContent + "node_modules\n.DS_Store\n",
|
initialContent: defaultExcludeFileContent + "node_modules\n.DS_Store\n",
|
||||||
patternToDelete: "node_modules",
|
patternToDelete: "node_modules",
|
||||||
expectedOutput: defaultExcludeFileContent + ".DS_Store\n" + "Deleted pattern 'node_modules' from the exclude file.\n",
|
expectedOut: "Deleted pattern 'node_modules' from the exclude file.\n",
|
||||||
|
expectedContent: defaultExcludeFileContent + ".DS_Store\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Delete non-existing pattern",
|
name: "Delete non-existing pattern",
|
||||||
initialContent: defaultExcludeFileContent + "node_modules\n.DS_Store\n",
|
initialContent: defaultExcludeFileContent + "node_modules\n.DS_Store\n",
|
||||||
patternToDelete: "dist",
|
patternToDelete: "dist",
|
||||||
expectedOutput: "Pattern 'dist' not found in the exclude file. Nothing to delete.\n",
|
expectedOut: "Pattern 'dist' not found in the exclude file. Nothing to delete.\n",
|
||||||
|
expectedContent: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var out bytes.Buffer
|
||||||
buf.WriteString(tt.initialContent)
|
var f seekBuffer
|
||||||
|
f.WriteString(tt.initialContent)
|
||||||
|
|
||||||
err := runDelCommand(&buf, &buf, tt.patternToDelete)
|
err := runDelCommand(&out, &f, tt.patternToDelete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("runDelCommand returned an error: %v", err)
|
t.Fatalf("runDelCommand returned an error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if buf.String() != tt.expectedOutput {
|
if out.String() != tt.expectedOut {
|
||||||
t.Errorf(
|
t.Errorf("Expected output:\n%s\nGot:\n%s", tt.expectedOut, out.String())
|
||||||
"Expected output and content:\n%s\nGot:\n%s",
|
}
|
||||||
tt.expectedOutput,
|
if f.String() != tt.expectedContent {
|
||||||
buf.String(),
|
t.Errorf("Expected file content:\n%s\nGot:\n%s", tt.expectedContent, f.String())
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
26
cmd/reset.go
26
cmd/reset.go
@@ -27,33 +27,15 @@ func init() {
|
|||||||
RootCmd.AddCommand(resetCmd)
|
RootCmd.AddCommand(resetCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate and seek to beginning
|
|
||||||
type truncater interface{ Truncate(size int64) error }
|
|
||||||
|
|
||||||
// resetAndWriteExcludeFile truncates and resets the file, then writes the default content
|
// resetAndWriteExcludeFile truncates and resets the file, then writes the default content
|
||||||
func resetAndWriteExcludeFile(out io.Writer, f any) error {
|
func resetAndWriteExcludeFile(out io.Writer, f readWriteTruncater) error {
|
||||||
// Try to assert to io.ReadWriteSeeker for file operations
|
if err := f.Truncate(0); err != nil {
|
||||||
rws, ok := f.(io.ReadWriteSeeker)
|
|
||||||
if !ok {
|
|
||||||
// If not a file, try as io.Writer (for test buffers)
|
|
||||||
if w, ok := f.(io.Writer); ok {
|
|
||||||
return writeDefaultExcludeContent(w)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("provided file does not support ReadWriteSeeker or Writer")
|
|
||||||
}
|
|
||||||
|
|
||||||
t, ok := f.(truncater)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("provided file does not support Truncate")
|
|
||||||
}
|
|
||||||
if err := t.Truncate(0); err != nil {
|
|
||||||
return fmt.Errorf("error truncating exclude file: %w", err)
|
return fmt.Errorf("error truncating exclude file: %w", err)
|
||||||
}
|
}
|
||||||
if _, err := rws.Seek(0, 0); err != nil {
|
if _, err := f.Seek(0, 0); err != nil {
|
||||||
return fmt.Errorf("error seeking to the beginning of exclude file: %w", err)
|
return fmt.Errorf("error seeking to the beginning of exclude file: %w", err)
|
||||||
}
|
}
|
||||||
err := writeDefaultExcludeContent(rws)
|
if err := writeDefaultExcludeContent(f); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error writing default exclude content: %w", err)
|
return fmt.Errorf("error writing default exclude content: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(out, "Exclude file reset successfully.\n")
|
fmt.Fprintf(out, "Exclude file reset successfully.\n")
|
||||||
|
|||||||
@@ -2,27 +2,23 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRunResetCommand(t *testing.T) {
|
func TestRunResetCommand(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var out bytes.Buffer
|
||||||
|
var f seekBuffer
|
||||||
|
|
||||||
if err := resetAndWriteExcludeFile(&buf, &buf); err != nil {
|
if err := resetAndWriteExcludeFile(&out, &f); err != nil {
|
||||||
t.Fatalf("resetAndWriteExcludeFile failed: %v", err)
|
t.Fatalf("resetAndWriteExcludeFile failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resetContent, err := io.ReadAll(&buf)
|
if f.String() != defaultExcludeFileContent {
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to read from temp file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(resetContent) != defaultExcludeFileContent {
|
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"unexpected content after reset:\nGot:\n%s\nExpected:\n%s",
|
"unexpected content after reset:\nGot:\n%s\nExpected:\n%s",
|
||||||
string(resetContent),
|
f.String(),
|
||||||
defaultExcludeFileContent,
|
defaultExcludeFileContent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
cmd/testhelpers_test.go
Normal file
18
cmd/testhelpers_test.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import "bytes"
|
||||||
|
|
||||||
|
// seekBuffer wraps bytes.Buffer to satisfy the readWriteTruncater interface,
|
||||||
|
// allowing it to be used in place of *os.File in tests.
|
||||||
|
type seekBuffer struct {
|
||||||
|
bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *seekBuffer) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *seekBuffer) Truncate(size int64) error {
|
||||||
|
s.Buffer.Reset()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -7,6 +7,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// readWriteTruncater is the interface satisfied by *os.File that allows
|
||||||
|
// reading, writing, seeking, and truncating — the operations needed to rewrite the exclude file.
|
||||||
|
type readWriteTruncater interface {
|
||||||
|
io.ReadWriteSeeker
|
||||||
|
Truncate(size int64) error
|
||||||
|
}
|
||||||
|
|
||||||
// readExistingPatterns reads the existing patterns from the exclude file, ignoring comments and empty lines
|
// readExistingPatterns reads the existing patterns from the exclude file, ignoring comments and empty lines
|
||||||
func readExistingPatterns(f io.Reader) ([]string, error) {
|
func readExistingPatterns(f io.Reader) ([]string, error) {
|
||||||
var patterns []string
|
var patterns []string
|
||||||
|
|||||||
Reference in New Issue
Block a user