Skip to content

Commit 50b4d2a

Browse files
committed
feat: add percentage and barchart in stats
1 parent cdc8d62 commit 50b4d2a

File tree

2 files changed

+158
-34
lines changed

2 files changed

+158
-34
lines changed

cmd/core.go

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"io"
1111
"io/ioutil"
1212
"os"
13+
"sort"
1314
"strings"
1415
"syscall"
1516

@@ -39,6 +40,7 @@ type statistics struct {
3940
TotCount int
4041
WordsCount int
4142
ScoreCount []int
43+
ScorePerc []int
4244
DuplicateCount int
4345
}
4446

@@ -371,6 +373,7 @@ func initStats(c int) statistics {
371373
TotCount: 0,
372374
WordsCount: c,
373375
ScoreCount: []int{0, 0, 0, 0, 0},
376+
ScorePerc: []int{0, 0, 0, 0, 0},
374377
DuplicateCount: 0,
375378
}
376379
}
@@ -417,25 +420,87 @@ func showTable(data [][]string, w io.Writer) {
417420
func showStats(stat statistics, w io.Writer) {
418421
// writer is a s parameter to pass buffer during tests
419422
table := tablewriter.NewWriter(w)
420-
table.SetHeader([]string{"Description", "Count"})
423+
table.SetHeader([]string{"Description", "Count", "%", "Bar"})
421424
table.SetBorder(false)
425+
table.SetAutoWrapText(false)
426+
427+
stat.ScorePerc = roundPercentage(stat.ScoreCount, stat.TotCount)
422428

423429
data := [][]string{
424-
{"Password checked", fmt.Sprintf("%d", stat.TotCount)},
425-
{"Words in dictionaries", fmt.Sprintf("%d", stat.WordsCount)},
426-
{"Duplicated passwords", fmt.Sprintf("%d", stat.DuplicateCount)},
427-
{"Really bad passwords", fmt.Sprintf("%d", stat.ScoreCount[0])},
428-
{"Bad passwords", fmt.Sprintf("%d", stat.ScoreCount[1])},
429-
{"Weak passwords", fmt.Sprintf("%d", stat.ScoreCount[2])},
430-
{"Good passwords", fmt.Sprintf("%d", stat.ScoreCount[3])},
431-
{"Strong passwords", fmt.Sprintf("%d", stat.ScoreCount[4])},
430+
{"Password checked", fmt.Sprintf("%d", stat.TotCount), "", ""},
431+
{"Words in dictionaries", fmt.Sprintf("%d", stat.WordsCount), "", ""},
432+
{"Duplicated passwords", fmt.Sprintf("%d", stat.DuplicateCount), "", ""},
433+
{"Really bad passwords", fmt.Sprintf("%d", stat.ScoreCount[0]), fmt.Sprintf("%3d%%", stat.ScorePerc[0]), showBarPerc(stat.ScorePerc[0])},
434+
{"Bad passwords", fmt.Sprintf("%d", stat.ScoreCount[1]), fmt.Sprintf("%3d%%", stat.ScorePerc[1]), showBarPerc(stat.ScorePerc[1])},
435+
{"Weak passwords", fmt.Sprintf("%d", stat.ScoreCount[2]), fmt.Sprintf("%3d%%", stat.ScorePerc[2]), showBarPerc(stat.ScorePerc[2])},
436+
{"Good passwords", fmt.Sprintf("%d", stat.ScoreCount[3]), fmt.Sprintf("%3d%%", stat.ScorePerc[3]), showBarPerc(stat.ScorePerc[3])},
437+
{"Strong passwords", fmt.Sprintf("%d", stat.ScoreCount[4]), fmt.Sprintf("%3d%%", stat.ScorePerc[4]), showBarPerc(stat.ScorePerc[4])},
438+
}
439+
440+
var scoreColor int
441+
for i, row := range data {
442+
switch i {
443+
case 3:
444+
scoreColor = tablewriter.BgRedColor
445+
case 4:
446+
scoreColor = tablewriter.BgHiRedColor
447+
case 5:
448+
scoreColor = tablewriter.BgHiYellowColor
449+
case 6:
450+
scoreColor = tablewriter.BgHiGreenColor
451+
case 7:
452+
scoreColor = tablewriter.BgGreenColor
453+
}
454+
// remove color is bar is 0%
455+
if row[1] == "0" {
456+
scoreColor = 0
457+
}
458+
table.Rich(row, []tablewriter.Colors{nil, nil, nil, {scoreColor}})
432459
}
460+
table.Render()
461+
}
433462

434-
for _, row := range data {
435-
table.Append(row)
463+
func showBarPerc(perc int) string {
464+
return fmt.Sprintf("%v", strings.Repeat(" ", perc))
465+
}
466+
467+
func roundPercentage(scoreCount []int, totCount int) []int {
468+
469+
type Percentage struct {
470+
Value float32
471+
Order int
436472
}
437473

438-
table.Render()
474+
roundedPerc := []int{}
475+
dataset := []Percentage{}
476+
totalPerc := 0
477+
478+
//percentages []float32
479+
for i, score := range scoreCount {
480+
perc := float32(score) / float32(totCount) * 100.0
481+
dataset = append(dataset, Percentage{Value: perc, Order: i})
482+
totalPerc += int(perc)
483+
}
484+
diffToAdd := 100 - totalPerc
485+
486+
// order by decimal
487+
sort.Slice(dataset, func(i, j int) bool {
488+
return dataset[i].Value-float32(int(dataset[i].Value)) > dataset[j].Value-float32(int(dataset[j].Value))
489+
})
490+
// distribute diff to get to 100%
491+
for n := 0; n < diffToAdd; n++ {
492+
dataset[n].Value++
493+
}
494+
// order by original position
495+
sort.Slice(dataset, func(i, j int) bool {
496+
return float32(dataset[i].Order) < float32(dataset[j].Order)
497+
})
498+
499+
for _, n := range dataset {
500+
roundedPerc = append(roundedPerc, int(n.Value))
501+
}
502+
503+
return roundedPerc
439504
}
440505

441506
func getPwdStdin() (string, error) {

cmd/core_test.go

Lines changed: 81 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ func TestShowTable(t *testing.T) {
262262
showTable(tt.in, buf)
263263

264264
if !reflect.DeepEqual(tt.out, buf.String()) {
265-
t.Fatalf("got %s, expected %s", buf.String(), tt.out)
265+
t.Fatalf("got %s, expected:\n%s", buf.String(), tt.out)
266266
}
267267

268268
})
@@ -282,18 +282,19 @@ func TestShowStats(t *testing.T) {
282282
TotCount: 1,
283283
WordsCount: 59824,
284284
ScoreCount: []int{0, 0, 0, 0, 1},
285+
ScorePerc: []int{0, 0, 0, 0, 100},
285286
DuplicateCount: 0,
286287
},
287-
out: ` DESCRIPTION | COUNT
288-
------------------------+--------
289-
Password checked | 1
290-
Words in dictionaries | 59824
291-
Duplicated passwords | 0
292-
Really bad passwords | 0
293-
Bad passwords | 0
294-
Weak passwords | 0
295-
Good passwords | 0
296-
Strong passwords | 1
288+
out: ` DESCRIPTION | COUNT | % | BAR
289+
------------------------+-------+------+-------------------------------------------------------------------------------------------------------
290+
Password checked | 1 | | [0m[0m
291+
Words in dictionaries | 59824 | | [0m[0m
292+
Duplicated passwords | 0 | | [0m[0m
293+
Really bad passwords | 0 | 0% | [0m[0m
294+
Bad passwords | 0 | 0% | [0m[0m
295+
Weak passwords | 0 | 0% | [0m[0m
296+
Good passwords | 0 | 0% | [0m[0m
297+
Strong passwords | 1 | 100% | [42m [0m
297298
`,
298299
},
299300
{
@@ -302,18 +303,19 @@ func TestShowStats(t *testing.T) {
302303
TotCount: 9,
303304
WordsCount: 59824,
304305
ScoreCount: []int{3, 1, 2, 1, 2},
306+
ScorePerc: []int{34, 11, 22, 11, 22},
305307
DuplicateCount: 2,
306308
},
307-
out: ` DESCRIPTION | COUNT
308-
------------------------+--------
309-
Password checked | 9
310-
Words in dictionaries | 59824
311-
Duplicated passwords | 2
312-
Really bad passwords | 3
313-
Bad passwords | 1
314-
Weak passwords | 2
315-
Good passwords | 1
316-
Strong passwords | 2
309+
out: ` DESCRIPTION | COUNT | % | BAR
310+
------------------------+-------+------+-------------------------------------
311+
Password checked | 9 | | [0m[0m
312+
Words in dictionaries | 59824 | | [0m[0m
313+
Duplicated passwords | 2 | | [0m[0m
314+
Really bad passwords | 3 | 34% | [41m [0m
315+
Bad passwords | 1 | 11% | [101m [0m
316+
Weak passwords | 2 | 22% | [103m [0m
317+
Good passwords | 1 | 11% | [102m [0m
318+
Strong passwords | 2 | 22% | [42m [0m
317319
`,
318320
},
319321
}
@@ -325,7 +327,64 @@ func TestShowStats(t *testing.T) {
325327
showStats(tt.in, buf)
326328

327329
if !reflect.DeepEqual(tt.out, buf.String()) {
328-
t.Fatalf("got %s, expected %s", buf.String(), tt.out)
330+
t.Fatalf("got:\n %s\nexpected:\n %s", buf.String(), tt.out)
331+
}
332+
333+
})
334+
}
335+
}
336+
337+
func TestRoundPercentages(t *testing.T) {
338+
339+
tests := []struct {
340+
name string
341+
in statistics
342+
out []int
343+
}{
344+
{
345+
name: "Single entry no round",
346+
in: statistics{
347+
TotCount: 1,
348+
ScoreCount: []int{1, 0, 0, 0, 0},
349+
},
350+
out: []int{100, 0, 0, 0, 0},
351+
},
352+
{
353+
name: "Two entries no round",
354+
in: statistics{
355+
TotCount: 2,
356+
ScoreCount: []int{1, 0, 0, 0, 1},
357+
},
358+
out: []int{50, 0, 0, 0, 50},
359+
},
360+
{
361+
name: "Three entries with round",
362+
in: statistics{
363+
TotCount: 3,
364+
ScoreCount: []int{1, 1, 1, 0, 0},
365+
},
366+
out: []int{34, 33, 33, 0, 0},
367+
},
368+
{
369+
name: "Five entries with round",
370+
in: statistics{
371+
TotCount: 18,
372+
ScoreCount: []int{7, 1, 4, 2, 4},
373+
},
374+
out: []int{39, 6, 22, 11, 22},
375+
},
376+
}
377+
378+
for _, tt := range tests {
379+
t.Run(tt.name, func(t *testing.T) {
380+
381+
buf := &bytes.Buffer{}
382+
showStats(tt.in, buf)
383+
384+
perc := roundPercentage(tt.in.ScoreCount, tt.in.TotCount)
385+
386+
if !reflect.DeepEqual(tt.out, perc) {
387+
t.Fatalf("got: %v expected: %v", perc, tt.out)
329388
}
330389

331390
})

0 commit comments

Comments
 (0)