diff --git a/README.md b/README.md index 04deb09..70db6e6 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ To print the directory structure of the current folder: pr ``` -To print the directory structure of a specific folder: +To print the directory structure of a specific folder:**** ```bash pr -dir /path/to/your/folder @@ -114,7 +114,7 @@ printLayout/ ### `--sort-by` - **Description**: Specifies the sorting criteria -- **Options**: +- **Options**: - `name`: Sort by file/directory name - `size`: Sort by file size - `time`: Sort by modification time diff --git a/cmd/main.go b/cmd/main.go index e39773f..4a12d6f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -8,11 +8,38 @@ import ( func main() { config := printer.Config{} + // Define flags here: flag.StringVar(&config.DirPath, "dir", ".", "Directory path to print the structure of") flag.StringVar(&config.OutputPath, "output", "", "Output file path") flag.StringVar(&config.ExtFilter, "ext", "", "File extension filter (e.g., .go, .js)") + flag.BoolVar(&config.NoColor, "no-color", false, "Disable colorized output") + flag.StringVar(&config.OutputFormat, "format", "text", "Output format (text, json, xml, yaml)") + flag.StringVar(&config.DirColor, "dir-color", "blue", "Color for directories (e.g., blue, green, red)") + flag.StringVar(&config.FileColor, "file-color", "green", "Color for files (e.g., yellow, cyan, magenta)") + flag.StringVar(&config.ExecColor, "exec-color", "red", "Color for executables (e.g., red, green, blue)") + flag.StringVar(&config.SortBy, "sort-by", "name", "Sort by 'name', 'size', or 'time'") + flag.StringVar(&config.Order, "order", "asc", "Sort order 'asc' or 'desc'") + // Add --exclude flag to specify exclusion patterns + flag.Func("exclude", "Exclude files/directories matching the pattern (can be specified multiple times)", func(pattern string) error { + config.ExcludePatterns = append(config.ExcludePatterns, pattern) + return nil + }) + + // Parse flags flag.Parse() - printer.PrintProjectStructure(config.DirPath, config.OutputPath, config.ExtFilter) + printer.PrintProjectStructure( + config.DirPath, + config.OutputPath, + config.ExtFilter, + !config.NoColor, + config.OutputFormat, + config.DirColor, + config.FileColor, + config.ExecColor, + config.ExcludePatterns, + config.SortBy, + config.Order, + ) } diff --git a/go.mod b/go.mod index 870decb..3adc3fd 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,14 @@ module PrintLayout go 1.23.5 + +require ( + github.com/fatih/color v1.18.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/sys v0.25.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d45ddac --- /dev/null +++ b/go.sum @@ -0,0 +1,15 @@ +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go index 803e30c..f1ffd97 100644 --- a/pkg/printer/printer.go +++ b/pkg/printer/printer.go @@ -1,42 +1,220 @@ package printer import ( + "encoding/json" + "encoding/xml" "fmt" "os" "path/filepath" "sort" "strings" + + "github.com/fatih/color" + "gopkg.in/yaml.v3" ) // Config holds the flag values type Config struct { - DirPath string - OutputPath string - ExtFilter string + DirPath string + OutputPath string + ExtFilter string + NoColor bool + OutputFormat string + DirColor string + FileColor string + ExecColor string + ExcludePatterns []string + SortBy string // "name", "size", "time" + Order string // "asc", "desc" +} + +var colorMap = map[string]color.Attribute{ + "black": color.FgBlack, + "red": color.FgRed, + "green": color.FgGreen, + "yellow": color.FgYellow, + "blue": color.FgBlue, + "magenta": color.FgMagenta, + "cyan": color.FgCyan, + "white": color.FgWhite, } -// HandleFlags +// getColorFunc returns a color function based on the color name +func getColorFunc(colorName string) func(a ...interface{}) string { + if attr, ok := colorMap[colorName]; ok { + return color.New(attr).SprintFunc() + } + return fmt.Sprint // Default to no color if the color name is invalid +} + +// HandleFlags processes the configuration and prints the directory structure. func HandleFlags(config Config) { - PrintProjectStructure(config.DirPath, config.OutputPath, config.ExtFilter) + PrintProjectStructure( + config.DirPath, + config.OutputPath, + config.ExtFilter, + !config.NoColor, config.OutputFormat, + config.DirColor, + config.FileColor, + config.ExecColor, + config.ExcludePatterns, + config.SortBy, + config.Order) } // PrintProjectStructure prints the directory structure of the given root directory. -// It always prints the structure to the console and writes to the output file if provided. -func PrintProjectStructure(root string, outputFile string, extFilter string) { +func PrintProjectStructure( + root string, + outputFile string, + extFilter string, + useColor bool, + format string, + dirColorName string, + fileColorName string, + execColorName string, + excludePatterns []string, + sortBy string, + order string) { absRoot, err := filepath.Abs(root) if err != nil { fmt.Println("Error getting absolute path:", err) return } - rootName := filepath.Base(absRoot) - output := fmt.Sprintf("%s/\n", rootName) - output += getTreeOutput(absRoot, "", extFilter) + if format == "text" { + dirCount, fileCount := getTreeOutput(absRoot, extFilter, useColor, dirColorName, fileColorName, execColorName, excludePatterns, sortBy, order) + fmt.Printf("\n%d directories, %d files\n", dirCount, fileCount) + } else { + tree := buildTree(absRoot, extFilter, excludePatterns, sortBy, order) + var output string + switch format { + case "json": + data, _ := json.MarshalIndent(tree, "", " ") + output = string(data) + case "xml": + data, _ := xml.MarshalIndent(tree, "", " ") + output = string(data) + case "yaml": + data, _ := yaml.Marshal(tree) + output = string(data) + default: + fmt.Println("Unsupported format:", format) + return + } + + fmt.Print(output) + + if outputFile != "" { + writeToFile(output, outputFile) + } + } +} + +func getTreeOutput(root string, extFilter string, useColor bool, dirColorName string, fileColorName string, execColorName string, excludePatterns []string, sortBy string, order string) (int, int) { + dirCount := 0 + fileCount := 0 + + dirColorFunc := getColorFunc(dirColorName) + fileColorFunc := getColorFunc(fileColorName) + execColorFunc := getColorFunc(execColorName) + + var traverse func(string, string) error + traverse = func(currentDir string, prefix string) error { + dir, err := os.Open(currentDir) + if err != nil { + return err + } + defer dir.Close() + + entries, err := dir.Readdir(-1) + if err != nil { + return err + } + + // Sort entries based on the specified criteria and order + sortEntries(entries, sortBy, order) + + for i, entry := range entries { + if strings.HasPrefix(entry.Name(), ".") { + continue + } + + if isExcluded(entry.Name(), excludePatterns) { + if entry.IsDir() { + continue + } + continue + } + + isLast := i == len(entries)-1 + + if entry.IsDir() { + dirCount++ + if useColor { + fmt.Printf("%s%s/\n", prefix+getTreePrefix(isLast), dirColorFunc(entry.Name())) + } else { + fmt.Printf("%s%s/\n", prefix+getTreePrefix(isLast), entry.Name()) + } + + err := traverse(filepath.Join(currentDir, entry.Name()), prefix+getIndent(isLast)) + if err != nil { + return err + } + } else { + if extFilter == "" || strings.HasSuffix(entry.Name(), extFilter) { + fileCount++ + if useColor { + info, err := os.Stat(filepath.Join(currentDir, entry.Name())) + if err != nil { + fmt.Printf("%s%s\n", prefix+getTreePrefix(isLast), entry.Name()) + } else if isExecutable(info) { + fmt.Printf("%s%s\n", prefix+getTreePrefix(isLast), execColorFunc(entry.Name())) + } else { + fmt.Printf("%s%s\n", prefix+getTreePrefix(isLast), fileColorFunc(entry.Name())) + } + } else { + fmt.Printf("%s%s\n", prefix+getTreePrefix(isLast), entry.Name()) + } + } + } + } + + return nil + } + + fmt.Printf("%s/\n", filepath.Base(root)) + err := traverse(root, "") + if err != nil { + fmt.Println("Error traversing directory:", err) + } - fmt.Print(output) + return dirCount, fileCount +} - if outputFile != "" { - writeToFile(output, outputFile) +// sortEntries sorts the entries based on the specified criteria and order +func sortEntries(entries []os.FileInfo, sortBy string, order string) { + switch sortBy { + case "name": + sort.Slice(entries, func(i, j int) bool { + if order == "asc" { + return entries[i].Name() < entries[j].Name() + } + return entries[i].Name() > entries[j].Name() + }) + case "size": + sort.Slice(entries, func(i, j int) bool { + if order == "asc" { + return entries[i].Size() < entries[j].Size() + } + return entries[i].Size() > entries[j].Size() + }) + case "time": + sort.Slice(entries, func(i, j int) bool { + if order == "asc" { + return entries[i].ModTime().Before(entries[j].ModTime()) + } + return entries[i].ModTime().After(entries[j].ModTime()) + }) } } @@ -53,44 +231,63 @@ func writeToFile(output, outputFile string) { } } -// getTreeOutput returns the directory tree structure as a string. -func getTreeOutput(currentDir string, prefix string, extFilter string) string { - var output string +// Node represents a directory or file in the tree structure +type Node struct { + Name string `json:"name" xml:"name"` + IsDir bool `json:"is_dir" xml:"is_dir"` + Children []*Node `json:"children,omitempty" xml:"children,omitempty"` +} +// buildTree constructs a tree of Nodes from the directory structure +func buildTree(currentDir string, extFilter string, excludePatterns []string, sortBy string, order string) *Node { dir, err := os.Open(currentDir) if err != nil { - return output + return nil } defer dir.Close() entries, err := dir.Readdir(-1) if err != nil { - return output + return nil } - sort.Slice(entries, func(i, j int) bool { - return entries[i].Name() < entries[j].Name() - }) + // Sort entries based on the specified criteria and order + sortEntries(entries, sortBy, order) - for i, entry := range entries { - // Skip hidden files/directories (those starting with ".") + node := &Node{ + Name: filepath.Base(currentDir), + IsDir: true, + } + + for _, entry := range entries { if strings.HasPrefix(entry.Name(), ".") { continue } - isLast := i == len(entries)-1 + // Check if the entry matches any exclusion pattern + if isExcluded(entry.Name(), excludePatterns) { + continue + } if entry.IsDir() { - output += fmt.Sprintf("%s%s/\n", prefix+getTreePrefix(isLast), entry.Name()) - output += getTreeOutput(filepath.Join(currentDir, entry.Name()), prefix+getIndent(isLast), extFilter) - } else { - if extFilter == "" || strings.HasSuffix(entry.Name(), extFilter) { - output += fmt.Sprintf("%s%s\n", prefix+getTreePrefix(isLast), entry.Name()) + child := buildTree(filepath.Join(currentDir, entry.Name()), extFilter, excludePatterns, sortBy, order) + if child != nil { + node.Children = append(node.Children, child) } + } else if extFilter == "" || strings.HasSuffix(entry.Name(), extFilter) { + node.Children = append(node.Children, &Node{ + Name: entry.Name(), + IsDir: false, + }) } } - return output + return node +} + +// isExecutable checks if a file is executable +func isExecutable(entry os.FileInfo) bool { + return entry.Mode()&0111 != 0 // Check executable bits } // getTreePrefix returns the tree prefix for the current entry. @@ -108,3 +305,18 @@ func getIndent(isLast bool) string { } return "│ " } + +// isExcluded checks if a file/directory matches any of the exclusion patterns +func isExcluded(name string, excludePatterns []string) bool { + for _, pattern := range excludePatterns { + matched, err := filepath.Match(pattern, name) + if err != nil { + fmt.Printf("Invalid exclude pattern: %s\n", pattern) + continue + } + if matched { + return true + } + } + return false +} diff --git a/pkg/printer/printer_bench_test.go b/pkg/printer/printer_bench_test.go new file mode 100644 index 0000000..99a532b --- /dev/null +++ b/pkg/printer/printer_bench_test.go @@ -0,0 +1,87 @@ +package printer + +import ( + "os" + "path/filepath" + "strconv" + "testing" +) + +// BenchmarkPrintProjectStructure benchmarks the PrintProjectStructure function. +func BenchmarkPrintProjectStructure(b *testing.B) { + // Create a temporary directory for benchmarking + tmpDir := b.TempDir() + createTestProjectStructure(b, tmpDir) + + // Change to the temporary directory + oldDir, err := os.Getwd() + if err != nil { + b.Fatalf("Failed to get current working directory: %v", err) + } + defer os.Chdir(oldDir) // Restore the original working directory + os.Chdir(tmpDir) + + // Run the benchmark + b.ResetTimer() // Reset the timer to exclude setup time + for i := 0; i < b.N; i++ { + PrintProjectStructure(".", "", "", false, "text", "blue", "green", "red", []string{}, "name", "asc") + } +} + +// BenchmarkPrintProjectStructure_JSON benchmarks the JSON output format. +func BenchmarkPrintProjectStructure_JSON(b *testing.B) { + // Create a temporary directory for benchmarking + tmpDir := b.TempDir() + createTestProjectStructure(b, tmpDir) + + oldDir, err := os.Getwd() + if err != nil { + b.Fatalf("Failed to get current working directory: %v", err) + } + defer os.Chdir(oldDir) // Restore the original working directory + os.Chdir(tmpDir) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + PrintProjectStructure(".", "", "", false, "json", "blue", "green", "red", []string{}, "name", "asc") + } +} + +// BenchmarkPrintProjectStructure_LargeDirectory benchmarks performance with a large directory. +func BenchmarkPrintProjectStructure_LargeDirectory(b *testing.B) { + tmpDir := b.TempDir() + createLargeTestProjectStructure(b, tmpDir) + + oldDir, err := os.Getwd() + if err != nil { + b.Fatalf("Failed to get current working directory: %v", err) + } + defer os.Chdir(oldDir) // Restore the original working directory + os.Chdir(tmpDir) + + b.ResetTimer() // Reset the timer to exclude setup time + for i := 0; i < b.N; i++ { + PrintProjectStructure(".", "", "", false, "text", "blue", "green", "red", []string{}, "name", "asc") + } +} + +// createLargeTestProjectStructure creates a large directory structure for benchmarking. +func createLargeTestProjectStructure(b *testing.B, root string) { + // Create 100 directories, each containing 10 files + for i := 0; i < 100; i++ { + dir := filepath.Join(root, "dir"+strconv.Itoa(i)) + err := os.MkdirAll(dir, 0755) + if err != nil { + b.Fatalf("Failed to create directory: %v", err) + } + + for j := 0; j < 10; j++ { + file := filepath.Join(dir, "file"+strconv.Itoa(j)) + f, err := os.Create(file) + if err != nil { + b.Fatalf("Failed to create file: %v", err) + } + f.Close() + } + } +} diff --git a/pkg/printer/printer_test.go b/pkg/printer/printer_test.go index 0dadb95..13c2eb4 100644 --- a/pkg/printer/printer_test.go +++ b/pkg/printer/printer_test.go @@ -1,11 +1,16 @@ package printer import ( + "encoding/json" + "encoding/xml" "io" "os" "path/filepath" "strings" "testing" + "time" + + "gopkg.in/yaml.v3" ) // TestPrintProjectStructure tests the PrintProjectStructure function. @@ -21,33 +26,166 @@ func TestPrintProjectStructure(t *testing.T) { defer os.Chdir(oldDir) // Restore the original working directory os.Chdir(tmpDir) - output := captureOutput(func() { - PrintProjectStructure(".", "", "") + // Test text output + t.Run("TextOutput", func(t *testing.T) { + output := captureOutput(func() { + PrintProjectStructure(".", "", "", false, "text", "blue", "green", "red", []string{}, "name", "asc") + }) + + rootName := filepath.Base(tmpDir) + + expected := rootName + "/\n" + + "├── cmd/\n" + + "│ └── main.go\n" + + "├── go.mod\n" + + "├── internal/\n" + + "│ └── utils/\n" + + "│ └── utils.go\n" + + "└── pkg/\n" + + " └── printer/\n" + + " ├── printer.go\n" + + " └── printer_test.go\n" + + "\n5 directories, 5 files\n" + output = strings.TrimSpace(output) + expected = strings.TrimSpace(expected) + + if output != expected { + t.Errorf("Unexpected output:\nGot:\n%s\nExpected:\n%s", output, expected) + } }) - rootName := filepath.Base(tmpDir) - - expected := rootName + "/\n" + - "├── cmd/\n" + - "│ └── main.go\n" + - "├── go.mod\n" + - "├── internal/\n" + - "│ └── utils/\n" + - "│ └── utils.go\n" + - "└── pkg/\n" + - " └── printer/\n" + - " ├── printer.go\n" + - " └── printer_test.go\n" - output = strings.TrimSpace(output) - expected = strings.TrimSpace(expected) - - if output != expected { - t.Errorf("Unexpected output:\nGot:\n%s\nExpected:\n%s", output, expected) - } + // Test JSON output + t.Run("JSONOutput", func(t *testing.T) { + output := captureOutput(func() { + PrintProjectStructure(".", "", "", false, "json", "blue", "green", "red", []string{}, "name", "asc") + }) + + // Verify that the output is valid JSON + var result interface{} + if err := json.Unmarshal([]byte(output), &result); err != nil { + t.Errorf("Output is not valid JSON: %v", err) + } + }) + + // Test XML output + t.Run("XMLOutput", func(t *testing.T) { + output := captureOutput(func() { + PrintProjectStructure(".", "", "", false, "xml", "blue", "green", "red", []string{}, "name", "asc") + }) + + // Verify that the output is valid XML + var result interface{} + if err := xml.Unmarshal([]byte(output), &result); err != nil { + t.Errorf("Output is not valid XML: %v", err) + } + }) + + // Test YAML output + t.Run("YAMLOutput", func(t *testing.T) { + output := captureOutput(func() { + PrintProjectStructure(".", "", "", false, "yaml", "blue", "green", "red", []string{}, "name", "asc") + }) + + // Verify that the output is valid YAML + var result interface{} + if err := yaml.Unmarshal([]byte(output), &result); err != nil { + t.Errorf("Output is not valid YAML: %v", err) + } + }) + + // Test exclusion patterns + t.Run("ExclusionPatterns", func(t *testing.T) { + output := captureOutput(func() { + PrintProjectStructure(".", "", "", false, "text", "blue", "green", "red", []string{"*.go"}, "name", "asc") + }) + + rootName := filepath.Base(tmpDir) + + expected := rootName + "/\n" + + "├── cmd/\n" + + "├── go.mod\n" + + "├── internal/\n" + + "│ └── utils/\n" + + "└── pkg/\n" + + " └── printer/\n" + + "\n5 directories, 1 files\n" + output = strings.TrimSpace(output) + expected = strings.TrimSpace(expected) + + if output != expected { + t.Errorf("Unexpected output:\nGot:\n%s\nExpected:\n%s", output, expected) + } + }) + + // Test sorting by name (ascending) + t.Run("SortByNameAsc", func(t *testing.T) { + output := captureOutput(func() { + PrintProjectStructure(".", "", "", false, "text", "blue", "green", "red", []string{}, "name", "asc") + }) + + // Verify that the output is sorted by name in ascending order + // You can add specific checks based on your expected output + t.Log(output) + }) + + // Test sorting by name (descending) + t.Run("SortByNameDesc", func(t *testing.T) { + output := captureOutput(func() { + PrintProjectStructure(".", "", "", false, "text", "blue", "green", "red", []string{}, "name", "desc") + }) + + // Verify that the output is sorted by name in descending order + // You can add specific checks based on your expected output + t.Log(output) + }) + + // Test sorting by size (ascending) + t.Run("SortBySizeAsc", func(t *testing.T) { + output := captureOutput(func() { + PrintProjectStructure(".", "", "", false, "text", "blue", "green", "red", []string{}, "size", "asc") + }) + + // Verify that the output is sorted by size in ascending order + // You can add specific checks based on your expected output + t.Log(output) + }) + + // Test sorting by size (descending) + t.Run("SortBySizeDesc", func(t *testing.T) { + output := captureOutput(func() { + PrintProjectStructure(".", "", "", false, "text", "blue", "green", "red", []string{}, "size", "desc") + }) + + // Verify that the output is sorted by size in descending order + // You can add specific checks based on your expected output + t.Log(output) + }) + + // Test sorting by time (ascending) + t.Run("SortByTimeAsc", func(t *testing.T) { + output := captureOutput(func() { + PrintProjectStructure(".", "", "", false, "text", "blue", "green", "red", []string{}, "time", "asc") + }) + + // Verify that the output is sorted by time in ascending order + // You can add specific checks based on your expected output + t.Log(output) + }) + + // Test sorting by time (descending) + t.Run("SortByTimeDesc", func(t *testing.T) { + output := captureOutput(func() { + PrintProjectStructure(".", "", "", false, "text", "blue", "green", "red", []string{}, "time", "desc") + }) + + // Verify that the output is sorted by time in descending order + // You can add specific checks based on your expected output + t.Log(output) + }) } // createTestProjectStructure creates a sample project structure for testing. -func createTestProjectStructure(t *testing.T, root string) { +func createTestProjectStructure(tb testing.TB, root string) { // Define the directories to create dirs := []string{ "cmd", @@ -55,7 +193,6 @@ func createTestProjectStructure(t *testing.T, root string) { "pkg/printer", } - // Define the files to create files := []string{ "cmd/main.go", "internal/utils/utils.go", @@ -64,21 +201,24 @@ func createTestProjectStructure(t *testing.T, root string) { "go.mod", } - // Create directories for _, dir := range dirs { err := os.MkdirAll(filepath.Join(root, dir), 0755) if err != nil { - t.Fatalf("Failed to create directory: %v", err) + tb.Fatalf("Failed to create directory: %v", err) } } - // Create files for _, file := range files { f, err := os.Create(filepath.Join(root, file)) if err != nil { - t.Fatalf("Failed to create file: %v", err) + tb.Fatalf("Failed to create file: %v", err) } f.Close() + + if strings.HasSuffix(file, ".go") { + modTime := time.Now().Add(-time.Hour * 24) // Set to 24 hours ago + os.Chtimes(filepath.Join(root, file), modTime, modTime) + } } }