You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

helper.go 12 kB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
11 years ago
11 years ago
11 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package templates
  5. import (
  6. "bytes"
  7. "container/list"
  8. "encoding/json"
  9. "errors"
  10. "fmt"
  11. "html"
  12. "html/template"
  13. "mime"
  14. "net/url"
  15. "path/filepath"
  16. "runtime"
  17. "strings"
  18. "time"
  19. "code.gitea.io/gitea/models"
  20. "code.gitea.io/gitea/modules/base"
  21. "code.gitea.io/gitea/modules/log"
  22. "code.gitea.io/gitea/modules/markup"
  23. "code.gitea.io/gitea/modules/setting"
  24. "golang.org/x/net/html/charset"
  25. "golang.org/x/text/transform"
  26. "gopkg.in/editorconfig/editorconfig-core-go.v1"
  27. )
  28. // NewFuncMap returns functions for injecting to templates
  29. func NewFuncMap() []template.FuncMap {
  30. return []template.FuncMap{map[string]interface{}{
  31. "GoVer": func() string {
  32. return strings.Title(runtime.Version())
  33. },
  34. "UseHTTPS": func() bool {
  35. return strings.HasPrefix(setting.AppURL, "https")
  36. },
  37. "AppName": func() string {
  38. return setting.AppName
  39. },
  40. "AppSubUrl": func() string {
  41. return setting.AppSubURL
  42. },
  43. "AppUrl": func() string {
  44. return setting.AppURL
  45. },
  46. "AppVer": func() string {
  47. return setting.AppVer
  48. },
  49. "AppBuiltWith": func() string {
  50. return setting.AppBuiltWith
  51. },
  52. "AppDomain": func() string {
  53. return setting.Domain
  54. },
  55. "DisableGravatar": func() bool {
  56. return setting.DisableGravatar
  57. },
  58. "ShowFooterTemplateLoadTime": func() bool {
  59. return setting.ShowFooterTemplateLoadTime
  60. },
  61. "LoadTimes": func(startTime time.Time) string {
  62. return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
  63. },
  64. "AvatarLink": base.AvatarLink,
  65. "Safe": Safe,
  66. "SafeJS": SafeJS,
  67. "Str2html": Str2html,
  68. "TimeSince": base.TimeSince,
  69. "TimeSinceUnix": base.TimeSinceUnix,
  70. "RawTimeSince": base.RawTimeSince,
  71. "FileSize": base.FileSize,
  72. "Subtract": base.Subtract,
  73. "Add": func(a, b int) int {
  74. return a + b
  75. },
  76. "ActionIcon": ActionIcon,
  77. "DateFmtLong": func(t time.Time) string {
  78. return t.Format(time.RFC1123Z)
  79. },
  80. "DateFmtShort": func(t time.Time) string {
  81. return t.Format("Jan 02, 2006")
  82. },
  83. "SizeFmt": func(s int64) string {
  84. return base.FileSize(s)
  85. },
  86. "List": List,
  87. "SubStr": func(str string, start, length int) string {
  88. if len(str) == 0 {
  89. return ""
  90. }
  91. end := start + length
  92. if length == -1 {
  93. end = len(str)
  94. }
  95. if len(str) < end {
  96. return str
  97. }
  98. return str[start:end]
  99. },
  100. "EllipsisString": base.EllipsisString,
  101. "DiffTypeToStr": DiffTypeToStr,
  102. "DiffLineTypeToStr": DiffLineTypeToStr,
  103. "Sha1": Sha1,
  104. "ShortSha": base.ShortSha,
  105. "MD5": base.EncodeMD5,
  106. "ActionContent2Commits": ActionContent2Commits,
  107. "PathEscape": url.PathEscape,
  108. "EscapePound": func(str string) string {
  109. return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str)
  110. },
  111. "RenderCommitMessage": RenderCommitMessage,
  112. "RenderCommitMessageLink": RenderCommitMessageLink,
  113. "RenderCommitBody": RenderCommitBody,
  114. "IsMultilineCommitMessage": IsMultilineCommitMessage,
  115. "ThemeColorMetaTag": func() string {
  116. return setting.UI.ThemeColorMetaTag
  117. },
  118. "MetaAuthor": func() string {
  119. return setting.UI.Meta.Author
  120. },
  121. "MetaDescription": func() string {
  122. return setting.UI.Meta.Description
  123. },
  124. "MetaKeywords": func() string {
  125. return setting.UI.Meta.Keywords
  126. },
  127. "FilenameIsImage": func(filename string) bool {
  128. mimeType := mime.TypeByExtension(filepath.Ext(filename))
  129. return strings.HasPrefix(mimeType, "image/")
  130. },
  131. "TabSizeClass": func(ec *editorconfig.Editorconfig, filename string) string {
  132. if ec != nil {
  133. def := ec.GetDefinitionForFilename(filename)
  134. if def.TabWidth > 0 {
  135. return fmt.Sprintf("tab-size-%d", def.TabWidth)
  136. }
  137. }
  138. return "tab-size-8"
  139. },
  140. "SubJumpablePath": func(str string) []string {
  141. var path []string
  142. index := strings.LastIndex(str, "/")
  143. if index != -1 && index != len(str) {
  144. path = append(path, str[0:index+1])
  145. path = append(path, str[index+1:])
  146. } else {
  147. path = append(path, str)
  148. }
  149. return path
  150. },
  151. "JsonPrettyPrint": func(in string) string {
  152. var out bytes.Buffer
  153. err := json.Indent(&out, []byte(in), "", " ")
  154. if err != nil {
  155. return ""
  156. }
  157. return out.String()
  158. },
  159. "DisableGitHooks": func() bool {
  160. return setting.DisableGitHooks
  161. },
  162. "TrN": TrN,
  163. "Dict": func(values ...interface{}) (map[string]interface{}, error) {
  164. if len(values)%2 != 0 {
  165. return nil, errors.New("invalid dict call")
  166. }
  167. dict := make(map[string]interface{}, len(values)/2)
  168. for i := 0; i < len(values); i += 2 {
  169. key, ok := values[i].(string)
  170. if !ok {
  171. return nil, errors.New("dict keys must be strings")
  172. }
  173. dict[key] = values[i+1]
  174. }
  175. return dict, nil
  176. },
  177. "Printf": fmt.Sprintf,
  178. "Escape": Escape,
  179. }}
  180. }
  181. // Safe render raw as HTML
  182. func Safe(raw string) template.HTML {
  183. return template.HTML(raw)
  184. }
  185. // SafeJS renders raw as JS
  186. func SafeJS(raw string) template.JS {
  187. return template.JS(raw)
  188. }
  189. // Str2html render Markdown text to HTML
  190. func Str2html(raw string) template.HTML {
  191. return template.HTML(markup.Sanitize(raw))
  192. }
  193. // Escape escapes a HTML string
  194. func Escape(raw string) string {
  195. return html.EscapeString(raw)
  196. }
  197. // List traversings the list
  198. func List(l *list.List) chan interface{} {
  199. e := l.Front()
  200. c := make(chan interface{})
  201. go func() {
  202. for e != nil {
  203. c <- e.Value
  204. e = e.Next()
  205. }
  206. close(c)
  207. }()
  208. return c
  209. }
  210. // Sha1 returns sha1 sum of string
  211. func Sha1(str string) string {
  212. return base.EncodeSha1(str)
  213. }
  214. // ToUTF8WithErr converts content to UTF8 encoding
  215. func ToUTF8WithErr(content []byte) (string, error) {
  216. charsetLabel, err := base.DetectEncoding(content)
  217. if err != nil {
  218. return "", err
  219. } else if charsetLabel == "UTF-8" {
  220. return string(content), nil
  221. }
  222. encoding, _ := charset.Lookup(charsetLabel)
  223. if encoding == nil {
  224. return string(content), fmt.Errorf("Unknown encoding: %s", charsetLabel)
  225. }
  226. // If there is an error, we concatenate the nicely decoded part and the
  227. // original left over. This way we won't loose data.
  228. result, n, err := transform.String(encoding.NewDecoder(), string(content))
  229. if err != nil {
  230. result = result + string(content[n:])
  231. }
  232. return result, err
  233. }
  234. // ToUTF8 converts content to UTF8 encoding and ignore error
  235. func ToUTF8(content string) string {
  236. res, _ := ToUTF8WithErr([]byte(content))
  237. return res
  238. }
  239. // ReplaceLeft replaces all prefixes 'old' in 's' with 'new'.
  240. func ReplaceLeft(s, old, new string) string {
  241. oldLen, newLen, i, n := len(old), len(new), 0, 0
  242. for ; i < len(s) && strings.HasPrefix(s[i:], old); n++ {
  243. i += oldLen
  244. }
  245. // simple optimization
  246. if n == 0 {
  247. return s
  248. }
  249. // allocating space for the new string
  250. curLen := n*newLen + len(s[i:])
  251. replacement := make([]byte, curLen, curLen)
  252. j := 0
  253. for ; j < n*newLen; j += newLen {
  254. copy(replacement[j:j+newLen], new)
  255. }
  256. copy(replacement[j:], s[i:])
  257. return string(replacement)
  258. }
  259. // RenderCommitMessage renders commit message with XSS-safe and special links.
  260. func RenderCommitMessage(msg, urlPrefix string, metas map[string]string) template.HTML {
  261. return RenderCommitMessageLink(msg, urlPrefix, "", metas)
  262. }
  263. // RenderCommitMessageLink renders commit message as a XXS-safe link to the provided
  264. // default url, handling for special links.
  265. func RenderCommitMessageLink(msg, urlPrefix, urlDefault string, metas map[string]string) template.HTML {
  266. cleanMsg := template.HTMLEscapeString(msg)
  267. // we can safely assume that it will not return any error, since there
  268. // shouldn't be any special HTML.
  269. fullMessage, err := markup.RenderCommitMessage([]byte(cleanMsg), urlPrefix, urlDefault, metas)
  270. if err != nil {
  271. log.Error(3, "RenderCommitMessage: %v", err)
  272. return ""
  273. }
  274. msgLines := strings.Split(strings.TrimSpace(string(fullMessage)), "\n")
  275. if len(msgLines) == 0 {
  276. return template.HTML("")
  277. }
  278. return template.HTML(msgLines[0])
  279. }
  280. // RenderCommitBody extracts the body of a commit message without its title.
  281. func RenderCommitBody(msg, urlPrefix string, metas map[string]string) template.HTML {
  282. cleanMsg := template.HTMLEscapeString(msg)
  283. fullMessage, err := markup.RenderCommitMessage([]byte(cleanMsg), urlPrefix, "", metas)
  284. if err != nil {
  285. log.Error(3, "RenderCommitMessage: %v", err)
  286. return ""
  287. }
  288. body := strings.Split(strings.TrimSpace(string(fullMessage)), "\n")
  289. if len(body) == 0 {
  290. return template.HTML("")
  291. }
  292. return template.HTML(strings.Join(body[1:], "\n"))
  293. }
  294. // IsMultilineCommitMessage checks to see if a commit message contains multiple lines.
  295. func IsMultilineCommitMessage(msg string) bool {
  296. return strings.Count(strings.TrimSpace(msg), "\n") > 1
  297. }
  298. // Actioner describes an action
  299. type Actioner interface {
  300. GetOpType() models.ActionType
  301. GetActUserName() string
  302. GetRepoUserName() string
  303. GetRepoName() string
  304. GetRepoPath() string
  305. GetRepoLink() string
  306. GetBranch() string
  307. GetContent() string
  308. GetCreate() time.Time
  309. GetIssueInfos() []string
  310. }
  311. // ActionIcon accepts an action operation type and returns an icon class name.
  312. func ActionIcon(opType models.ActionType) string {
  313. switch opType {
  314. case models.ActionCreateRepo, models.ActionTransferRepo:
  315. return "repo"
  316. case models.ActionCommitRepo, models.ActionPushTag, models.ActionDeleteTag, models.ActionDeleteBranch:
  317. return "git-commit"
  318. case models.ActionCreateIssue:
  319. return "issue-opened"
  320. case models.ActionCreatePullRequest:
  321. return "git-pull-request"
  322. case models.ActionCommentIssue:
  323. return "comment-discussion"
  324. case models.ActionMergePullRequest:
  325. return "git-merge"
  326. case models.ActionCloseIssue, models.ActionClosePullRequest:
  327. return "issue-closed"
  328. case models.ActionReopenIssue, models.ActionReopenPullRequest:
  329. return "issue-reopened"
  330. default:
  331. return "invalid type"
  332. }
  333. }
  334. // ActionContent2Commits converts action content to push commits
  335. func ActionContent2Commits(act Actioner) *models.PushCommits {
  336. push := models.NewPushCommits()
  337. if err := json.Unmarshal([]byte(act.GetContent()), push); err != nil {
  338. log.Error(4, "json.Unmarshal:\n%s\nERROR: %v", act.GetContent(), err)
  339. }
  340. return push
  341. }
  342. // DiffTypeToStr returns diff type name
  343. func DiffTypeToStr(diffType int) string {
  344. diffTypes := map[int]string{
  345. 1: "add", 2: "modify", 3: "del", 4: "rename",
  346. }
  347. return diffTypes[diffType]
  348. }
  349. // DiffLineTypeToStr returns diff line type name
  350. func DiffLineTypeToStr(diffType int) string {
  351. switch diffType {
  352. case 2:
  353. return "add"
  354. case 3:
  355. return "del"
  356. case 4:
  357. return "tag"
  358. }
  359. return "same"
  360. }
  361. // Language specific rules for translating plural texts
  362. var trNLangRules = map[string]func(int64) int{
  363. "en-US": func(cnt int64) int {
  364. if cnt == 1 {
  365. return 0
  366. }
  367. return 1
  368. },
  369. "lv-LV": func(cnt int64) int {
  370. if cnt%10 == 1 && cnt%100 != 11 {
  371. return 0
  372. }
  373. return 1
  374. },
  375. "ru-RU": func(cnt int64) int {
  376. if cnt%10 == 1 && cnt%100 != 11 {
  377. return 0
  378. }
  379. return 1
  380. },
  381. "zh-CN": func(cnt int64) int {
  382. return 0
  383. },
  384. "zh-HK": func(cnt int64) int {
  385. return 0
  386. },
  387. "zh-TW": func(cnt int64) int {
  388. return 0
  389. },
  390. }
  391. // TrN returns key to be used for plural text translation
  392. func TrN(lang string, cnt interface{}, key1, keyN string) string {
  393. var c int64
  394. if t, ok := cnt.(int); ok {
  395. c = int64(t)
  396. } else if t, ok := cnt.(int16); ok {
  397. c = int64(t)
  398. } else if t, ok := cnt.(int32); ok {
  399. c = int64(t)
  400. } else if t, ok := cnt.(int64); ok {
  401. c = t
  402. } else {
  403. return keyN
  404. }
  405. ruleFunc, ok := trNLangRules[lang]
  406. if !ok {
  407. ruleFunc = trNLangRules["en-US"]
  408. }
  409. if ruleFunc(c) == 0 {
  410. return key1
  411. }
  412. return keyN
  413. }