github.com/jacekolszak/noteo@v0.5.0/note/frontmatter.go (about) 1 package note 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 "sync" 8 "time" 9 10 "github.com/jacekolszak/noteo/date" 11 "github.com/jacekolszak/noteo/tag" 12 "gopkg.in/yaml.v2" 13 ) 14 15 type frontMatter struct { 16 path string 17 original func() (string, error) 18 once sync.Once 19 mapSlice mapSlice 20 created time.Time 21 tags []tag.Tag 22 } 23 24 type mapSlice yaml.MapSlice 25 26 func (s mapSlice) at(name string) (interface{}, bool) { 27 nameLowerCase := strings.ToLower(name) 28 for _, item := range s { 29 key := fmt.Sprintf("%v", item.Key) 30 key = strings.ToLower(key) 31 if key == nameLowerCase { 32 return item.Value, true 33 } 34 } 35 return nil, false 36 } 37 38 func (s mapSlice) set(name string, val interface{}) mapSlice { 39 nameLowerCase := strings.ToLower(name) 40 for i, item := range s { 41 key := fmt.Sprintf("%v", item.Key) 42 key = strings.ToLower(key) 43 if key == nameLowerCase { 44 s[i] = yaml.MapItem{ 45 Key: item.Key, 46 Value: val, 47 } 48 return s 49 } 50 } 51 return append(s, yaml.MapItem{ 52 Key: name, 53 Value: val, 54 }) 55 } 56 57 func (s mapSlice) isEmpty() bool { 58 return len(s) == 0 59 } 60 61 func (h *frontMatter) ensureParsed() error { 62 var err error 63 h.once.Do(func() { 64 frontMatter, e := h.original() 65 if e != nil { 66 err = e 67 return 68 } 69 if e := yaml.Unmarshal([]byte(frontMatter), &h.mapSlice); e != nil { 70 err = fmt.Errorf("%s YAML front matter unmarshal failed: %v", h.path, e) 71 return 72 } 73 tags, ok := h.mapSlice.at("Tags") 74 if ok { 75 tagsSlice, e := parseTags(tags) 76 if e != nil { 77 err = e 78 return 79 } 80 h.tags = append(h.tags, tagsSlice...) 81 } 82 created, ok := h.mapSlice.at("Created") 83 if ok { 84 createdTime, e := date.ParseAbsolute(created.(string)) 85 if e == nil { 86 h.created = createdTime 87 } else { 88 err = fmt.Errorf("%s parse failed: %v", h.path, e) 89 } 90 } 91 }) 92 return err 93 } 94 95 func parseTags(tags interface{}) ([]tag.Tag, error) { 96 var result []tag.Tag 97 for _, t := range stringTags(tags) { 98 t = strings.Trim(t, " ") 99 ta, err := tag.New(t) 100 if err != nil { 101 return nil, err 102 } 103 result = append(result, ta) 104 } 105 return result, nil 106 } 107 108 func stringTags(tags interface{}) []string { 109 tagSeparator := regexp.MustCompile(`[,\s]+`) 110 var stringTags []string 111 switch v := tags.(type) { 112 case string: 113 stringTags = tagSeparator.Split(v, -1) 114 if stringTags[0] == "" { 115 stringTags = stringTags[1:] 116 } 117 case []interface{}: 118 for _, s := range v { 119 stringTags = append(stringTags, fmt.Sprintf("%s", s)) 120 } 121 } 122 return stringTags 123 } 124 125 func (h *frontMatter) Created() (time.Time, error) { 126 if err := h.ensureParsed(); err != nil { 127 return time.Time{}, err 128 } 129 return h.created, nil 130 } 131 132 func (h *frontMatter) Tags() ([]tag.Tag, error) { 133 if err := h.ensureParsed(); err != nil { 134 return nil, err 135 } 136 return h.tags, nil 137 } 138 139 func (h *frontMatter) setTag(newTag tag.Tag) error { 140 if err := h.ensureParsed(); err != nil { 141 return err 142 } 143 normalizedTag, err := newTag.MakeDateAbsolute() 144 if err == nil { 145 newTag = normalizedTag 146 } 147 for i, oldTag := range h.tags { 148 if oldTag.Name() == newTag.Name() { 149 h.tags[i] = newTag 150 return nil 151 } 152 } 153 h.tags = append(h.tags, newTag) 154 return nil 155 } 156 157 func (h *frontMatter) removeTag(newTag tag.Tag) error { 158 if err := h.ensureParsed(); err != nil { 159 return err 160 } 161 for i, oldTag := range h.tags { 162 if oldTag == newTag { 163 h.tags = append(h.tags[:i], h.tags[i+1:]...) 164 return nil 165 } 166 } 167 return nil 168 } 169 170 func (h *frontMatter) removeTagRegex(regex *regexp.Regexp) error { 171 if err := h.ensureParsed(); err != nil { 172 return err 173 } 174 for i, oldTag := range h.tags { 175 if regex.MatchString(oldTag.String()) { 176 h.tags = append(h.tags[:i], h.tags[i+1:]...) 177 return nil 178 } 179 } 180 return nil 181 } 182 183 func (h *frontMatter) marshal() (string, error) { 184 tags, err := h.Tags() 185 if err != nil { 186 return "", err 187 } 188 var stringTags []string 189 for _, t := range tags { 190 stringTags = append(stringTags, t.String()) 191 } 192 serializedTags := strings.Join(stringTags, " ") 193 _, tagsWereGivenBefore := h.mapSlice.at("Tags") 194 if len(serializedTags) != 0 || tagsWereGivenBefore { 195 h.mapSlice = h.mapSlice.set("Tags", serializedTags) 196 } 197 marshaledBytes, err := yaml.Marshal(h.mapSlice) 198 if err != nil { 199 return "", err 200 } 201 if h.mapSlice.isEmpty() { 202 return "", nil 203 } 204 return "---\n" + string(marshaledBytes) + "---\n", nil 205 }