@@ -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