Skip to content

Commit

Permalink
Create options for human-readable output formats (#437)
Browse files Browse the repository at this point in the history
* Add a -summary option to print a short summary of the linting

Linting the test file `testdata/utf8ControlX88.pem` results in:
```
+-------+--------------+
| LEVEL | # OCCURANCES |
+-------+--------------+
| info  |            0 |
| warn  |            7 |
| error |           15 |
| fatal |            0 |
+-------+--------------+
```

* Added -longsummary option and output

Running:
```sh
testdata/indivValAllBad.pem | ./zlint -longsummary
```
the output is:
```
+-------+--------------+------------------------------------------+
| LEVEL | # OCCURANCES |                 DETAILS                  |
+-------+--------------+------------------------------------------+
| info  |            0 |  -                                       |
| warn  |            1 | w_ext_san_critical_with_subject_dn       |
| error |            7 | e_ca_crl_sign_not_set                    |
|       |              | e_sub_ca_crl_distribution_points_missing |
|       |              | e_ca_country_name_missing                |
|       |              | e_cert_policy_iv_requires_country        |
|       |              | e_sub_cert_not_is_ca                     |
|       |              | e_ca_key_cert_sign_not_set               |
|       |              | e_ca_organization_name_missing           |
| fatal |            0 |  -                                       |
+-------+--------------+------------------------------------------+
```

* spelling fix

* Remove tablewriter dependency and reimplement the good parts

* spelling fix

* Fixed a missed merge :(

* switched longsummary to longSummary; fixed output bug

 - switched `-longsummary` option to `-longSummary` to be more consistent with
   existing options

 - fixed an embarrassing output bug when two categories had the same number of
   errors

* Cleaned up typos, variable names, formatting

* parent 9957909
author Andrew Caird <acaird@gmail.com> 1590420366 -0400
committer Andrew Caird <acaird@gmail.com> 1593372751 -0400

Add a -summary option to print a short summary of the linting

Linting the test file `testdata/utf8ControlX88.pem` results in:
```
+-------+--------------+
| LEVEL | # OCCURANCES |
+-------+--------------+
| info  |            0 |
| warn  |            7 |
| error |           15 |
| fatal |            0 |
+-------+--------------+
```

and a  -longSummary option and output

Running:
```sh
testdata/indivValAllBad.pem | ./zlint -longsummary
```
the output is:
```
+-------+--------------+------------------------------------------+
| LEVEL | # OCCURANCES |                 DETAILS                  |
+-------+--------------+------------------------------------------+
| info  |            0 |  -                                       |
| warn  |            1 | w_ext_san_critical_with_subject_dn       |
| error |            7 | e_ca_crl_sign_not_set                    |
|       |              | e_sub_ca_crl_distribution_points_missing |
|       |              | e_ca_country_name_missing                |
|       |              | e_cert_policy_iv_requires_country        |
|       |              | e_sub_cert_not_is_ca                     |
|       |              | e_ca_key_cert_sign_not_set               |
|       |              | e_ca_organization_name_missing           |
| fatal |            0 |  -                                       |
+-------+--------------+------------------------------------------+
```

* autopull: 2020-05-27T14:34:02Z (#441)

Co-authored-by: tld-update-bot <cpu+tldbot@letsencrypt.org>

* gTLD autopull: 2020-05-28T14:35:00Z (#442)

Co-authored-by: tld-update-bot <cpu+tldbot@letsencrypt.org>

* Moved structure creation out of function into a method for reporting

* Moved the formatted output routines out of main

* Changed newRT to a pointer receiver

* Changed output options to all them all; newlines for nice output

* Changed output options to allow printing of them all; newlines for nice output

Co-authored-by: Zakir Durumeric <zakird@gmail.com>
Co-authored-by: Daniel McCarney <daniel@binaryparadox.net>
Co-authored-by: TLD Update Robot <47792085+tld-update-bot@users.noreply.github.com>
Co-authored-by: tld-update-bot <cpu+tldbot@letsencrypt.org>
  • Loading branch information
5 people authored Jul 20, 2020
1 parent 5f05d1d commit ca9532d
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 5 deletions.
17 changes: 15 additions & 2 deletions v2/cmd/zlint/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ import (
"github.com/zmap/zcrypto/x509"
"github.com/zmap/zlint/v2"
"github.com/zmap/zlint/v2/lint"
"github.com/zmap/zlint/v2/formattedoutput"
)

var ( // flags
listLintsJSON bool
listLintSources bool
summary bool
longSummary bool
prettyprint bool
format string
nameFilter string
Expand All @@ -51,14 +54,16 @@ var ( // flags
func init() {
flag.BoolVar(&listLintsJSON, "list-lints-json", false, "Print lints in JSON format, one per line")
flag.BoolVar(&listLintSources, "list-lints-source", false, "Print list of lint sources, one per line")
flag.BoolVar(&summary, "summary", false, "Prints a short human-readable summary report")
flag.BoolVar(&longSummary, "longSummary", false, "Prints a human-readable summary report with details")
flag.StringVar(&format, "format", "pem", "One of {pem, der, base64}")
flag.StringVar(&nameFilter, "nameFilter", "", "Only run lints with a name matching the provided regex. (Can not be used with -includeNames/-excludeNames)")
flag.StringVar(&includeNames, "includeNames", "", "Comma-separated list of lints to include by name")
flag.StringVar(&excludeNames, "excludeNames", "", "Comma-separated list of lints to exclude by name")
flag.StringVar(&includeSources, "includeSources", "", "Comma-separated list of lint sources to include")
flag.StringVar(&excludeSources, "excludeSources", "", "Comma-separated list of lint sources to exclude")

flag.BoolVar(&prettyprint, "pretty", false, "Pretty-print output")
flag.BoolVar(&prettyprint, "pretty", false, "Pretty-print JSON output")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "ZLint version %s\n\n", version)
fmt.Fprintf(os.Stderr, "Usage: %s [flags] file...\n", os.Args[0])
Expand Down Expand Up @@ -156,7 +161,15 @@ func doLint(inputFile *os.File, inform string, registry lint.Registry) {
log.Fatalf("can't format output: %s", err)
}
os.Stdout.Write(out.Bytes())
} else {
fmt.Printf("\n\n")
}
if summary {
formattedoutput.OutputSummary(zlintResult, false)
}
if longSummary {
formattedoutput.OutputSummary(zlintResult, true)
}
if !prettyprint && !summary && !longSummary {
os.Stdout.Write(jsonBytes)
}
os.Stdout.Write([]byte{'\n'})
Expand Down
160 changes: 160 additions & 0 deletions v2/formattedoutput/formattedOutput.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package formattedoutput

import (
"fmt"
"strconv"
"strings"
"unicode/utf8"
"sort"

"github.com/zmap/zlint/v2"
"github.com/zmap/zlint/v2/lint"

)

type resultsTable struct {
resultCount map[lint.LintStatus]int
resultDetails map[lint.LintStatus][]string
lintLevelsAboveThreshold map[int]lint.LintStatus
sortedLevels []int
}

func (r *resultsTable) newRT(threshold lint.LintStatus, results *zlint.ResultSet, longSummary bool) resultsTable {

r.resultCount = make(map[lint.LintStatus]int)
r.resultDetails = make(map[lint.LintStatus][]string)
r.lintLevelsAboveThreshold = make(map[int]lint.LintStatus)

// Make the list of lint levels that matter
for _, i := range lint.StatusLabelToLintStatus {
if i <= threshold {
continue
}
r.lintLevelsAboveThreshold[int(i)] = i
}
// Set all of the levels to 0 events so they are all displayed
// in the -summary table
for _, level := range r.lintLevelsAboveThreshold {
r.resultCount[level] = 0
}
// Count up the number of each event
for lintName, lintResult := range results.Results {
if lintResult.Status > threshold {
r.resultCount[lintResult.Status]++
if longSummary {
r.resultDetails[lintResult.Status] = append(
r.resultDetails[lintResult.Status],
string(lintName),
)
}
}
}
// Sort the levels we have so we can get a nice output
for key := range r.resultCount {
r.sortedLevels = append(r.sortedLevels, int(key))
}
sort.Ints(r.sortedLevels)

return *r
}


func OutputSummary(zlintResult *zlint.ResultSet, longSummary bool) {
// Set the threashold under which (inclusive) events are not
// counted
threshold := lint.Pass

rt := (&resultsTable{}).newRT(threshold, zlintResult, longSummary)

// make and print the requested table type
if longSummary {
// make a table with the internal lint names grouped
// by type
var olsl string
headings := []string{
"Level",
"# occurrences",
" Details ",
}
lines := [][]string{}
lsl := ""
rescount := ""

hlengths := printTableHeadings(headings)
// Construct the table lines, but don't repeat
// LintStatus(level) or the results count. Also, just
// because a level wasn't seen doesn't mean it isn't
// important; display "empty" levels, too
for _, level := range rt.sortedLevels {
foundDetail := false
for _, detail := range rt.resultDetails[lint.LintStatus(level)] {
if fmt.Sprintf("%s", lint.LintStatus(level)) != olsl {
olsl = fmt.Sprintf("%s", lint.LintStatus(level))
lsl = olsl
rescount = strconv.Itoa(rt.resultCount[lint.LintStatus(level)])
} else {
lsl = ""
rescount = ""
}
lines = append(lines, ([]string{lsl, rescount, detail}))
foundDetail = true
}
if !foundDetail {
lines = append(lines, []string{
fmt.Sprintf("%s", lint.LintStatus(level)),
strconv.Itoa(rt.resultCount[lint.LintStatus(level)]),
" - ",
})
}
}
printTableBody(hlengths, lines)
} else {
headings := []string{"Level", "# occurrences"}
hlengths := printTableHeadings(headings)
lines := [][]string{}
for _, level := range rt.sortedLevels {
lines = append(lines, []string{
fmt.Sprintf("%s", lint.LintStatus(level)),
strconv.Itoa(rt.resultCount[lint.LintStatus(level)])})
}
printTableBody(hlengths, lines)
fmt.Printf("\n")
}
}

func printTableHeadings(headings []string) []int {
hlengths := []int{}
for i, h := range headings {
hlengths = append(
hlengths,
utf8.RuneCountInString(h)+1)
fmt.Printf("| %s ", strings.ToUpper(h))
if i == len(headings)-1 {
fmt.Printf("|\n")
for ii, j := range hlengths {
fmt.Printf("+%s", strings.Repeat("-", j+1))
if ii == len(headings)-1 {
fmt.Printf("+\n")
}
}
}
}
return hlengths
}

func printTableBody(hlengths []int, lines [][]string) {
for _, line := range lines {
for i, hlen := range hlengths {
// This makes a format string with the
// right widths, e.g. "%7.7s"
fmtstring := fmt.Sprintf("|%%%[1]d.%[1]ds", hlen)
fmt.Printf(fmtstring, line[i])
if i == len(hlengths)-1 {
fmt.Printf(" |\n")
} else {
fmt.Printf(" ")
}
}
}

}
6 changes: 3 additions & 3 deletions v2/lint/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ const (
)

var (
// statusLabelToLintStatus is used to work backwards from
// StatusLabelToLintStatus is used to work backwards from
// a LintStatus.String() to the LintStatus. This is used by
// LintStatus.Unmarshal.
statusLabelToLintStatus = map[string]LintStatus{
StatusLabelToLintStatus = map[string]LintStatus{
Reserved.String(): Reserved,
NA.String(): NA,
NE.String(): NE,
Expand Down Expand Up @@ -73,7 +73,7 @@ func (e LintStatus) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the json.Unmarshaler interface.
func (e *LintStatus) UnmarshalJSON(data []byte) error {
key := strings.ReplaceAll(string(data), `"`, "")
if status, ok := statusLabelToLintStatus[key]; ok {
if status, ok := StatusLabelToLintStatus[key]; ok {
*e = status
} else {
return fmt.Errorf("bad LintStatus JSON value: %s", string(data))
Expand Down

0 comments on commit ca9532d

Please sign in to comment.