snapshot of 8b9e9fcfc8b13c2d29024f6f756b28bc6851512b Annotations about the code that implements koment.

internal/ui/view.go

1 package ui
2
3 import (
4 "net/url"
5 "strings"
6
7 "github.com/koment-dev/koment/internal/anchor"
8 "github.com/koment-dev/koment/internal/application"
9 "github.com/koment-dev/koment/internal/store"
10 )
11
12 const sourceURL = "https://github.com/koment-dev/koment"
13
14 type view struct {
15 Total int
16 Tally []tallyEntry
17 Tree []treeNode
18 Loose []entry
19 Repositories []repositoryLink
20 Repository string
21 Current string
22 File *fileView
23 Empty bool
24 NotFound bool
25 Home string
26 Stylesheet string
27 Script string
28 LogoSVG string
29 LogoPNG string
30 SourceURL string
31 Snapshot *snapshot
32 WriteToken string
33 CanWrite bool
34 CreatedID string
35 WriteWarning string
36 ReviewURL string
37 }
38
39 type snapshot struct {
40 Commit string
41 CommitURL string
42 Banner string
43 BannerHref string
44 }
45
46 type repositoryLink struct {
47 ID string
48 Name string
49 Href string
50 Current bool
51 }
52
53 type links struct {
54 file func(target string) string
55 home string
56 stylesheet string
57 script string
58 logoSVG string
59 logoPNG string
60 }
61
62 func servedLinks(repositoryID string) links {
63 base := repositoryPrefix + repositoryID + "/"
64 return links{
65 file: func(target string) string { return base + "f/" + escapedFilePath(target) },
66 home: base,
67 stylesheet: "/assets/style.css",
68 script: "/assets/koment.js",
69 logoSVG: "/assets/koment-logo.svg",
70 logoPNG: "/assets/koment-logo.png",
71 }
72 }
73
74 func escapedFilePath(file string) string {
75 parts := strings.Split(file, "/")
76 for index, part := range parts {
77 parts[index] = url.PathEscape(part)
78 }
79 return strings.Join(parts, "/")
80 }
81
82 type tallyEntry struct {
83 Status anchor.Status
84 Count int
85 }
86
87 type entry struct {
88 Path string
89 Name string
90 Href string
91 Count int
92 Worst anchor.Status
93 Current bool
94 Search string
95 }
96
97 type fileView struct {
98 Path string
99 Lines []line
100 Notes []note
101 Detached []note
102 Missing bool
103 }
104
105 type line struct {
106 Number int
107 Text string
108 Marker anchor.Status
109 }
110
111 type note struct {
112 ID string
113 Kind string
114 Title string
115 Status anchor.Status
116 Line int
117 Body []string
118 Created string
119 Excerpt string
120 Stale bool
121 Warning string
122 }
123
124 var statusOrder = []anchor.Status{
125 anchor.StatusOK, anchor.StatusAmbiguous, anchor.StatusDrifted, anchor.StatusOrphaned,
126 }
127
128 func build(repositorySnapshot *application.RepositorySnapshot, requested string, how links) (*view, error) {
129 if len(repositorySnapshot.Files) == 0 {
130 return &view{
131 Empty: true, Stylesheet: how.stylesheet, Script: how.script,
132 Home: how.home, LogoSVG: how.logoSVG, LogoPNG: how.logoPNG, SourceURL: sourceURL,
133 }, nil
134 }
135
136 current := requested
137 if current == "" {
138 current = repositorySnapshot.Files[0].Path
139 }
140
141 built := &view{
142 Current: current,
143 Home: how.home,
144 Stylesheet: how.stylesheet,
145 Script: how.script,
146 LogoSVG: how.logoSVG,
147 LogoPNG: how.logoPNG,
148 SourceURL: sourceURL,
149 }
150 counts := map[anchor.Status]int{}
151 listed := make([]entry, 0, len(repositorySnapshot.Files))
152
153 for _, file := range repositorySnapshot.Files {
154 worst := anchor.StatusOK
155 var searchable strings.Builder
156 searchable.WriteString(file.Path)
157 for _, annotation := range file.Annotations {
158 counts[annotation.Status]++
159 built.Total++
160 if statusSeverity[annotation.Status] > statusSeverity[worst] {
161 worst = annotation.Status
162 }
163 searchable.WriteString("\n" + string(annotation.Record.Spec.Type) + "\n" + annotation.Record.Headline() + "\n" + annotation.Record.Spec.Body + "\n" + annotation.Record.Spec.Author.Name)
164 }
165
166 listed = append(listed, entry{
167 Path: file.Path,
168 Name: baseName(file.Path),
169 Href: how.file(file.Path),
170 Count: len(file.Annotations),
171 Worst: worst,
172 Current: file.Path == current,
173 Search: strings.ToLower(searchable.String()),
174 })
175
176 if file.Path == current {
177 built.File = buildFile(file)
178 }
179 }
180
181 if built.File == nil {
182 built.NotFound = true
183 }
184 built.Tally = tallyOf(counts)
185 built.Tree, built.Loose = buildTree(listed, current)
186 return built, nil
187 }
188
189 func baseName(file string) string {
190 if cut := strings.LastIndex(file, "/"); cut >= 0 {
191 return file[cut+1:]
192 }
193 return file
194 }
195
196 func tallyOf(counts map[anchor.Status]int) []tallyEntry {
197 var tally []tallyEntry
198 for _, status := range statusOrder {
199 if counts[status] > 0 {
200 tally = append(tally, tallyEntry{Status: status, Count: counts[status]})
201 }
202 }
203 return tally
204 }
205
206 func buildFile(file application.FileSnapshot) *fileView {
207 built := &fileView{Path: file.Path}
208 if !file.Exists {
209 built.Missing = true
210 for _, annotation := range file.Annotations {
211 built.Detached = append(built.Detached, describe(annotation))
212 }
213 return built
214 }
215
216 marked := map[int]anchor.Status{}
217 for _, annotation := range file.Annotations {
218 described := describe(annotation)
219 if annotation.Line == 0 {
220 built.Detached = append(built.Detached, described)
221 continue
222 }
223 built.Notes = append(built.Notes, described)
224 worst, seen := marked[annotation.Line]
225 if !seen || statusSeverity[annotation.Status] > statusSeverity[worst] {
226 marked[annotation.Line] = annotation.Status
227 }
228 }
229
230 for i, text := range strings.Split(strings.TrimSuffix(string(file.Content), "\n"), "\n") {
231 number := i + 1
232 built.Lines = append(built.Lines, line{Number: number, Text: text, Marker: marked[number]})
233 }
234 return built
235 }
236
237 func describe(annotation application.AnnotationView) note {
238 stale := annotation.Status.IsFailure()
239 return note{
240 ID: annotation.Record.Metadata.ID,
241 Kind: string(annotation.Record.Spec.Type),
242 Status: annotation.Status,
243 Line: annotation.Line,
244 Title: annotation.Record.Headline(),
245 Body: store.Paragraphs(annotation.Record.Spec.Body),
246 Created: annotation.Record.Metadata.Created.Format("2006-01-02"),
247 Excerpt: annotation.Record.Spec.Anchor.Excerpt,
248 Stale: stale,
249 Warning: annotation.Warning,
250 }
251 }

Find an annotation

Search file paths, rationale, kinds, and authors.

moveEnter openEsc close