[READ-ONLY] Mirror of https://github.com/andrioid/md-social. markdown social media poster (experimental)
0

Configure Feed

Select the types of activity you want to include in your feed.

md-social / markdown.go
3.8 kB 194 lines
1package main 2 3import ( 4 "fmt" 5 "io" 6 "log" 7 "net/url" 8 "os" 9 "path/filepath" 10 "regexp" 11 "strings" 12 "time" 13 14 "gopkg.in/yaml.v3" 15) 16 17type MDFile struct { 18 // Frontmatter 19 FM map[string]any 20 // Markdown body 21 Body string 22 // Frontmatter raw 23 RawFM string // without --- markers 24 PendingWrite bool // If we need to write it or not 25 // Relative filename, used for URL if no slug is specified 26 Filename string 27 BaseDir string 28 BaseURL string 29} 30 31var fmRe = regexp.MustCompile(`(?s)^(\s*?)---\s*\n(.*?)\n---\s*\n?`) 32 33func ParseMarkdownFile(file, prefix, baseURL string) (*MDFile, error) { 34 inputData, err := os.ReadFile(file) 35 if err != nil { 36 return nil, err 37 } 38 // Drop BOM 39 input := string(inputData) 40 body := strings.TrimPrefix(input, "\uFEFF") 41 m := fmRe.FindStringSubmatchIndex(input) 42 43 if relativePath, hasPrefix := strings.CutPrefix(file, prefix); hasPrefix { 44 file = relativePath 45 } 46 47 // No frontmatter, no point 48 if m == nil || m[0] != 0 { 49 return nil, fmt.Errorf("%w: no frontmatter", ErrFileSkipped) 50 } 51 52 rawFM := input[m[4]:m[5]] 53 body = input[m[1]:] // full match length 54 fm, err := parseFrontMatter([]byte(rawFM)) 55 if err != nil { 56 log.Fatal(err) 57 } 58 59 mdf := &MDFile{ 60 FM: fm, 61 Filename: file, 62 BaseDir: prefix, 63 Body: body, 64 RawFM: rawFM, 65 PendingWrite: false, 66 BaseURL: baseURL, 67 } 68 69 if mdf.Title() == "" || mdf.URL() == "" { 70 return nil, fmt.Errorf("%w: no title or url in frontmatter: %s", ErrFileSkipped, file) 71 } 72 if mdf.Date().IsZero() { 73 return nil, fmt.Errorf("%w: date not found or empty: %s", ErrFileSkipped, file) 74 } 75 return mdf, nil 76} 77 78func parseFrontMatter(data []byte) (map[string]any, error) { 79 d := map[string]any{} 80 err := yaml.Unmarshal(data, &d) 81 if err != nil { 82 return nil, err 83 } 84 return d, nil 85} 86 87func (mdf *MDFile) WriteTo(w io.Writer) (int64, error) { 88 var content strings.Builder 89 90 content.WriteString("---\n") 91 92 yamlData, err := yaml.Marshal(mdf.FM) 93 if err != nil { 94 return 0, err 95 } 96 content.Write(yamlData) 97 content.WriteString("---\n") 98 99 content.WriteString(mdf.Body) 100 101 n, err := io.WriteString(w, content.String()) 102 if err == nil { 103 mdf.PendingWrite = false 104 } 105 106 return int64(n), err 107} 108 109func (mdf *MDFile) SetSocial(key, url string) { 110 // Existing 111 social, ok := mdf.FM["social"].(map[string]string) 112 if !ok { 113 social = map[string]string{} 114 } 115 116 social[key] = url 117 mdf.FM["social"] = social 118 mdf.PendingWrite = true 119} 120 121func (mdf *MDFile) GetSocial(key string) string { 122 social, ok := mdf.FM["social"].(map[string]any) 123 if !ok { 124 return "" 125 } 126 val, ok := social[key].(string) 127 if !ok { 128 return "" 129 } 130 return val 131} 132 133func (mdf *MDFile) URL() string { 134 slug, ok := mdf.FM["slug"].(string) 135 if ok { 136 purl, _ := url.JoinPath(mdf.BaseURL, slug) 137 return purl 138 } 139 // If no slug, use filename, without extension 140 name := mdf.Filename 141 ext := filepath.Ext(name) 142 name = strings.TrimSuffix(name, ext) 143 purl, _ := url.JoinPath(mdf.BaseURL, name) 144 return purl 145} 146 147func (mdf *MDFile) Date() time.Time { 148 datestr, ok := mdf.FM["date"].(string) 149 if ok { 150 d, err := time.Parse(time.RFC3339, datestr) 151 if err == nil { 152 return d 153 } 154 } 155 return time.Time{} 156} 157 158func (mdf *MDFile) Title() string { 159 title, ok := mdf.FM["title"].(string) 160 if ok { 161 return title 162 } 163 return "" 164 165} 166 167func (mdf *MDFile) CoverImage() string { 168 if val, ok := mdf.FM["coverImage"].(string); ok { 169 return val 170 } 171 return "" 172} 173 174func (mdf *MDFile) Tags() []string { 175 if val, ok := mdf.FM["tags"].([]string); ok { 176 return val 177 } 178 return []string{} 179 180} 181 182func (mdf *MDFile) Description() string { 183 if val, ok := mdf.FM["description"].(string); ok { 184 return val 185 } 186 return "" 187} 188 189func (mdf *MDFile) String(key string) string { 190 if val, ok := mdf.FM[key].(string); ok { 191 return val 192 } 193 return "" 194}