diff options
Diffstat (limited to 'content/item.go')
-rw-r--r-- | content/item.go | 71 |
1 files changed, 71 insertions, 0 deletions
diff --git a/content/item.go b/content/item.go index eb79aa0..9ccd420 100644 --- a/content/item.go +++ b/content/item.go @@ -3,8 +3,13 @@ package content import ( "fmt" "net/http" + "regexp" + "strings" + "unicode" uuid "github.com/satori/go.uuid" + "golang.org/x/text/transform" + "golang.org/x/text/unicode/norm" ) // Sluggable makes a struct locatable by URL with it's own path @@ -27,6 +32,12 @@ type Identifiable interface { String() string } +// Sortable ensures data is sortable by time +type Sortable interface { + Time() int64 + Touch() int64 +} + // Hookable provides our user with an easy way to intercept or add functionality // to the different lifecycles/events a struct may encounter. Item implements // Hookable with no-ops so our user can override only whichever ones necessary. @@ -136,3 +147,63 @@ func (i Item) BeforeReject(req *http.Request) error { func (i Item) AfterReject(req *http.Request) error { return nil } + +// Slug returns a URL friendly string from the title of a post item +func Slug(i Identifiable) (string, error) { + // get the name of the post item + name := strings.TrimSpace(i.String()) + + // filter out non-alphanumeric character or non-whitespace + slug, err := stringToSlug(name) + if err != nil { + return "", err + } + + return slug, nil +} + +func isMn(r rune) bool { + return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks +} + +// modified version of: https://www.socketloop.com/tutorials/golang-format-strings-to-seo-friendly-url-example +func stringToSlug(s string) (string, error) { + src := []byte(strings.ToLower(s)) + + // convert all spaces to dash + rx := regexp.MustCompile("[[:space:]]") + src = rx.ReplaceAll(src, []byte("-")) + + // remove all blanks such as tab + rx = regexp.MustCompile("[[:blank:]]") + src = rx.ReplaceAll(src, []byte("")) + + rx = regexp.MustCompile("[!/:-@[-`{-~]") + src = rx.ReplaceAll(src, []byte("")) + + rx = regexp.MustCompile("/[^\x20-\x7F]/") + src = rx.ReplaceAll(src, []byte("")) + + rx = regexp.MustCompile("`&(amp;)?#?[a-z0-9]+;`i") + src = rx.ReplaceAll(src, []byte("-")) + + rx = regexp.MustCompile("`&([a-z])(acute|uml|circ|grave|ring|cedil|slash|tilde|caron|lig|quot|rsquo);`i") + src = rx.ReplaceAll(src, []byte("\\1")) + + rx = regexp.MustCompile("`[^a-z0-9]`i") + src = rx.ReplaceAll(src, []byte("-")) + + rx = regexp.MustCompile("`[-]+`") + src = rx.ReplaceAll(src, []byte("-")) + + str := strings.Replace(string(src), "'", "", -1) + str = strings.Replace(str, `"`, "", -1) + + t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC) + slug, _, err := transform.String(t, str) + if err != nil { + return "", err + } + + return strings.TrimSpace(slug), nil +} |