code.gitea.io/gitea@v1.19.3/modules/markup/markdown/meta.go (about) 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package markdown 5 6 import ( 7 "bytes" 8 "errors" 9 "unicode" 10 "unicode/utf8" 11 12 "gopkg.in/yaml.v3" 13 ) 14 15 func isYAMLSeparator(line []byte) bool { 16 idx := 0 17 for ; idx < len(line); idx++ { 18 if line[idx] >= utf8.RuneSelf { 19 r, sz := utf8.DecodeRune(line[idx:]) 20 if !unicode.IsSpace(r) { 21 return false 22 } 23 idx += sz 24 continue 25 } 26 if line[idx] != ' ' { 27 break 28 } 29 } 30 dashCount := 0 31 for ; idx < len(line); idx++ { 32 if line[idx] != '-' { 33 break 34 } 35 dashCount++ 36 } 37 if dashCount < 3 { 38 return false 39 } 40 for ; idx < len(line); idx++ { 41 if line[idx] >= utf8.RuneSelf { 42 r, sz := utf8.DecodeRune(line[idx:]) 43 if !unicode.IsSpace(r) { 44 return false 45 } 46 idx += sz 47 continue 48 } 49 if line[idx] != ' ' { 50 return false 51 } 52 } 53 return true 54 } 55 56 // ExtractMetadata consumes a markdown file, parses YAML frontmatter, 57 // and returns the frontmatter metadata separated from the markdown content 58 func ExtractMetadata(contents string, out interface{}) (string, error) { 59 body, err := ExtractMetadataBytes([]byte(contents), out) 60 return string(body), err 61 } 62 63 // ExtractMetadata consumes a markdown file, parses YAML frontmatter, 64 // and returns the frontmatter metadata separated from the markdown content 65 func ExtractMetadataBytes(contents []byte, out interface{}) ([]byte, error) { 66 var front, body []byte 67 68 start, end := 0, len(contents) 69 idx := bytes.IndexByte(contents[start:], '\n') 70 if idx >= 0 { 71 end = start + idx 72 } 73 line := contents[start:end] 74 75 if !isYAMLSeparator(line) { 76 return contents, errors.New("frontmatter must start with a separator line") 77 } 78 frontMatterStart := end + 1 79 for start = frontMatterStart; start < len(contents); start = end + 1 { 80 end = len(contents) 81 idx := bytes.IndexByte(contents[start:], '\n') 82 if idx >= 0 { 83 end = start + idx 84 } 85 line := contents[start:end] 86 if isYAMLSeparator(line) { 87 front = contents[frontMatterStart:start] 88 if end+1 < len(contents) { 89 body = contents[end+1:] 90 } 91 break 92 } 93 } 94 95 if len(front) == 0 { 96 return contents, errors.New("could not determine metadata") 97 } 98 99 if err := yaml.Unmarshal(front, out); err != nil { 100 return contents, err 101 } 102 return body, nil 103 }