mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
104 lines
2.2 KiB
Go
104 lines
2.2 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package markdown
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"go.yaml.in/yaml/v4"
|
|
)
|
|
|
|
func isYAMLSeparator(line []byte) bool {
|
|
idx := 0
|
|
for ; idx < len(line); idx++ {
|
|
if line[idx] >= utf8.RuneSelf {
|
|
r, sz := utf8.DecodeRune(line[idx:])
|
|
if !unicode.IsSpace(r) {
|
|
return false
|
|
}
|
|
idx += sz
|
|
continue
|
|
}
|
|
if line[idx] != ' ' {
|
|
break
|
|
}
|
|
}
|
|
dashCount := 0
|
|
for ; idx < len(line); idx++ {
|
|
if line[idx] != '-' {
|
|
break
|
|
}
|
|
dashCount++
|
|
}
|
|
if dashCount < 3 {
|
|
return false
|
|
}
|
|
for ; idx < len(line); idx++ {
|
|
if line[idx] >= utf8.RuneSelf {
|
|
r, sz := utf8.DecodeRune(line[idx:])
|
|
if !unicode.IsSpace(r) {
|
|
return false
|
|
}
|
|
idx += sz
|
|
continue
|
|
}
|
|
if line[idx] != ' ' {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ExtractMetadata consumes a markdown file, parses YAML frontmatter,
|
|
// and returns the frontmatter metadata separated from the markdown content
|
|
func ExtractMetadata(contents string, out any) (string, error) {
|
|
body, err := ExtractMetadataBytes([]byte(contents), out)
|
|
return string(body), err
|
|
}
|
|
|
|
// ExtractMetadataBytes consumes a Markdown content, parses YAML frontmatter,
|
|
// and returns the frontmatter metadata separated from the Markdown content
|
|
func ExtractMetadataBytes(contents []byte, out any) ([]byte, error) {
|
|
var front, body []byte
|
|
|
|
start, end := 0, len(contents)
|
|
idx := bytes.IndexByte(contents[start:], '\n')
|
|
if idx >= 0 {
|
|
end = start + idx
|
|
}
|
|
line := contents[start:end]
|
|
|
|
if !isYAMLSeparator(line) {
|
|
return contents, errors.New("frontmatter must start with a separator line")
|
|
}
|
|
frontMatterStart := end + 1
|
|
for start = frontMatterStart; start < len(contents); start = end + 1 {
|
|
end = len(contents)
|
|
idx := bytes.IndexByte(contents[start:], '\n')
|
|
if idx >= 0 {
|
|
end = start + idx
|
|
}
|
|
line := contents[start:end]
|
|
if isYAMLSeparator(line) {
|
|
front = contents[frontMatterStart:start]
|
|
if end+1 < len(contents) {
|
|
body = contents[end+1:]
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(front) == 0 {
|
|
return contents, errors.New("could not determine metadata")
|
|
}
|
|
|
|
if err := yaml.Unmarshal(front, out); err != nil {
|
|
return contents, err
|
|
}
|
|
return body, nil
|
|
}
|