loading up the forgejo repo on tangled to test page performance
1// Copyright 2018 The Gitea Authors. All rights reserved.
2// SPDX-License-Identifier: MIT
3
4package markup
5
6import (
7 "bufio"
8 "html"
9 "io"
10 "regexp"
11 "strconv"
12
13 "forgejo.org/modules/csv"
14 "forgejo.org/modules/markup"
15 "forgejo.org/modules/setting"
16 "forgejo.org/modules/translation"
17 "forgejo.org/modules/util"
18)
19
20func init() {
21 markup.RegisterRenderer(Renderer{})
22}
23
24// Renderer implements markup.Renderer for csv files
25type Renderer struct{}
26
27// Name implements markup.Renderer
28func (Renderer) Name() string {
29 return "csv"
30}
31
32// Extensions implements markup.Renderer
33func (Renderer) Extensions() []string {
34 return []string{".csv", ".tsv"}
35}
36
37// SanitizerRules implements markup.Renderer
38func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
39 return []setting.MarkupSanitizerRule{
40 {Element: "table", AllowAttr: "class", Regexp: regexp.MustCompile(`^data-table$`)},
41 {Element: "th", AllowAttr: "class", Regexp: regexp.MustCompile(`^line-num$`)},
42 {Element: "td", AllowAttr: "class", Regexp: regexp.MustCompile(`^line-num$`)},
43 }
44}
45
46func writeField(w io.Writer, element, class, field string) error {
47 if _, err := io.WriteString(w, "<"); err != nil {
48 return err
49 }
50 if _, err := io.WriteString(w, element); err != nil {
51 return err
52 }
53 if len(class) > 0 {
54 if _, err := io.WriteString(w, " class=\""); err != nil {
55 return err
56 }
57 if _, err := io.WriteString(w, class); err != nil {
58 return err
59 }
60 if _, err := io.WriteString(w, "\""); err != nil {
61 return err
62 }
63 }
64 if _, err := io.WriteString(w, ">"); err != nil {
65 return err
66 }
67 if _, err := io.WriteString(w, html.EscapeString(field)); err != nil {
68 return err
69 }
70 if _, err := io.WriteString(w, "</"); err != nil {
71 return err
72 }
73 if _, err := io.WriteString(w, element); err != nil {
74 return err
75 }
76 _, err := io.WriteString(w, ">")
77 return err
78}
79
80// Render implements markup.Renderer
81func (r Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
82 tmpBlock := bufio.NewWriter(output)
83 maxSize := setting.UI.CSV.MaxFileSize
84 maxRows := setting.UI.CSV.MaxRows
85
86 if maxSize != 0 {
87 input = io.LimitReader(input, maxSize+1)
88 }
89
90 rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, input)
91 if err != nil {
92 return err
93 }
94 if _, err := tmpBlock.WriteString(`<table class="data-table">`); err != nil {
95 return err
96 }
97
98 row := 0
99 for {
100 fields, err := rd.Read()
101 if err == io.EOF || (row >= maxRows && maxRows != 0) {
102 break
103 }
104 if err != nil {
105 continue
106 }
107
108 if _, err := tmpBlock.WriteString("<tr>"); err != nil {
109 return err
110 }
111 element := "td"
112 if row == 0 {
113 element = "th"
114 }
115 if err := writeField(tmpBlock, element, "line-num", strconv.Itoa(row+1)); err != nil {
116 return err
117 }
118 for _, field := range fields {
119 if err := writeField(tmpBlock, element, "", field); err != nil {
120 return err
121 }
122 }
123 if _, err := tmpBlock.WriteString("</tr>"); err != nil {
124 return err
125 }
126
127 row++
128 }
129
130 if _, err = tmpBlock.WriteString("</table>"); err != nil {
131 return err
132 }
133
134 // Check if maxRows or maxSize is reached, and if true, warn.
135 if (row >= maxRows && maxRows != 0) || (rd.InputOffset() >= maxSize && maxSize != 0) {
136 warn := `<table class="data-table"><tr><td>`
137 rawLink := ` <a href="` + ctx.Links.RawLink() + `/` + util.PathEscapeSegments(ctx.RelativePath) + `">`
138
139 // Try to get the user translation
140 if locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale); ok {
141 warn += locale.TrString("repo.file_too_large")
142 rawLink += locale.TrString("repo.file_view_raw")
143 } else {
144 warn += "The file is too large to be shown."
145 rawLink += "View Raw"
146 }
147
148 warn += rawLink + `</a></td></tr></table>`
149
150 // Write the HTML string to the output
151 if _, err := tmpBlock.WriteString(warn); err != nil {
152 return err
153 }
154 }
155
156 return tmpBlock.Flush()
157}