Skip to content

Commit

Permalink
Add cli option to group reporter output. Closes #38 (#92)
Browse files Browse the repository at this point in the history
* Add GroupBy command

Add -groupby command to group output by file type, pass/fail, and directory

* Add multiple groupby commands and validation

Add: Validation for the groupby command and return error if value passed is not a valid command

Add: Allow for multiple values for the groupby command

* Add Tests

Add: Added tests for cli_test.go and validator_test.go

* Reformat groupBy

Reformatted the groupBy functions and groupBy validation

* Add Test Files

Added more test files for testing purposes

* Update grouping order and update readme

Updated the groupings to be done in reverse order. Updated the readme with information on the groupby command

* Fix groupby directory

Fixed the groupby directory function to group by the last directory in the path

* Update Tests

Updated CLI tests

* Fix Comment

Fixed the cleanString funciton comment

* Update README.md

Co-authored-by: Jamie Davidson <[email protected]>

* Update cli_test.go

Fixed formatting

* Update validator.go

Update pass/fail to pass-fail for validation checks

* Update validator_test.go

Fixed formatting indentation

* Update group_output.go

Changed pass/fail to pass-fail for GroupBy routing

* Update README.md

Changed pass/fail to pass-fail for documentation

* Update validator.go

Fixed formatting for groupBy string cleansing

* Update group_output.go

Updated indentation

* Update validator.go

Update flag to pass-fail

* Update cli_test.go

Update pass/fail to pass-fail

* Update group_output.go

Update GroupByPassFail comment to be consistent with pass-fail

* Formatting fix

Update to formatting

* Add Group output

Organized output for when groupby flag is set

* Implement Group Output standard out

Implementation of standard out using single and double groupbys

* Implement Single Groupby JSON

Implement the JSON output for a single groupby call

* Refactor GroupBy

Refactored groupBy when 2 values are passed

* Refactor GroupByTwo

Refactored groupby when 2 values are passed

* Update to Stdout

Update to standard out print

* Implement JSON single group

Implement output for JSON when single group is passed

* Implement JSON double group and tests

Implemented the JSON output for when two groupbys are passed. Also added test cases in the CLI path

* Update go fmt

Updated running go fmt

* Implement triple group output

Implement output for when 3 groupbys are passed

* Add Output Tests

Added output tests for groupby outputs

* Clean comments

Cleaned up comments

* Changed Summary Format

Changed the summary per group to be aligned with the inner most group rather than the tests

* Update README.md

Co-authored-by: Clayton Kehoe <[email protected]>

* Update cmd/validator/validator.go

Update file type to filetype to avoid input confusion

Co-authored-by: Clayton Kehoe <[email protected]>

* Add Header Comments

Added header comments to the stdout and json reporters

* Fix Naming Conflict

Fixed a naming conflict in the json reporter groupby when 3 groups are passed

* Add Util function for Json Reporter

Added a utility function to the JSON reporter to reduce duplicated code.

* Rename Utility Function

Renamed the utility function and added a header comment

* Rename Utility Function Calls

Renamed the utility function calls to the correct name

* Update JSON Print

Updated the JSON print to use the utility function

* Update STD Reporter Test

Updated the standard reporter tests to align with groupby function changes

* Refactor Reporter

Refactored the print groupBy functions to be removed from the Reporter Interface

* Update GroupBy Filetype

Updated grouping for filetypes to group mixed cases

* GoFmt update

Updated the group_output.go to align to gofmt standards

* Update directory groupby

Updated the directory groupby to check if the filepath is in Windows

* Add GroupBy Directory test

Added a test for the GroupBy directory flag for Windows and other directory paths

---------

Co-authored-by: Jamie Davidson <[email protected]>
Co-authored-by: Clayton Kehoe <[email protected]>
  • Loading branch information
3 people authored Dec 18, 2023
1 parent 7abc395 commit 0efb7d0
Show file tree
Hide file tree
Showing 14 changed files with 1,036 additions and 31 deletions.
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ There are several ways to install the config file validator tool

We offer alpine, ubuntu, and scratch containers

#### Apline
#### Alpine

```
docker pull ghcr.io/boeing/config-file-validator:v1.5.0
Expand Down Expand Up @@ -103,6 +103,8 @@ optional flags:
Subdirectories to exclude when searching for configuration files
-exclude-file-types string
A comma separated list of file types to ignore
-groupby string
Group the output by filetype, pass-fail, or directory. Supported Reporters are Standard and JSON
-reporter string
Format of the printed report. Options are standard and json (default "standard")
-version
Expand Down Expand Up @@ -130,7 +132,7 @@ validator /path/to/search /another/path/to/search
Exclude subdirectories in the search path

```
validator --exclude-dirs=/path/to/search/tests /path/to/search
validator --exclude-dirs=/path/to/search/tests /path/to/search
```

![Exclude Dirs Run](./img/exclude_dirs.png)
Expand All @@ -145,7 +147,7 @@ validator --exclude-file-types=json /path/to/search
![Exclude File Types Run](./img/exclude_file_types.png)

#### Customize recursion depth
By default there is no recursion limit. If desired, the recursion depth can be set to an integer value. If depth is set to `0` recursion will be disabled and only the files in the search path will be validated.
By default there is no recursion limit. If desired, the recursion depth can be set to an integer value. If depth is set to `0` recursion will be disabled and only the files in the search path will be validated.

```
validator --depth=0 /path/to/search
Expand All @@ -162,6 +164,14 @@ validator --reporter=json /path/to/search

![Exclude File Types Run](./img/custom_reporter.png)

### Group report output
Group the report output by file type, directory, or pass-fail. Supports one or more groupings.

```
validator -groupby filetype
validator -groupby directory,pass-fail
```


#### Container Run
```
Expand Down Expand Up @@ -242,4 +252,4 @@ docker build . -t config-file-validator:v1.5.0
We welcome contributions! Please refer to our [contributing guide](/CONTRIBUTING.md)

## License
The Config File Validator is released under the [Apache 2.0](/LICENSE) License
The Config File Validator is released under the [Apache 2.0](/LICENSE) License
47 changes: 46 additions & 1 deletion cmd/validator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"fmt"
"log"
"os"
"slices"
"strings"

configfilevalidator "github.com/Boeing/config-file-validator"
Expand All @@ -46,6 +47,7 @@ type validatorConfig struct {
reportType *string
depth *int
versionQuery *bool
groupOutput *string
}

// Custom Usage function to cover
Expand All @@ -71,6 +73,7 @@ func getFlags() (validatorConfig, error) {
excludeFileTypesPtr := flag.String("exclude-file-types", "", "A comma separated list of file types to ignore")
depthPtr := flag.Int("depth", 0, "Depth of recursion for the provided search paths. Set depth to 0 to disable recursive path traversal")
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")
flag.Parse()

searchPaths := make([]string, 0)
Expand All @@ -90,19 +93,48 @@ func getFlags() (validatorConfig, error) {
return validatorConfig{}, errors.New("Wrong parameter value for reporter, only supports standard, json or junit")
}

if *reportTypePtr == "junit" && *groupOutputPtr != "" {
fmt.Println("Wrong parameter value for reporter, groupby is not supported for JUnit reports")
flag.Usage()
return validatorConfig{}, errors.New("Wrong parameter value for reporter, groupby is not supported for JUnit reports")
}

if depthPtr != nil && isFlagSet("depth") && *depthPtr < 0 {
fmt.Println("Wrong parameter value for depth, value cannot be negative.")
flag.Usage()
return validatorConfig{}, errors.New("Wrong parameter value for depth, value cannot be negative")
}

groupByCleanString := cleanString("groupby")
groupByUserInput := strings.Split(groupByCleanString, ",")
groupByAllowedValues := []string{"filetype", "directory", "pass-fail"}
seenValues := make(map[string]bool)

// Check that the groupby values are valid and not duplicates
if groupOutputPtr != nil && isFlagSet("groupby") {
for _, groupBy := range groupByUserInput {
if !slices.Contains(groupByAllowedValues, groupBy) {
fmt.Println("Wrong parameter value for groupby, only supports filetype, directory, pass-fail")
flag.Usage()
return validatorConfig{}, errors.New("Wrong parameter value for groupby, only supports filetype, directory, pass-fail")
}
if _, ok := seenValues[groupBy]; ok {
fmt.Println("Wrong parameter value for groupby, duplicate values are not allowed")
flag.Usage()
return validatorConfig{}, errors.New("Wrong parameter value for groupby, duplicate values are not allowed")
}
seenValues[groupBy] = true
}
}

config := validatorConfig{
searchPaths,
excludeDirsPtr,
excludeFileTypesPtr,
reportTypePtr,
depthPtr,
versionPtr,
groupOutputPtr,
}

return config, nil
Expand Down Expand Up @@ -134,6 +166,16 @@ func getReporter(reportType *string) reporter.Reporter {
}
}

// cleanString takes a command string and a split string
// and returns a cleaned string
func cleanString(command string) string {
cleanedString := flag.Lookup(command).Value.String()
cleanedString = strings.ToLower(cleanedString)
cleanedString = strings.TrimSpace(cleanedString)

return cleanedString
}

func mainInit() int {
validatorConfig, err := getFlags()
if err != nil {
Expand All @@ -148,8 +190,10 @@ func mainInit() int {
// since the exclude dirs are a comma separated string
// it needs to be split into a slice of strings
excludeDirs := strings.Split(*validatorConfig.excludeDirs, ",")
reporter := getReporter(validatorConfig.reportType)
excludeFileTypes := strings.Split(*validatorConfig.excludeFileTypes, ",")
groupOutput := strings.Split(*validatorConfig.groupOutput, ",")

reporter := getReporter(validatorConfig.reportType)

fsOpts := []finder.FSFinderOptions{finder.WithPathRoots(validatorConfig.searchPaths...),
finder.WithExcludeDirs(excludeDirs),
Expand All @@ -166,6 +210,7 @@ func mainInit() int {
cli := cli.Init(
cli.WithReporter(reporter),
cli.WithFinder(fileSystemFinder),
cli.WithGroupOutput(groupOutput),
)

// Run the config file validation
Expand Down
2 changes: 2 additions & 0 deletions cmd/validator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ func Test_flags(t *testing.T) {
{"exclude file types set", []string{"--exclude-file-types=json", "."}, 0},
{"multiple paths", []string{"../../test/fixtures/subdir/good.json", "../../test/fixtures/good.json"}, 0},
{"version", []string{"--version"}, 0},
{"incorrect group", []string{"-groupby=badgroup", "."}, 1},
{"correct group", []string{"-groupby=directory", "."}, 0},
}
for _, tc := range cases {
// this call is required because otherwise flags panics,
Expand Down
48 changes: 47 additions & 1 deletion pkg/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import (
"github.com/Boeing/config-file-validator/pkg/reporter"
)

// GroupOutput is a global variable that is used to
// store the group by options that the user specifies
var GroupOutput []string

type CLI struct {
// FileFinder interface to search for the files
// in the SearchPath
Expand Down Expand Up @@ -36,6 +40,12 @@ func WithReporter(reporter reporter.Reporter) CLIOption {
}
}

func WithGroupOutput(groupOutput []string) CLIOption {
return func(c *CLI) {
GroupOutput = groupOutput
}
}

// Initialize the CLI object
func Init(opts ...CLIOption) *CLI {
defaultFsFinder := finder.FileSystemFinderInit()
Expand Down Expand Up @@ -88,7 +98,43 @@ func (c CLI) Run() (int, error) {
reports = append(reports, report)
}

c.Reporter.Print(reports)
// Group the output if the user specified a group by option
// Length is equal to one when empty as it contains an empty string
if len(GroupOutput) == 1 && GroupOutput[0] != "" {
reportGroup, err := GroupBySingle(reports, GroupOutput[0])
if err != nil {
return 1, fmt.Errorf("unable to group by single value: %v", err)
}
// Check reporter type to determine how to print
if _, ok := c.Reporter.(reporter.JsonReporter); ok {
reporter.PrintSingleGroupJson(reportGroup)
} else {
reporter.PrintSingleGroupStdout(reportGroup)
}
} else if len(GroupOutput) == 2 {
reportGroup, err := GroupByDouble(reports, GroupOutput)
if err != nil {
return 1, fmt.Errorf("unable to group by double value: %v", err)
}
if _, ok := c.Reporter.(reporter.JsonReporter); ok {
reporter.PrintDoubleGroupJson(reportGroup)
} else {
reporter.PrintDoubleGroupStdout(reportGroup)
}

} else if len(GroupOutput) == 3 {
reportGroup, err := GroupByTriple(reports, GroupOutput)
if err != nil {
return 1, fmt.Errorf("unable to group by triple value: %v", err)
}
if _, ok := c.Reporter.(reporter.JsonReporter); ok {
reporter.PrintTripleGroupJson(reportGroup)
} else {
reporter.PrintTripleGroupStdout(reportGroup)
}
} else {
c.Reporter.Print(reports)
}

if errorFound {
return 1, nil
Expand Down
28 changes: 28 additions & 0 deletions pkg/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
func Test_CLI(t *testing.T) {
searchPath := "../../test"
excludeDirs := []string{"subdir", "subdir2"}
groupOutput := []string{""}
stdoutReporter := reporter.StdoutReporter{}

fsFinder := finder.FileSystemFinderInit(
Expand All @@ -19,6 +20,7 @@ func Test_CLI(t *testing.T) {
cli := Init(
WithFinder(fsFinder),
WithReporter(stdoutReporter),
WithGroupOutput(groupOutput),
)
exitStatus, err := cli.Run()

Expand Down Expand Up @@ -70,3 +72,29 @@ func Test_CLIBadPath(t *testing.T) {
t.Errorf("Exit status was not 1")
}
}

func Test_CLIWithGroup(t *testing.T) {
searchPath := "../../test"
excludeDirs := []string{"subdir", "subdir2"}
groupOutput := []string{"pass-fail", "directory"}
stdoutReporter := reporter.StdoutReporter{}

fsFinder := finder.FileSystemFinderInit(
finder.WithPathRoots(searchPath),
finder.WithExcludeDirs(excludeDirs),
)
cli := Init(
WithFinder(fsFinder),
WithReporter(stdoutReporter),
WithGroupOutput(groupOutput),
)
exitStatus, err := cli.Run()

if err != nil {
t.Errorf("An error was returned: %v", err)
}

if exitStatus != 0 {
t.Errorf("Exit status was not 0")
}
}
Loading

0 comments on commit 0efb7d0

Please sign in to comment.