github.com/per1234/editorconfig-checker/v2@v2.8.5/pkg/files/files.go (about) 1 // Package files contains functions and structs related to files 2 package files 3 4 import ( 5 "bufio" 6 "bytes" 7 "fmt" 8 "io/fs" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "regexp" 13 "strings" 14 15 "github.com/gabriel-vasile/mimetype" 16 17 "github.com/editorconfig/editorconfig-core-go/v2" 18 19 "github.com/per1234/editorconfig-checker/v2/pkg/config" 20 "github.com/per1234/editorconfig-checker/v2/pkg/utils" 21 ) 22 23 const DefaultMimeType = "application/octet-stream" 24 25 // FileInformation is a Struct which represents some FileInformation 26 type FileInformation struct { 27 Line string 28 Content string 29 FilePath string 30 LineNumber int 31 Editorconfig *editorconfig.Definition 32 } 33 34 // IsExcluded returns whether the file is excluded via arguments or config file 35 func IsExcluded(filePath string, config config.Config) (bool, error) { 36 if len(config.Exclude) == 0 && config.IgnoreDefaults { 37 return false, nil 38 } 39 40 relativeFilePath, err := GetRelativePath(filePath) 41 if err != nil { 42 return true, err 43 } 44 45 result, err := regexp.MatchString(config.GetExcludesAsRegularExpression(), relativeFilePath) 46 if err != nil { 47 return true, err 48 } 49 50 return result, nil 51 } 52 53 // AddToFiles adds a file to a slice if it isn't already in there 54 // and meets the requirements and returns the new slice 55 func AddToFiles(filePaths []string, filePath string, config config.Config) []string { 56 contentType, err := GetContentType(filePath, config) 57 58 config.Logger.Debug("AddToFiles: filePath: %s, contentType: %s", filePath, contentType) 59 60 if err != nil { 61 config.Logger.Error("Could not get the ContentType of file: %s", filePath) 62 config.Logger.Error(err.Error()) 63 } 64 65 isExcluded, err := IsExcluded(filePath, config) 66 67 if err == nil && !isExcluded && IsAllowedContentType(contentType, config) { 68 config.Logger.Verbose("Add %s to be checked", filePath) 69 return append(filePaths, filePath) 70 } 71 72 config.Logger.Verbose("Don't add %s to be checked", filePath) 73 return filePaths 74 } 75 76 // GetFiles returns all files which should be checked 77 func GetFiles(config config.Config) ([]string, error) { 78 var filePaths []string 79 80 // Handle explicit passed files 81 if len(config.PassedFiles) != 0 { 82 for _, passedFile := range config.PassedFiles { 83 if utils.IsDirectory(passedFile) { 84 _ = fs.WalkDir(os.DirFS(passedFile), ".", func(path string, de fs.DirEntry, err error) error { 85 if err != nil { 86 return err 87 } 88 89 fi, err := de.Info() 90 if err != nil { 91 return err 92 } 93 94 if fi.Mode().IsRegular() { 95 filePaths = AddToFiles(filePaths, path, config) 96 } 97 98 return nil 99 }) 100 } else { 101 filePaths = AddToFiles(filePaths, passedFile, config) 102 } 103 } 104 105 return filePaths, nil 106 } 107 108 byteArray, err := exec.Command("git", "ls-files", "--cached", "--others", "--exclude-standard").Output() 109 if err != nil { 110 // It is not a git repository. 111 cwd, err := os.Getwd() 112 if err != nil { 113 return filePaths, err 114 } 115 116 _ = fs.WalkDir(os.DirFS(cwd), ".", func(path string, de fs.DirEntry, err error) error { 117 if err != nil { 118 return err 119 } 120 121 fi, err := de.Info() 122 if err != nil { 123 return err 124 } 125 126 if fi.Mode().IsRegular() { 127 filePaths = AddToFiles(filePaths, path, config) 128 } 129 130 return nil 131 }) 132 } 133 134 filesSlice := strings.Split(string(byteArray[:]), "\n") 135 136 for _, filePath := range filesSlice { 137 if len(filePath) > 0 { 138 fi, err := os.Stat(filePath) 139 140 // The err would be a broken symlink for example, 141 // so we want to program to continue but the file should not be checked 142 if err == nil && fi.Mode().IsRegular() { 143 filePaths = AddToFiles(filePaths, filePath, config) 144 } 145 } 146 } 147 148 return filePaths, nil 149 } 150 151 // ReadLines returns the lines from a file as a slice 152 func ReadLines(content string) []string { 153 var lines []string 154 stringReader := strings.NewReader(content) 155 fileScanner := bufio.NewScanner(stringReader) 156 for fileScanner.Scan() { 157 lines = append(lines, fileScanner.Text()) 158 } 159 160 return lines 161 } 162 163 // GetContentType returns the content type of a file 164 func GetContentType(path string, config config.Config) (string, error) { 165 fileStat, err := os.Stat(path) 166 if err != nil { 167 return "", err 168 } 169 170 if fileStat.IsDir() { 171 return "", fmt.Errorf("%s is a directory", path) 172 } 173 174 if fileStat.Size() == 0 { 175 return "", nil 176 } 177 178 rawFileContent, err := os.ReadFile(path) 179 if err != nil { 180 panic(err) 181 } 182 return GetContentTypeBytes(rawFileContent, config) 183 } 184 185 // GetContentTypeBytes returns the content type of a byte slice 186 func GetContentTypeBytes(rawFileContent []byte, config config.Config) (string, error) { 187 bytesReader := bytes.NewReader(rawFileContent) 188 189 mimeType, err := mimetype.DetectReader(bytesReader) 190 if err != nil { 191 return "", err 192 } 193 194 mime := mimeType.String() 195 // Always returns a valid content-type and "application/octet-stream" if no others seemed to match. 196 parts := strings.Split(mime, ";") 197 if len(parts) > 0 { 198 mime = strings.TrimSpace(parts[0]) 199 } 200 if mime == "" { 201 return DefaultMimeType, nil 202 } 203 return mime, nil 204 } 205 206 // PathExists checks whether a path of a file or directory exists or not 207 func PathExists(filePath string) bool { 208 absolutePath, _ := filepath.Abs(filePath) 209 _, err := os.Stat(absolutePath) 210 211 return err == nil 212 } 213 214 // GetRelativePath returns the relative path of a file from the current working directory 215 func GetRelativePath(filePath string) (string, error) { 216 filePath = filepath.FromSlash(filePath) 217 if !filepath.IsAbs(filePath) { 218 // Path is already relative. No changes needed 219 return filepath.ToSlash(filePath), nil 220 } 221 222 cwd, err := os.Getwd() 223 if err != nil { 224 return "", fmt.Errorf("Could not get the current working directory") 225 } 226 227 cwd = filepath.FromSlash(cwd) 228 rel, err := filepath.Rel(cwd, filePath) 229 return filepath.ToSlash(rel), err 230 } 231 232 // IsAllowedContentType returns whether the contentType is 233 // an allowed content type to check or not 234 func IsAllowedContentType(contentType string, config config.Config) bool { 235 result := false 236 237 for _, allowedContentType := range config.AllowedContentTypes { 238 result = result || strings.Contains(contentType, allowedContentType) 239 } 240 241 return result 242 }