diff --git a/README.md b/README.md index 1a3fb0e..60233e1 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,8 @@ optional flags: A comma separated list of file types to ignore -groupby string Group output by filetype, directory, pass-fail. Supported for Standard and JSON reports + -globbing bool + Set globbing to true to enable pattern matching for search paths. Enclose the pattern in double quotes. The flag is not compatible with -exclude-dirs and -exclude-file-types. -quiet If quiet flag is set. It doesn't print any output to stdout. -reporter string @@ -210,6 +212,23 @@ Passing the `--quiet` flag suppresses all output to stdout. If there are invalid validator --quiet /path/to/search ``` +### Search files using a glob pattern + +Use the `-globbing` flag to validate files matching a specified pattern. Include the pattern as a positional argument in double quotes. Multiple glob patterns and direct file paths are supported. If invalid config files are detected, the validator tool exits with code 1, and errors (e.g., invalid patterns) are displayed. + +[Learn more about glob patterns](https://www.digitalocean.com/community/tools/glob) + +``` +# Validate all `.json` files in a directory +validator -globbing "/path/to/files/*.json" + +# Recursively validate all `.json` files in subdirectories +validator -globbing "/path/to/files/**/*.json" + +# Mix glob patterns and paths +validator -globbing "/path/*.json" /path/to/search +``` + #### Container Run ``` docker run -it --rm -v /path/to/config/files:/test config-file-validator:1.6.0 /test diff --git a/cmd/validator/validator.go b/cmd/validator/validator.go index e80f8b3..e500fe0 100644 --- a/cmd/validator/validator.go +++ b/cmd/validator/validator.go @@ -17,6 +17,8 @@ optional flags: Subdirectories to exclude when searching for configuration files -exclude-file-types string A comma separated list of file types to ignore + -globbing bool + Set globbing to true to enable pattern matching for search paths -reporter string A string representing report format and optional output file path separated by colon if present. Usage: --reporter : @@ -39,6 +41,8 @@ import ( "sort" "strings" + "github.com/bmatcuk/doublestar/v4" + configfilevalidator "github.com/Boeing/config-file-validator" "github.com/Boeing/config-file-validator/pkg/cli" "github.com/Boeing/config-file-validator/pkg/filetype" @@ -55,6 +59,7 @@ type validatorConfig struct { versionQuery *bool groupOutput *string quiet *bool + globbing *bool } type reporterFlags []string @@ -121,6 +126,7 @@ func getFlags() (validatorConfig, error) { versionPtr = flag.Bool("version", false, "Version prints the release version of validator") groupOutputPtr = flag.String("groupby", "", "Group output by filetype, directory, pass-fail. Supported for Standard and JSON reports") quietPtr = flag.Bool("quiet", false, "If quiet flag is set. It doesn't print any output to stdout.") + globbingPrt = flag.Bool("globbing", false, "If globbing flag is set, check for glob patterns in the arguments.") ) flag.Var( &reporterConfigFlags, @@ -144,7 +150,14 @@ Supported formats: standard, json, junit (default: "standard")`, return validatorConfig{}, err } - searchPaths := parseSearchPath() + if err := validateGlobbing(globbingPrt); err != nil { + return validatorConfig{}, err + } + + searchPaths, err := parseSearchPath(globbingPrt) + if err != nil { + return validatorConfig{}, err + } err = validateReporterConf(reporterConf, groupOutputPtr) if err != nil { @@ -176,6 +189,7 @@ Supported formats: standard, json, junit (default: "standard")`, versionPtr, groupOutputPtr, quietPtr, + globbingPrt, } return config, nil @@ -221,19 +235,42 @@ func validateGroupByConf(groupBy *string) error { return nil } -func parseSearchPath() []string { +func validateGlobbing(globbingPrt *bool) error { + if *globbingPrt && (isFlagSet("exclude-dirs") || isFlagSet("exclude-file-types")) { + fmt.Println("The -globbing flag cannot be used with --exclude-dirs or --exclude-file-types") + flag.Usage() + return errors.New("the -globbing flag cannot be used with --exclude-dirs or --exclude-file-types") + } + return nil +} + +func parseSearchPath(globbingPrt *bool) ([]string, error) { searchPaths := make([]string, 0) - // If search path arg is empty, set it to the cwd - // if not, set it to the arg. Supports n number of - // paths if flag.NArg() == 0 { searchPaths = append(searchPaths, ".") + } else if *globbingPrt { + return handleGlobbing(searchPaths) } else { searchPaths = append(searchPaths, flag.Args()...) } - return searchPaths + return searchPaths, nil +} + +func handleGlobbing(searchPaths []string) ([]string, error) { + for _, flagArg := range flag.Args() { + if isGlobPattern(flagArg) { + matches, err := doublestar.Glob(os.DirFS("."), flagArg) + if err != nil { + return nil, errors.New("Glob matching error") + } + searchPaths = append(searchPaths, matches...) + } else { + searchPaths = append(searchPaths, flagArg) + } + } + return searchPaths, nil } func parseReporterFlags(flags reporterFlags) (map[string]string, error) { @@ -332,6 +369,11 @@ func cleanString(command string) string { return cleanedString } +// Function to check if a string is a glob pattern +func isGlobPattern(s string) bool { + return strings.ContainsAny(s, "*?[]") +} + func mainInit() int { validatorConfig, err := getFlags() if err != nil { diff --git a/cmd/validator/validator_test.go b/cmd/validator/validator_test.go index 42f612d..5b7dfc7 100644 --- a/cmd/validator/validator_test.go +++ b/cmd/validator/validator_test.go @@ -39,6 +39,12 @@ func Test_flags(t *testing.T) { {"grouped sarif", []string{"-groupby=directory", "--reporter=sarif", "."}, 1}, {"groupby duplicate", []string{"--groupby=directory,directory", "."}, 1}, {"quiet flag", []string{"--quiet=true", "."}, 0}, + {"globbing flag set", []string{"--globbing=true", "."}, 0}, + {"globbing flag with a pattern", []string{"--globbing=true", "../../test/**/[m-t]*.json"}, 0}, + {"globbing flag with no matches", []string{"--globbing=true", "../../test/**/*.nomatch"}, 0}, + {"globbing flag not set", []string{"test/**/*.json", "."}, 1}, + {"globbing flag with exclude-dirs", []string{"-globbing", "--exclude-dirs=subdir", "test/**/*.json", "."}, 1}, + {"globbing flag with exclude-file-types", []string{"-globbing", "--exclude-file-types=hcl", "test/**/*.json", "."}, 1}, } for _, tc := range cases { // this call is required because otherwise flags panics, diff --git a/go.mod b/go.mod index 87b1354..2180cb6 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/Boeing/config-file-validator go 1.21.0 require ( + github.com/bmatcuk/doublestar/v4 v4.7.1 github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 github.com/fatih/color v1.13.0 github.com/gurkankaymak/hocon v1.2.18 diff --git a/go.sum b/go.sum index 6f0ca38..fb24db3 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6 github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= +github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 h1:dKG8sc7n321deIVRcQtwlMNoBEra7j0qQ8RwxO8RN0w=