github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/present/style.go (about) 1 // Copyright 2012 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package present 6 7 import ( 8 "bytes" 9 "html" 10 "html/template" 11 "strings" 12 "unicode" 13 "unicode/utf8" 14 ) 15 16 /* 17 Fonts are demarcated by an initial and final char bracketing a 18 space-delimited word, plus possibly some terminal punctuation. 19 The chars are 20 _ for italic 21 * for bold 22 ` (back quote) for fixed width. 23 Inner appearances of the char become spaces. For instance, 24 _this_is_italic_! 25 becomes 26 <i>this is italic</i>! 27 */ 28 29 func init() { 30 funcs["style"] = Style 31 } 32 33 // Style returns s with HTML entities escaped and font indicators turned into 34 // HTML font tags. 35 func Style(s string) template.HTML { 36 return template.HTML(font(html.EscapeString(s))) 37 } 38 39 // font returns s with font indicators turned into HTML font tags. 40 func font(s string) string { 41 if !strings.ContainsAny(s, "[`_*") { 42 return s 43 } 44 words := split(s) 45 var b bytes.Buffer 46 Word: 47 for w, word := range words { 48 if len(word) < 2 { 49 continue Word 50 } 51 if link, _ := parseInlineLink(word); link != "" { 52 words[w] = link 53 continue Word 54 } 55 const marker = "_*`" 56 // Initial punctuation is OK but must be peeled off. 57 first := strings.IndexAny(word, marker) 58 if first == -1 { 59 continue Word 60 } 61 // Opening marker must be at the beginning of the token or else preceded by punctuation. 62 if first != 0 { 63 r, _ := utf8.DecodeLastRuneInString(word[:first]) 64 if !unicode.IsPunct(r) { 65 continue Word 66 } 67 } 68 open, word := word[:first], word[first:] 69 char := word[0] // ASCII is OK. 70 close := "" 71 switch char { 72 default: 73 continue Word 74 case '_': 75 open += "<i>" 76 close = "</i>" 77 case '*': 78 open += "<b>" 79 close = "</b>" 80 case '`': 81 open += "<code>" 82 close = "</code>" 83 } 84 // Closing marker must be at the end of the token or else followed by punctuation. 85 last := strings.LastIndex(word, word[:1]) 86 if last == 0 { 87 continue Word 88 } 89 if last+1 != len(word) { 90 r, _ := utf8.DecodeRuneInString(word[last+1:]) 91 if !unicode.IsPunct(r) { 92 continue Word 93 } 94 } 95 head, tail := word[:last+1], word[last+1:] 96 b.Reset() 97 b.WriteString(open) 98 var wid int 99 for i := 1; i < len(head)-1; i += wid { 100 var r rune 101 r, wid = utf8.DecodeRuneInString(head[i:]) 102 if r != rune(char) { 103 // Ordinary character. 104 b.WriteRune(r) 105 continue 106 } 107 if head[i+1] != char { 108 // Inner char becomes space. 109 b.WriteRune(' ') 110 continue 111 } 112 // Doubled char becomes real char. 113 // Not worth worrying about "_x__". 114 b.WriteByte(char) 115 wid++ // Consumed two chars, both ASCII. 116 } 117 b.WriteString(close) // Write closing tag. 118 b.WriteString(tail) // Restore trailing punctuation. 119 words[w] = b.String() 120 } 121 return strings.Join(words, "") 122 } 123 124 // split is like strings.Fields but also returns the runs of spaces 125 // and treats inline links as distinct words. 126 func split(s string) []string { 127 var ( 128 words = make([]string, 0, 10) 129 start = 0 130 ) 131 132 // appendWord appends the string s[start:end] to the words slice. 133 // If the word contains the beginning of a link, the non-link portion 134 // of the word and the entire link are appended as separate words, 135 // and the start index is advanced to the end of the link. 136 appendWord := func(end int) { 137 if j := strings.Index(s[start:end], "[["); j > -1 { 138 if _, l := parseInlineLink(s[start+j:]); l > 0 { 139 // Append portion before link, if any. 140 if j > 0 { 141 words = append(words, s[start:start+j]) 142 } 143 // Append link itself. 144 words = append(words, s[start+j:start+j+l]) 145 // Advance start index to end of link. 146 start = start + j + l 147 return 148 } 149 } 150 // No link; just add the word. 151 words = append(words, s[start:end]) 152 start = end 153 } 154 155 wasSpace := false 156 for i, r := range s { 157 isSpace := unicode.IsSpace(r) 158 if i > start && isSpace != wasSpace { 159 appendWord(i) 160 } 161 wasSpace = isSpace 162 } 163 for start < len(s) { 164 appendWord(len(s)) 165 } 166 return words 167 }