This repository has been archived by the owner on Apr 15, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathui.go
222 lines (201 loc) · 6.85 KB
/
ui.go
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package ortfomk
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/mattn/go-isatty"
"github.com/mitchellh/colorstring"
"github.com/theckman/yacspin"
)
// A yacspin spinner or a dummy spinner that does nothing.
// Used to avoid having to check for nil pointers everywhere when --silent is set.
type Spinner interface {
Start() error
Stop() error
Message(string)
Pause() error
Unpause() error
}
type DummySpinner struct {
}
func (d DummySpinner) Start() error { return nil }
func (d DummySpinner) Stop() error { return nil }
func (d DummySpinner) Message(string) {}
func (d DummySpinner) Pause() error { return nil }
func (d DummySpinner) Unpause() error { return nil }
func CreateSpinner() Spinner {
writer := os.Stdout
// Don't clog stdout if we're not in a tty
if !isatty.IsTerminal(os.Stdout.Fd()) {
writer = os.Stderr
}
spinner, err := yacspin.New(yacspin.Config{
Writer: writer,
Frequency: 100 * time.Millisecond,
Suffix: " ",
Message: " 0% Warming up",
CharSet: yacspin.CharSets[14],
Colors: []string{"fgCyan"},
StopCharacter: "✓",
StopColors: []string{"fgGreen"},
StopMessage: colorstring.Color(fmt.Sprintf("Website built to [bold]./%s[reset]", g.OutputDirectory)),
StopFailCharacter: "✗",
StopFailColors: []string{"fgRed"},
ShowCursor: true, // XXX temporary, as currently the cursors is not shown back when the user Ctrl-Cs
})
if err != nil {
LogError("Couldn't start spinner: %s", err)
return DummySpinner{}
}
if g.Flags.Silent {
return DummySpinner{}
}
return spinner
}
// absorb absorbs errors and returns the inner value, silently ignoring errors.
// It's kind of like Rust's .unwrap_or_default().
// Use wisely and as sparsely as possible.
func absorb[T any](value T, err error) (val T) {
if err == nil {
val = value
}
return
}
func UpdateSpinner() {
var message string
cwdRel := func(p string) string {
if pretty, err := filepath.Rel(absorb(os.Getwd()), p); err == nil {
return pretty
} else {
return p
}
}
switch g.Progress.Step {
case StepBuildPage:
message = fmt.Sprintf("Building page [magenta]%s[reset] as [magenta]%s[reset]", cwdRel(g.Progress.File), cwdRel(g.CurrentOutputFile))
case StepDeadLinks:
message = "Checking for dead links (this might take a while, disable it with DEADLINKS_CHECK=0)"
case StepGeneratePDF:
message = fmt.Sprintf("Generating PDF for [magenta]%s[reset] as [magenta]%s[reset]", cwdRel(g.Progress.File), cwdRel(g.CurrentOutputFile))
case StepLoadExternalSites:
message = fmt.Sprintf("Loading external sites from [magenta]%s[reset]", cwdRel(g.Progress.File))
case StepLoadTags:
message = fmt.Sprintf("Loading tags from [magenta]%s[reset]", cwdRel(g.Progress.File))
case StepLoadTechnologies:
message = fmt.Sprintf("Loading technologies from [magenta]%s[reset]", cwdRel(g.Progress.File))
case StepLoadTranslations:
message = fmt.Sprintf("Loading translations from [magenta]%s[reset]", cwdRel(g.Progress.File))
case StepLoadWorks:
message = fmt.Sprintf("Loading works from database [magenta]%s[reset]", cwdRel(g.Progress.File))
case StepLoadCollections:
message = fmt.Sprintf("Loading work collections from [magenta]%s[reset]", cwdRel(g.Progress.File))
default:
message = string(g.Progress.Step)
}
var currentObjectType = ""
if strings.Contains(g.Progress.File, ":work") {
currentObjectType = "work"
} else if strings.Contains(g.Progress.File, ":tag") {
currentObjectType = "tag"
} else if strings.Contains(g.Progress.File, ":tech") {
currentObjectType = "tech"
} else if strings.Contains(g.Progress.File, ":site") {
currentObjectType = "site"
} else if strings.Contains(g.Progress.File, ":collection") {
currentObjectType = "collection"
} else if g.Progress.Step == StepBuildPage || g.Progress.Step == StepGeneratePDF {
currentObjectType = "page"
}
fullMessage := colorstring.Color(fmt.Sprintf(
"[light_blue]%3d%%[reset] [bold][green]%s[reset] [bold]%s [yellow]%s[reset][bold][dim]:[reset] %s…",
g.ProgressPercent(),
currentObjectType,
g.CurrentObjectID,
g.CurrentLanguage,
message,
))
g.Spinner.Message(fullMessage)
}
// LogError logs non-fatal errors.
func LogError(message string, fmtArgs ...interface{}) {
spinner.Pause()
colorstring.Fprintf(os.Stderr, "\033[2K\r[red]error[reset] [bold][dim](%s)[reset] %s\n", currentWorkID, fmt.Sprintf(message, fmtArgs...))
spinner.Unpause()
}
// LogFatal logs fatal errors.
func LogFatal(message string, fmtArgs ...interface{}) {
spinner.Pause()
colorstring.Fprintf(os.Stderr, "\033[2K\r[invert][bold][red]crash[reset] [bold][dim](%s)[reset] %s\n", currentWorkID, fmt.Sprintf(message, fmtArgs...))
spinner.Unpause()
}
// LogInfo logs infos.
func LogInfo(message string, fmtArgs ...interface{}) {
spinner.Pause()
colorstring.Fprintf(os.Stderr, "\033[2K\r[blue]info [reset] [bold][dim](%s)[reset] %s\n", currentWorkID, fmt.Sprintf(message, fmtArgs...))
spinner.Unpause()
}
var lastDebugTimestamp time.Time = time.Now()
// LogDebug logs debug messages.
func LogDebug(message string, fmtArgs ...interface{}) {
if os.Getenv("DEBUG") != "1" {
return
}
spinner.Pause()
duration := time.Since(lastDebugTimestamp)
colorstring.Fprintf(os.Stderr, "\033[2K\r[magenta]debug[reset] [bold][dim](%s) %s[reset] %s\n", currentWorkID, duration.String(), fmt.Sprintf(message, fmtArgs...))
lastDebugTimestamp = time.Now()
spinner.Unpause()
}
// LogWarning logs warnings.
func LogWarning(message string, fmtArgs ...interface{}) {
spinner.Pause()
colorstring.Fprintf(os.Stderr, "\033[2K\r[yellow]warn [reset] [bold][dim](%s)[reset] %s\n", currentWorkID, fmt.Sprintf(message, fmtArgs...))
spinner.Unpause()
}
// codeSpinnetAround shows a 10-line code exerpt around the given line.
// Line numbers are displayed on the left.
func codeSpinnetAround(file string, lineNumber uint64, columnNumber uint64) string {
output := ""
for i, line := range strings.Split(file, "\n") {
if uint64(i) >= lineNumber-1-5 && uint64(i) <= lineNumber-1+5 {
line := fmt.Sprintf("%3d | %s\n", i+1, truncateLineAround(line, int(columnNumber)-1, 200))
if uint64(i) == lineNumber-1 {
line = "> " + line
} else {
line = " " + line
}
output += line
}
}
return output
}
// truncateLineAround truncates a line to a maximum length, centered around the character the given index.
// when the line exceeds the maximum length, it is truncated at the given index.
func truncateLineAround(line string, columnIndex int, maxLength int) string {
if len(line) <= maxLength {
return line
}
var ellipsisLeft, ellipsisRight bool
start := columnIndex - maxLength/2
if start < 0 {
start = 0
} else {
ellipsisLeft = true
}
end := start + maxLength
if end > len(line) {
end = len(line)
} else {
ellipsisRight = true
}
output := line[start:end]
if ellipsisLeft {
output = "…" + output
}
if ellipsisRight {
output = output + "…"
}
return output
}