Repository: google/yamlfmt (go)

Issue:
Failure if folders with `.yml` and `.yaml` in the name exist
When running the tool, it will fail if there is a folder with `.yml` or `.yaml` in the name.

Maybe we should we skip folders when attempting for format yaml files?

Pull request:
paths: do not include directories in formatting
Closes #57 

If a directory was named such that it matched the include pattern (in most circumstances, if it was named ending in `.yml`/`.yaml`) then yamlfmt would try to read it and format it which would fail because it's a directory. This PR adds a stat check to ignore all directories. Also adds tests for this package which did not previously exist.

Patch:
diff --git a/internal/paths/paths.go b/internal/paths/paths.go
index 6ab3821..426e2bd 100644
--- a/internal/paths/paths.go
+++ b/internal/paths/paths.go
@@ -15,6 +15,8 @@
 package paths
 
 import (
+	"os"
+
 	"github.com/bmatcuk/doublestar/v4"
 )
 
@@ -50,6 +52,10 @@ func CollectPathsToFormat(include, exclude []string) ([]string, error) {
 	}
 	pathsToFormat := []string{}
 	for path := range pathsToFormatSet {
+		info, err := os.Stat(path)
+		if err != nil || info.IsDir() {
+			continue
+		}
 		pathsToFormat = append(pathsToFormat, path)
 	}
 	return pathsToFormat, nil


Test patch:
diff --git a/internal/paths/paths_test.go b/internal/paths/paths_test.go
new file mode 100644
index 0000000..0b0b17b
--- /dev/null
+++ b/internal/paths/paths_test.go
@@ -0,0 +1,138 @@
+package paths_test
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+
+	"github.com/google/yamlfmt/internal/paths"
+)
+
+type dummyPath struct {
+	path     string
+	filename string
+	isDir    bool
+}
+
+func (p *dummyPath) Create() error {
+	file := p.fullPath()
+	var err error
+	if p.isDir {
+		err = os.Mkdir(file, 0x0700)
+	} else {
+		err = os.WriteFile(file, []byte{}, 0x0700)
+	}
+	return err
+}
+
+func (p *dummyPath) fullPath() string {
+	return filepath.Join(p.path, p.filename)
+}
+
+func TestCollectPathsToFormat(t *testing.T) {
+	testCases := []struct {
+		name            string
+		files           []dummyPath
+		includePatterns []string
+		excludePatterns []string
+		expectedFiles   map[string]struct{}
+	}{
+		{
+			name: "no excludes",
+			files: []dummyPath{
+				{filename: "x.yaml"},
+				{filename: "y.yaml"},
+				{filename: "z.yaml"},
+			},
+			expectedFiles: map[string]struct{}{
+				"x.yaml": {},
+				"y.yaml": {},
+				"z.yaml": {},
+			},
+		},
+		{
+			name: "does not include directories",
+			files: []dummyPath{
+				{filename: "x.yaml"},
+				{filename: "y", isDir: true},
+			},
+			expectedFiles: map[string]struct{}{
+				"x.yaml": {},
+			},
+		},
+		{
+			name: "only include what is asked",
+			files: []dummyPath{
+				{filename: "x.yaml"},
+				{filename: "y.yaml"},
+			},
+			includePatterns: []string{
+				"y.yaml",
+			},
+			expectedFiles: map[string]struct{}{
+				"y.yaml": {},
+			},
+		},
+		{
+			name: "exclude what is asked",
+			files: []dummyPath{
+				{filename: "x.yaml"},
+				{filename: "y.yaml"},
+			},
+			excludePatterns: []string{
+				"y.yaml",
+			},
+			expectedFiles: map[string]struct{}{
+				"x.yaml": {},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			tempPath := t.TempDir()
+
+			for _, file := range tc.files {
+				file.path = tempPath
+				if err := file.Create(); err != nil {
+					t.Fatalf("Failed to create file")
+				}
+			}
+
+			includePaths := formatTempPaths(tempPath, tc.includePatterns)
+			if len(includePaths) == 0 {
+				includePaths = []string{fmt.Sprintf("%s/**", tempPath)}
+			}
+			excludePaths := formatTempPaths(tempPath, tc.excludePatterns)
+			if len(excludePaths) == 0 {
+				excludePaths = []string{}
+			}
+
+			paths, err := paths.CollectPathsToFormat(includePaths, excludePaths)
+			if err != nil {
+				t.Fatalf("CollectPathsToFormat failed: %v", err)
+			}
+
+			filesToFormat := map[string]struct{}{}
+			for _, path := range paths {
+				_, filename := filepath.Split(path)
+				filesToFormat[filename] = struct{}{}
+			}
+			if !reflect.DeepEqual(filesToFormat, tc.expectedFiles) {
+				t.Fatalf("Expected to receive paths %v but got %v", tc.expectedFiles, filesToFormat)
+			}
+		})
+	}
+}
+
+func formatTempPaths(tempPath string, patterns []string) []string {
+	formatted := make([]string, len(patterns))
+	for i, pattern := range patterns {
+		formatted[i] = fmt.Sprintf("%s/%s", tempPath, pattern)
+	}
+	return formatted
+}