package models import ( "bufio" "fmt" "strings" "time" "unicode" ) const ( StatePublished = iota StateDraft ) type Post struct { ID int64 `json:"id"` SiteID int64 `json:"site_id"` State int `json:"state"` GUID string `json:"guid"` Title string `json:"title"` Body string `json:"body"` Slug string `json:"slug"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at,omitempty"` DeletedAt time.Time `json:"deleted_at,omitempty"` PublishedAt time.Time `json:"published_at,omitempty"` } func (p *Post) NanoSummary() string { if p.Title != "" { return p.Title } firstWords := firstNWords(p.Body, 7, wordForSummary) if firstWords == "" { firstWords = "(no content)" } else if len(firstWords) < len(p.Body) { return firstWords + "..." } return firstWords } func (p *Post) BestSlug() string { if p.Slug != "" { return p.Slug } bestDateToUse := p.PublishedAt if bestDateToUse.IsZero() { bestDateToUse = p.CreatedAt } slugPath := firstNWords(p.Title, 3, wordForSlug) if slugPath == "" { slugPath = firstNWords(p.Body, 3, wordForSlug) } if slugPath != "" { slugPath = strings.Replace(strings.ToLower(slugPath), " ", "-", -1) } else { slugPath = p.GUID if slugPath == "" { slugPath = bestDateToUse.Format("150405") } } datePart := fmt.Sprintf("%04d/%02d/%02d", bestDateToUse.Year(), bestDateToUse.Month(), bestDateToUse.Day()) return fmt.Sprintf("/%s/%s", datePart, slugPath) } func wordForSummary(word string) string { return word } func wordForSlug(word string) string { var sb strings.Builder for _, c := range word { if unicode.IsLetter(c) || unicode.IsNumber(c) { sb.WriteRune(c) } } return sb.String() } func firstNWords(s string, n int, keepWord func(word string) string) string { if n == 0 { return "" } suitableWords := make([]string, 0, n) scnr := bufio.NewScanner(strings.NewReader(s)) scnr.Split(bufio.ScanWords) for scnr.Scan() { word := scnr.Text() if w := keepWord(word); w != "" { suitableWords = append(suitableWords, w) if len(suitableWords) >= n { break } } } return strings.Join(suitableWords, " ") }