@@ -25,7 +25,27 @@ const (
2525 IssueNameStyleRegexp = "regexp"
2626)
2727
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 {}
2949 // NOTE: All below regex matching do not perform any extra validation.
3050 // Thus a link is produced even if the linked entity does not exist.
3151 // While fast, this is also incorrect and lead to false positives.
@@ -36,79 +56,58 @@ var (
3656 // hashCurrentPattern matches string that represents a commit SHA, e.g. d8a994ef243349f321568f9e36d5c3f444b99cae
3757 // Although SHA1 hashes are 40 chars long, SHA256 are 64, the regex matches the hash from 7 to 64 chars in length
3858 // 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|$))` )
4060
4161 // shortLinkPattern matches short but difficult to parse [[name|link|arg=test]] syntax
42- shortLinkPattern = regexp .MustCompile (`\[\[(.*?)\]\](\w*)` )
62+ v . shortLinkPattern = regexp .MustCompile (`\[\[(.*?)\]\](\w*)` )
4363
4464 // 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]+)?` )
4666
4767 // 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]+)?` )
4969
5070 // 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]+:` )
5272
5373 // emailRegex is definitely not perfect with edge cases,
5474 // it is still accepted by the CommonMark specification, as well as the HTML5 spec:
5575 // http://spec.commonmark.org/0.28/#email-address
5676 // 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|$))" )
5878
5979 // 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-` )
6181
6282 // emojiShortCodeRegex find emoji by alias like :smile:
63- emojiShortCodeRegex = regexp .MustCompile (`:[-+\w]+:` )
64- )
83+ v .emojiShortCodeRegex = regexp .MustCompile (`:[-+\w]+:` )
6584
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+ })
6897
6998// IsFullURLBytes reports whether link fits valid format.
7099func IsFullURLBytes (link []byte ) bool {
71- return fullURLPattern .Match (link )
100+ return globalVars (). fullURLPattern .Match (link )
72101}
73102
74103func IsFullURLString (link string ) bool {
75- return fullURLPattern .MatchString (link )
104+ return globalVars (). fullURLPattern .MatchString (link )
76105}
77106
78107func IsNonEmptyRelativePath (link string ) bool {
79108 return link != "" && ! IsFullURLString (link ) && link [0 ] != '/' && link [0 ] != '?' && link [0 ] != '#'
80109}
81110
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-
112111// CustomLinkURLSchemes allows for additional schemes to be detected when parsing links within text
113112func CustomLinkURLSchemes (schemes []string ) {
114113 schemes = append (schemes , "http" , "https" )
@@ -286,11 +285,6 @@ func RenderEmoji(
286285 return renderProcessString (ctx , emojiProcessors , content )
287286}
288287
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-
294288func postProcess (ctx * RenderContext , procs []processor , input io.Reader , output io.Writer ) error {
295289 defer ctx .Cancel ()
296290 // FIXME: don't read all content to memory
@@ -304,7 +298,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output
304298 // prepend "<html><body>"
305299 strings .NewReader ("<html><body>" ),
306300 // 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" ))),
308302 // close the tags
309303 strings .NewReader ("</body></html>" ),
310304 ))
@@ -351,7 +345,7 @@ func visitNode(ctx *RenderContext, procs []processor, node *html.Node) *html.Nod
351345 // Add user-content- to IDs and "#" links if they don't already have them
352346 for idx , attr := range node .Attr {
353347 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 ))
355349
356350 if attr .Key == "id" && notHasPrefix {
357351 node .Attr [idx ].Val = "user-content-" + attr .Val
0 commit comments