package main import ( "bufio" "bytes" "errors" "fmt" "io/ioutil" "math/rand" "net/http" "net/url" "strings" "time" ) type Entry struct { content string title string date string lastmod string section string tags []string link string slug string replyLink string replyTitle string likeLink string likeTitle string filename string location string token string } func CreateEntry(contentType ContentType, r *http.Request) (*Entry, error) { if contentType == WwwForm { bodyString, err := parseRequestBody(r) if err != nil { return nil, err } bodyValues, err := url.ParseQuery(bodyString) if err != nil { return nil, errors.New("failed to parse query") } return createEntryFromValueMap(bodyValues) } else if contentType == Multipart { err := r.ParseMultipartForm(1024 * 1024 * 16) if err != nil { return nil, errors.New("failed to parse Multipart") } return createEntryFromValueMap(r.MultipartForm.Value) } else if contentType == Json { return nil, errors.New("json content-type is not implemented yet") } else { return nil, errors.New("unsupported content-type") } } func parseRequestBody(r *http.Request) (string, error) { defer r.Body.Close() bodyBytes, err := ioutil.ReadAll(r.Body) if err != nil { return "", errors.New("failed to read body") } return string(bodyBytes), nil } func createEntryFromValueMap(values map[string][]string) (*Entry, error) { if h, ok := values["h"]; ok && len(h) == 1 && h[0] != "entry" { return nil, errors.New("only entry type is supported so far") } if _, ok := values["content"]; ok { entry := new(Entry) entry.content = values["content"][0] if name, ok := values["title"]; ok { entry.title = name[0] } if category, ok := values["category"]; ok { entry.tags = category } else if categories, ok := values["category[]"]; ok { entry.tags = categories } else { entry.tags = nil } if slug, ok := values["mp-slug"]; ok && len(slug) > 0 && slug[0] != "" { entry.slug = slug[0] } if inReplyTo, ok := values["in-reply-to"]; ok { entry.replyLink = inReplyTo[0] } if likeOf, ok := values["like-of"]; ok { entry.likeLink = likeOf[0] } if token, ok := values["access_token"]; ok { entry.token = "Bearer " + token[0] } err := computeExtraSettings(entry) if err != nil { return nil, err } return entry, nil } return nil, errors.New("error parsing the entry") } func computeExtraSettings(entry *Entry) error { // Find settings hidden in content var filteredContent bytes.Buffer contentScanner := bufio.NewScanner(strings.NewReader(entry.content)) for contentScanner.Scan() { text := contentScanner.Text() if strings.HasPrefix(text, "section: ") { // Section entry.section = strings.TrimPrefix(text, "section: ") } else if strings.HasPrefix(text, "title: ") { // Title entry.title = strings.TrimPrefix(text, "title: ") } else if strings.HasPrefix(text, "slug: ") { // Slug entry.slug = strings.TrimPrefix(text, "slug: ") } else if strings.HasPrefix(text, "tags: ") { // Tags entry.tags = strings.Split(strings.TrimPrefix(text, "tags: "), ",") } else if strings.HasPrefix(text, "link: ") { // Link entry.link = strings.TrimPrefix(text, "link: ") } else if strings.HasPrefix(text, "reply-link: ") { // Reply link entry.replyLink = strings.TrimPrefix(text, "reply-link: ") } else if strings.HasPrefix(text, "reply-title: ") { // Reply title entry.replyTitle = strings.TrimPrefix(text, "reply-title: ") } else if strings.HasPrefix(text, "like-link: ") { // Like link entry.likeLink = strings.TrimPrefix(text, "like-link: ") } else if strings.HasPrefix(text, "like-title: ") { // Like title entry.likeTitle = strings.TrimPrefix(text, "like-title: ") } else { _, _ = fmt.Fprintln(&filteredContent, text) } } entry.content = filteredContent.String() now := time.Now() // Compute slug if empty if len(entry.slug) == 0 || entry.slug == "" { random := generateRandomString(now, 5) entry.slug = fmt.Sprintf("%v-%02d-%02d-%v", now.Year(), int(now.Month()), now.Day(), random) } // Compute filename and location blogURL, err := GetBlogURL() if err != nil { return err } if len(entry.section) < 1 { entry.section = "micro" } entry.section = strings.ToLower(entry.section) if entry.section == "posts" { entry.filename = "content/" + entry.section + "/" + entry.slug + ".md" entry.location = blogURL + entry.section + "/" + entry.slug } else if entry.section == "thoughts" || entry.section == "links" || entry.section == "micro" { entry.filename = fmt.Sprintf("content/%v/%02d/%02d/%v.md", entry.section, now.Year(), int(now.Month()), entry.slug) entry.location = fmt.Sprintf("%v%v/%02d/%02d/%v", blogURL, entry.section, now.Year(), int(now.Month()), entry.slug) } else { entry.filename = "content/" + entry.section + "/" + entry.slug + ".md" entry.location = blogURL + entry.section + "/" + entry.slug } return nil } func generateRandomString(now time.Time, n int) string { rand.Seed(now.UnixNano()) letters := []rune("abcdefghijklmnopqrstuvwxyz") b := make([]rune, n) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } func WriteEntry(entry *Entry) (string, error) { file := WriteHugoPost(entry) err := CreateFile(entry.filename, file, entry.title) if err != nil { return "", err } return entry.location, nil } func analyzeURL(url string) (filePath string, section string, slug string, err error) { blogUrl, err := GetBlogURL() if err != nil || !strings.HasPrefix(url, blogUrl) { return } path := strings.TrimSuffix(strings.TrimPrefix(url, blogUrl), "/") pathParts := strings.Split(path, "/") filePath = "content/" + path + ".md" section = pathParts[0] slug = pathParts[len(pathParts)-1] return } func ReadEntry(url string) (entry *Entry, err error) { filePath, section, slug, err := analyzeURL(url) if err != nil { return } fileContent, err := ReadFile(filePath) if err != nil { return } entry, err = ReadHugoPost(fileContent) if entry != nil { entry.location = url entry.filename = filePath entry.section = section entry.slug = slug } return }