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  }