github.com/vmware/govmomi@v0.51.0/hack/header/main.go (about) 1 // © Broadcom. All Rights Reserved. 2 // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 3 // SPDX-License-Identifier: Apache-2.0 4 5 package main 6 7 import ( 8 "bufio" 9 "encoding/json" 10 "flag" 11 "fmt" 12 "os" 13 "path/filepath" 14 "strings" 15 ) 16 17 // Config defines the structure of the JSON configuration file. 18 type Config struct { 19 HeaderLines []string `json:"headerLines"` // Expected file header. 20 FileCommentPrefixes map[string]string `json:"fileCommentPrefixes"` // Comment styles for file types. 21 IgnoredPaths []string `json:"ignoredPaths"` // Paths to ignore. 22 MaxScanLines int `json:"maxScanLines"` // Number of lines to check for the header. 23 } 24 25 // Global variables to hold the configuration and results. 26 var config Config 27 var filesWithIssues []string 28 var filesWithError []string 29 var processedCount = 0 30 31 var providedConfigPath = flag.String("config", "", "Path to the configuration file") 32 33 // main is the entry point for the header check. 34 func main() { 35 flag.Parse() 36 37 if *providedConfigPath == "" { 38 fmt.Println("Provide a JSON configuration using the --config flag.") 39 os.Exit(1) 40 } 41 42 fmt.Printf("Configuration file: %s\n", *providedConfigPath) 43 configPathToLoad := *providedConfigPath 44 45 // Load the configuration file. 46 if err := loadConfig(configPathToLoad); err != nil { 47 fmt.Printf("Error loading configuration: %v\n", err) 48 os.Exit(1) // Configuration error is critical. 49 } 50 51 // Start directory traversal. 52 err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error { 53 if err != nil { 54 return err 55 } 56 57 // Skip directories. 58 if info.IsDir() { 59 return nil 60 } 61 62 // Skip ignored paths. 63 if isIgnored(path) { 64 return nil 65 } 66 67 // Check for file types supported by `fileCommentPrefixes`. 68 ext := filepath.Ext(path) 69 if commentPrefix, ok := config.FileCommentPrefixes[ext]; ok { 70 processedCount++ 71 currentHeader := transformHeader(config.HeaderLines, commentPrefix) 72 73 // Check the file for the required header. 74 if missing, err := checkHeader(path, currentHeader); err != nil { 75 filesWithError = append(filesWithError, fmt.Sprintf("%s (error: %v)", path, err)) 76 } else if missing { 77 filesWithIssues = append(filesWithIssues, path) 78 } 79 } 80 81 return nil 82 }) 83 84 if err != nil { 85 fmt.Printf("❌ Error during directory traversal: %v\n", err) 86 os.Exit(2) // Critical error during file traversal. 87 } 88 89 // Print the summary and get the exit code. 90 exitCode := printSummary() 91 os.Exit(exitCode) 92 } 93 94 // loadConfig loads the JSON configuration into the global config variable. 95 func loadConfig(filepath string) error { 96 file, err := os.Open(filepath) 97 if err != nil { 98 return fmt.Errorf("failed to open configuration file: %w", err) 99 } 100 defer file.Close() 101 102 decoder := json.NewDecoder(file) 103 if err := decoder.Decode(&config); err != nil { 104 return fmt.Errorf("failed to decode configuration: %w", err) 105 } 106 107 return nil 108 } 109 110 // isIgnored determines if a file or directory should be skipped based on ignoredPaths. 111 func isIgnored(path string) bool { 112 normalizedPath := strings.ReplaceAll(path, string(filepath.Separator), "/") 113 for _, pattern := range config.IgnoredPaths { 114 if matched, _ := filepath.Match(pattern, normalizedPath); matched { 115 return true 116 } 117 if strings.Contains(pattern, "**") { 118 prefix := strings.Split(pattern, "**")[0] 119 if strings.HasPrefix(normalizedPath, prefix) { 120 return true 121 } 122 } 123 } 124 return false 125 } 126 127 // transformHeader adjusts the header format for the target file's comment style. 128 func transformHeader(header []string, prefix string) []string { 129 transformed := make([]string, len(header)) 130 for i, line := range header { 131 transformed[i] = prefix + " " + strings.TrimPrefix(line, "// ") 132 } 133 return transformed 134 } 135 136 // checkHeader verifies if the file contains the required header. 137 func checkHeader(path string, headerLines []string) (bool, error) { 138 file, err := os.Open(path) 139 if err != nil { 140 return false, err 141 } 142 defer file.Close() 143 144 scanner := bufio.NewScanner(file) 145 lines := []string{} 146 skipShebang := filepath.Ext(path) == ".sh" // Handle shebang for shell scripts. 147 148 // Only scan the first `MaxScanLines` lines. 149 for i := 0; i < config.MaxScanLines && scanner.Scan(); i++ { 150 line := strings.TrimSpace(scanner.Text()) 151 152 // Skip shebang if present. 153 if skipShebang && strings.HasPrefix(line, "#!") { 154 skipShebang = false 155 continue 156 } 157 158 lines = append(lines, line) 159 } 160 161 if err := scanner.Err(); err != nil { 162 return false, err 163 } 164 165 return !containsHeader(lines, headerLines), nil 166 } 167 168 // containsHeader checks if the required header lines exist in a file. 169 func containsHeader(fileLines, headerLines []string) bool { 170 i := 0 171 for _, line := range fileLines { 172 if line == headerLines[i] { 173 i++ 174 if i == len(headerLines) { 175 return true 176 } 177 } 178 } 179 return false 180 } 181 182 // printSummary displays the results after processing all files and returns the exit code. 183 func printSummary() int { 184 fmt.Println("Processing complete.") 185 fmt.Printf("Total files processed: %d\n", processedCount) 186 187 exitCode := 0 188 189 if len(filesWithIssues) > 0 { 190 fmt.Printf("Missing headers: %d\n", len(filesWithIssues)) 191 for _, file := range filesWithIssues { 192 fmt.Printf(" - %s\n", file) 193 } 194 195 exitCode = 1 // Missing headers found. 196 } 197 198 if len(filesWithError) > 0 { 199 fmt.Printf("Errors encountered in files: %d\n", len(filesWithError)) 200 for _, file := range filesWithError { 201 fmt.Printf(" - %s\n", file) 202 } 203 204 if exitCode == 1 { 205 exitCode = 3 // Both missing headers and errors. 206 } else { 207 exitCode = 2 // Only errors occurred. 208 } 209 } 210 211 if len(filesWithIssues) == 0 && len(filesWithError) == 0 { 212 fmt.Println("All processed files have the expected header.") 213 } 214 215 return exitCode 216 }