@@ -25,7 +25,27 @@ const (
25
25
IssueNameStyleRegexp = "regexp"
26
26
)
27
27
28
- var (
28
+ // CSS class for action keywords (e.g. "closes: #1")
29
+ const keywordClass = "issue-keyword"
30
+
31
+ type globalVarsType struct {
32
+ hashCurrentPattern * regexp.Regexp
33
+ shortLinkPattern * regexp.Regexp
34
+ anyHashPattern * regexp.Regexp
35
+ comparePattern * regexp.Regexp
36
+ fullURLPattern * regexp.Regexp
37
+ emailRegex * regexp.Regexp
38
+ blackfridayExtRegex * regexp.Regexp
39
+ emojiShortCodeRegex * regexp.Regexp
40
+ issueFullPattern * regexp.Regexp
41
+ filesChangedFullPattern * regexp.Regexp
42
+
43
+ tagCleaner * regexp.Regexp
44
+ nulCleaner * strings.Replacer
45
+ }
46
+
47
+ var globalVars = sync.OnceValue [* globalVarsType ](func () * globalVarsType {
48
+ v := & globalVarsType {}
29
49
// NOTE: All below regex matching do not perform any extra validation.
30
50
// Thus a link is produced even if the linked entity does not exist.
31
51
// While fast, this is also incorrect and lead to false positives.
@@ -36,79 +56,58 @@ var (
36
56
// hashCurrentPattern matches string that represents a commit SHA, e.g. d8a994ef243349f321568f9e36d5c3f444b99cae
37
57
// Although SHA1 hashes are 40 chars long, SHA256 are 64, the regex matches the hash from 7 to 64 chars in length
38
58
// so that abbreviated hash links can be used as well. This matches git and GitHub usability.
39
- hashCurrentPattern = regexp .MustCompile (`(?:\s|^|\(|\[)([0-9a-f]{7,64})(?:\s|$|\)|\]|[.,:](\s|$))` )
59
+ v . hashCurrentPattern = regexp .MustCompile (`(?:\s|^|\(|\[)([0-9a-f]{7,64})(?:\s|$|\)|\]|[.,:](\s|$))` )
40
60
41
61
// shortLinkPattern matches short but difficult to parse [[name|link|arg=test]] syntax
42
- shortLinkPattern = regexp .MustCompile (`\[\[(.*?)\]\](\w*)` )
62
+ v . shortLinkPattern = regexp .MustCompile (`\[\[(.*?)\]\](\w*)` )
43
63
44
64
// anyHashPattern splits url containing SHA into parts
45
- anyHashPattern = regexp .MustCompile (`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~%./\w]+)?(\?[-+~%.\w&=]+)?(#[-+~%.\w]+)?` )
65
+ v . anyHashPattern = regexp .MustCompile (`https?://(?:\S+/){4,5}([0-9a-f]{40,64})(/[-+~%./\w]+)?(\?[-+~%.\w&=]+)?(#[-+~%.\w]+)?` )
46
66
47
67
// comparePattern matches "http://domain/org/repo/compare/COMMIT1...COMMIT2#hash"
48
- comparePattern = regexp .MustCompile (`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?` )
68
+ v . comparePattern = regexp .MustCompile (`https?://(?:\S+/){4,5}([0-9a-f]{7,64})(\.\.\.?)([0-9a-f]{7,64})?(#[-+~_%.a-zA-Z0-9]+)?` )
49
69
50
70
// fullURLPattern matches full URL like "mailto:...", "https://..." and "ssh+git://..."
51
- fullURLPattern = regexp .MustCompile (`^[a-z][-+\w]+:` )
71
+ v . fullURLPattern = regexp .MustCompile (`^[a-z][-+\w]+:` )
52
72
53
73
// emailRegex is definitely not perfect with edge cases,
54
74
// it is still accepted by the CommonMark specification, as well as the HTML5 spec:
55
75
// http://spec.commonmark.org/0.28/#email-address
56
76
// https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail)
57
- emailRegex = regexp .MustCompile ("(?:\\ s|^|\\ (|\\ [)([a-zA-Z0-9.!#$%&'*+\\ /=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\ .[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\ s|$|\\ )|\\ ]|;|,|\\ ?|!|\\ .(\\ s|$))" )
77
+ v . emailRegex = regexp .MustCompile ("(?:\\ s|^|\\ (|\\ [)([a-zA-Z0-9.!#$%&'*+\\ /=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\ .[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\ s|$|\\ )|\\ ]|;|,|\\ ?|!|\\ .(\\ s|$))" )
58
78
59
79
// blackfridayExtRegex is for blackfriday extensions create IDs like fn:user-content-footnote
60
- blackfridayExtRegex = regexp .MustCompile (`[^:]*:user-content-` )
80
+ v . blackfridayExtRegex = regexp .MustCompile (`[^:]*:user-content-` )
61
81
62
82
// emojiShortCodeRegex find emoji by alias like :smile:
63
- emojiShortCodeRegex = regexp .MustCompile (`:[-+\w]+:` )
64
- )
83
+ v .emojiShortCodeRegex = regexp .MustCompile (`:[-+\w]+:` )
65
84
66
- // CSS class for action keywords (e.g. "closes: #1")
67
- const keywordClass = "issue-keyword"
85
+ // example: https://domain/org/repo/pulls/27#hash
86
+ v .issueFullPattern = regexp .MustCompile (regexp .QuoteMeta (setting .AppURL ) +
87
+ `[\w_.-]+/[\w_.-]+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b` )
88
+
89
+ // example: https://domain/org/repo/pulls/27/files#hash
90
+ v .filesChangedFullPattern = regexp .MustCompile (regexp .QuoteMeta (setting .AppURL ) +
91
+ `[\w_.-]+/[\w_.-]+/pulls/((?:\w{1,10}-)?[1-9][0-9]*)/files([\?|#](\S+)?)?\b` )
92
+
93
+ v .tagCleaner = regexp .MustCompile (`<((?:/?\w+/\w+)|(?:/[\w ]+/)|(/?[hH][tT][mM][lL]\b)|(/?[hH][eE][aA][dD]\b))` )
94
+ v .nulCleaner = strings .NewReplacer ("\000 " , "" )
95
+ return v
96
+ })
68
97
69
98
// IsFullURLBytes reports whether link fits valid format.
70
99
func IsFullURLBytes (link []byte ) bool {
71
- return fullURLPattern .Match (link )
100
+ return globalVars (). fullURLPattern .Match (link )
72
101
}
73
102
74
103
func IsFullURLString (link string ) bool {
75
- return fullURLPattern .MatchString (link )
104
+ return globalVars (). fullURLPattern .MatchString (link )
76
105
}
77
106
78
107
func IsNonEmptyRelativePath (link string ) bool {
79
108
return link != "" && ! IsFullURLString (link ) && link [0 ] != '/' && link [0 ] != '?' && link [0 ] != '#'
80
109
}
81
110
82
- // regexp for full links to issues/pulls
83
- var issueFullPattern * regexp.Regexp
84
-
85
- // Once for to prevent races
86
- var issueFullPatternOnce sync.Once
87
-
88
- // regexp for full links to hash comment in pull request files changed tab
89
- var filesChangedFullPattern * regexp.Regexp
90
-
91
- // Once for to prevent races
92
- var filesChangedFullPatternOnce sync.Once
93
-
94
- func getIssueFullPattern () * regexp.Regexp {
95
- issueFullPatternOnce .Do (func () {
96
- // example: https://domain/org/repo/pulls/27#hash
97
- issueFullPattern = regexp .MustCompile (regexp .QuoteMeta (setting .AppURL ) +
98
- `[\w_.-]+/[\w_.-]+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#](\S+)?)?\b` )
99
- })
100
- return issueFullPattern
101
- }
102
-
103
- func getFilesChangedFullPattern () * regexp.Regexp {
104
- filesChangedFullPatternOnce .Do (func () {
105
- // example: https://domain/org/repo/pulls/27/files#hash
106
- filesChangedFullPattern = regexp .MustCompile (regexp .QuoteMeta (setting .AppURL ) +
107
- `[\w_.-]+/[\w_.-]+/pulls/((?:\w{1,10}-)?[1-9][0-9]*)/files([\?|#](\S+)?)?\b` )
108
- })
109
- return filesChangedFullPattern
110
- }
111
-
112
111
// CustomLinkURLSchemes allows for additional schemes to be detected when parsing links within text
113
112
func CustomLinkURLSchemes (schemes []string ) {
114
113
schemes = append (schemes , "http" , "https" )
@@ -286,11 +285,6 @@ func RenderEmoji(
286
285
return renderProcessString (ctx , emojiProcessors , content )
287
286
}
288
287
289
- var (
290
- tagCleaner = regexp .MustCompile (`<((?:/?\w+/\w+)|(?:/[\w ]+/)|(/?[hH][tT][mM][lL]\b)|(/?[hH][eE][aA][dD]\b))` )
291
- nulCleaner = strings .NewReplacer ("\000 " , "" )
292
- )
293
-
294
288
func postProcess (ctx * RenderContext , procs []processor , input io.Reader , output io.Writer ) error {
295
289
defer ctx .Cancel ()
296
290
// FIXME: don't read all content to memory
@@ -304,7 +298,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output
304
298
// prepend "<html><body>"
305
299
strings .NewReader ("<html><body>" ),
306
300
// Strip out nuls - they're always invalid
307
- bytes .NewReader (tagCleaner .ReplaceAll ([]byte (nulCleaner .Replace (string (rawHTML ))), []byte ("<$1" ))),
301
+ bytes .NewReader (globalVars (). tagCleaner .ReplaceAll ([]byte (globalVars (). nulCleaner .Replace (string (rawHTML ))), []byte ("<$1" ))),
308
302
// close the tags
309
303
strings .NewReader ("</body></html>" ),
310
304
))
@@ -351,7 +345,7 @@ func visitNode(ctx *RenderContext, procs []processor, node *html.Node) *html.Nod
351
345
// Add user-content- to IDs and "#" links if they don't already have them
352
346
for idx , attr := range node .Attr {
353
347
val := strings .TrimPrefix (attr .Val , "#" )
354
- notHasPrefix := ! (strings .HasPrefix (val , "user-content-" ) || blackfridayExtRegex .MatchString (val ))
348
+ notHasPrefix := ! (strings .HasPrefix (val , "user-content-" ) || globalVars (). blackfridayExtRegex .MatchString (val ))
355
349
356
350
if attr .Key == "id" && notHasPrefix {
357
351
node .Attr [idx ].Val = "user-content-" + attr .Val
0 commit comments