/hugolib/permalinks.go

https://gitlab.com/Slind/hugo · Go · 189 lines · 136 code · 31 blank · 22 comment · 23 complexity · 92171782efa980a79da244fb4f2aec27 MD5 · raw file

  1. package hugolib
  2. import (
  3. "errors"
  4. "fmt"
  5. "regexp"
  6. "strconv"
  7. "strings"
  8. "github.com/spf13/hugo/helpers"
  9. )
  10. // PathPattern represents a string which builds up a URL from attributes
  11. type PathPattern string
  12. // PageToPermaAttribute is the type of a function which, given a page and a tag
  13. // can return a string to go in that position in the page (or an error)
  14. type PageToPermaAttribute func(*Page, string) (string, error)
  15. // PermalinkOverrides maps a section name to a PathPattern
  16. type PermalinkOverrides map[string]PathPattern
  17. // knownPermalinkAttributes maps :tags in a permalink specification to a
  18. // function which, given a page and the tag, returns the resulting string
  19. // to be used to replace that tag.
  20. var knownPermalinkAttributes map[string]PageToPermaAttribute
  21. var attributeRegexp *regexp.Regexp
  22. // validate determines if a PathPattern is well-formed
  23. func (pp PathPattern) validate() bool {
  24. fragments := strings.Split(string(pp[1:]), "/")
  25. var bail = false
  26. for i := range fragments {
  27. if bail {
  28. return false
  29. }
  30. if len(fragments[i]) == 0 {
  31. bail = true
  32. continue
  33. }
  34. matches := attributeRegexp.FindAllStringSubmatch(fragments[i], -1)
  35. if matches == nil {
  36. continue
  37. }
  38. for _, match := range matches {
  39. k := strings.ToLower(match[0][1:])
  40. if _, ok := knownPermalinkAttributes[k]; !ok {
  41. return false
  42. }
  43. }
  44. }
  45. return true
  46. }
  47. type permalinkExpandError struct {
  48. pattern PathPattern
  49. section string
  50. err error
  51. }
  52. func (pee *permalinkExpandError) Error() string {
  53. return fmt.Sprintf("error expanding %q section %q: %s", string(pee.pattern), pee.section, pee.err)
  54. }
  55. var (
  56. errPermalinkIllFormed = errors.New("permalink ill-formed")
  57. errPermalinkAttributeUnknown = errors.New("permalink attribute not recognised")
  58. )
  59. // Expand on a PathPattern takes a Page and returns the fully expanded Permalink
  60. // or an error explaining the failure.
  61. func (pp PathPattern) Expand(p *Page) (string, error) {
  62. if !pp.validate() {
  63. return "", &permalinkExpandError{pattern: pp, section: "<all>", err: errPermalinkIllFormed}
  64. }
  65. sections := strings.Split(string(pp), "/")
  66. for i, field := range sections {
  67. if len(field) == 0 {
  68. continue
  69. }
  70. matches := attributeRegexp.FindAllStringSubmatch(field, -1)
  71. if matches == nil {
  72. continue
  73. }
  74. newField := field
  75. for _, match := range matches {
  76. attr := match[0][1:]
  77. callback, ok := knownPermalinkAttributes[attr]
  78. if !ok {
  79. return "", &permalinkExpandError{pattern: pp, section: strconv.Itoa(i), err: errPermalinkAttributeUnknown}
  80. }
  81. newAttr, err := callback(p, attr)
  82. if err != nil {
  83. return "", &permalinkExpandError{pattern: pp, section: strconv.Itoa(i), err: err}
  84. }
  85. newField = strings.Replace(newField, match[0], newAttr, 1)
  86. }
  87. sections[i] = newField
  88. }
  89. return strings.Join(sections, "/"), nil
  90. }
  91. func pageToPermalinkDate(p *Page, dateField string) (string, error) {
  92. // a Page contains a Node which provides a field Date, time.Time
  93. switch dateField {
  94. case "year":
  95. return strconv.Itoa(p.Date.Year()), nil
  96. case "month":
  97. return fmt.Sprintf("%02d", int(p.Date.Month())), nil
  98. case "monthname":
  99. return p.Date.Month().String(), nil
  100. case "day":
  101. return fmt.Sprintf("%02d", int(p.Date.Day())), nil
  102. case "weekday":
  103. return strconv.Itoa(int(p.Date.Weekday())), nil
  104. case "weekdayname":
  105. return p.Date.Weekday().String(), nil
  106. case "yearday":
  107. return strconv.Itoa(p.Date.YearDay()), nil
  108. }
  109. //TODO: support classic strftime escapes too
  110. // (and pass those through despite not being in the map)
  111. panic("coding error: should not be here")
  112. }
  113. // pageToPermalinkTitle returns the URL-safe form of the title
  114. func pageToPermalinkTitle(p *Page, _ string) (string, error) {
  115. // Page contains Node which has Title
  116. // (also contains URLPath which has Slug, sometimes)
  117. return helpers.URLize(p.Title), nil
  118. }
  119. // pageToPermalinkFilename returns the URL-safe form of the filename
  120. func pageToPermalinkFilename(p *Page, _ string) (string, error) {
  121. //var extension = p.Source.Ext
  122. //var name = p.Source.Path()[0 : len(p.Source.Path())-len(extension)]
  123. return helpers.URLize(p.Source.BaseFileName()), nil
  124. }
  125. // if the page has a slug, return the slug, else return the title
  126. func pageToPermalinkSlugElseTitle(p *Page, a string) (string, error) {
  127. if p.Slug != "" {
  128. // Don't start or end with a -
  129. if strings.HasPrefix(p.Slug, "-") {
  130. p.Slug = p.Slug[1:len(p.Slug)]
  131. }
  132. if strings.HasSuffix(p.Slug, "-") {
  133. p.Slug = p.Slug[0 : len(p.Slug)-1]
  134. }
  135. return helpers.URLize(p.Slug), nil
  136. }
  137. return pageToPermalinkTitle(p, a)
  138. }
  139. func pageToPermalinkSection(p *Page, _ string) (string, error) {
  140. // Page contains Node contains URLPath which has Section
  141. return p.Section(), nil
  142. }
  143. func init() {
  144. knownPermalinkAttributes = map[string]PageToPermaAttribute{
  145. "year": pageToPermalinkDate,
  146. "month": pageToPermalinkDate,
  147. "monthname": pageToPermalinkDate,
  148. "day": pageToPermalinkDate,
  149. "weekday": pageToPermalinkDate,
  150. "weekdayname": pageToPermalinkDate,
  151. "yearday": pageToPermalinkDate,
  152. "section": pageToPermalinkSection,
  153. "title": pageToPermalinkTitle,
  154. "slug": pageToPermalinkSlugElseTitle,
  155. "filename": pageToPermalinkFilename,
  156. }
  157. attributeRegexp = regexp.MustCompile(":\\w+")
  158. }