-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreport.go
More file actions
167 lines (147 loc) · 3.91 KB
/
Copy pathreport.go
File metadata and controls
167 lines (147 loc) · 3.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package main
// What a finding is, and how it reaches the screen.
import (
"fmt"
"io"
"sort"
"strings"
)
type Severity int
const (
// Note is worth knowing and not worth worrying about.
Note Severity = iota
// Warn narrows down who you are or where you go.
Warn
// Alarm puts you somewhere specific, or is actively broadcasting.
Alarm
)
func (s Severity) tag() string {
switch s {
case Alarm:
return "TELLS "
case Warn:
return "NARROWS"
}
return "NOTE "
}
type Finding struct {
Severity Severity
Title string
Lines []string
// Advice is what somebody does about it. A finding without it is a
// finding nobody acts on.
Advice string
}
type Report struct {
Findings []Finding
// Gaps are the things this run could not read. They print first,
// because they change how everything under them should be read.
Gaps []string
}
func (r *Report) add(f Finding) { r.Findings = append(r.Findings, f) }
func (r *Report) worst() Severity {
worst := Note
for _, f := range r.Findings {
if f.Severity > worst {
worst = f.Severity
}
}
return worst
}
const (
indent = " "
wrap = 62
)
func (r *Report) render(out io.Writer, title string) {
fmt.Fprintf(out, "\n %s\n %s\n\n", title, strings.Repeat("=", min(len(title), wrap+13)))
for _, g := range r.Gaps {
block(out, "UNREAD ", "Something here could not be read", nil, g)
}
// Loudest first. Somebody who reads the first screen and stops should
// have read the part that mattered.
sorted := make([]Finding, len(r.Findings))
copy(sorted, r.Findings)
sort.SliceStable(sorted, func(i, j int) bool {
return sorted[i].Severity > sorted[j].Severity
})
for _, f := range sorted {
block(out, f.Severity.tag(), f.Title, f.Lines, f.Advice)
}
fmt.Fprintln(out, " Nothing was changed. This only reads.")
fmt.Fprintln(out)
}
func block(out io.Writer, tag, title string, lines []string, advice string) {
fmt.Fprintf(out, " [%s] %s\n", tag, title)
for _, line := range lines {
fmt.Fprintf(out, "%s%s\n", indent, strings.TrimRight(line, " "))
}
if advice != "" {
if len(lines) > 0 {
fmt.Fprintln(out)
}
for _, line := range wrapText(advice, wrap) {
fmt.Fprintf(out, "%s%s\n", indent, line)
}
}
fmt.Fprintln(out)
}
// maskName hides a network name well enough that a report pasted into a
// chat is not a list of the places you go, and leaves enough that you can
// recognise your own. Hiding it completely would defeat the point: the
// whole exercise is working out which of these you want gone.
func maskName(ssid string, reveal bool) string {
if reveal {
return ssid
}
// Word by word, so the shape of the name survives. "Casa Martinez"
// stays two words, which is what makes it recognisable to its owner and
// useless to anybody else.
words := strings.Split(ssid, " ")
for i, word := range words {
words[i] = maskWord(word)
}
return strings.Join(words, " ")
}
func maskWord(s string) string {
r := []rune(s)
switch {
case len(r) == 0:
return ""
case len(r) <= 2:
return string(r[0]) + strings.Repeat("*", len(r)-1)
}
return string(r[0]) + strings.Repeat("*", len(r)-2) + string(r[len(r)-1])
}
func wrapText(s string, width int) []string {
words := strings.Fields(s)
if len(words) == 0 {
return nil
}
var lines []string
line := words[0]
for _, w := range words[1:] {
if len(line)+1+len(w) > width {
lines = append(lines, line)
line = w
continue
}
line += " " + w
}
return append(lines, line)
}
func plural(n int, one, many string) string {
if n == 1 {
return fmt.Sprintf("%d %s", n, one)
}
return fmt.Sprintf("%d %s", n, many)
}
// atMost keeps a list readable. Somebody with two hundred saved networks
// does not need all two hundred printed to understand the point.
func atMost(lines []string, n int) []string {
if len(lines) <= n {
return lines
}
kept := make([]string, 0, n+1)
kept = append(kept, lines[:n]...)
return append(kept, fmt.Sprintf("... and %d more", len(lines)-n))
}