Add YAML front matter parsing support
Fixes #1. Only a subset of Hugo front matter props are supported, namely title/date.
This commit is contained in:
parent
326dc63112
commit
b181afc5f1
5 changed files with 93 additions and 17 deletions
|
@ -13,7 +13,8 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with gmnhg. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// md2gmn converts Markdown text files to text/gemini.
|
||||
// md2gmn converts Markdown text files to text/gemini. It panics on
|
||||
// invalid input.
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -46,5 +47,10 @@ func main() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
os.Stdout.Write(gemini.RenderMarkdown(text))
|
||||
geminiContent, err := gemini.RenderMarkdown(text)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
os.Stdout.Write(geminiContent)
|
||||
}
|
||||
|
|
5
go.mod
5
go.mod
|
@ -2,4 +2,7 @@ module git.tdem.in/tdemin/gmnhg
|
|||
|
||||
go 1.15
|
||||
|
||||
require github.com/gomarkdown/markdown v0.0.0-20201024011455-45c732cc8a6b
|
||||
require (
|
||||
github.com/gomarkdown/markdown v0.0.0-20201024011455-45c732cc8a6b
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
)
|
||||
|
|
3
go.sum
3
go.sum
|
@ -1,3 +1,6 @@
|
|||
github.com/gomarkdown/markdown v0.0.0-20201024011455-45c732cc8a6b h1:Om9FdD4lzIJELyJxwr9EWSjaG6GMUNS3iebnhrGevhI=
|
||||
github.com/gomarkdown/markdown v0.0.0-20201024011455-45c732cc8a6b/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
|
||||
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/gomarkdown/markdown/ast"
|
||||
)
|
||||
|
@ -35,14 +36,30 @@ var (
|
|||
itemIndent = []byte{'\t'}
|
||||
)
|
||||
|
||||
const timestampFormat = "2006-01-02 15:04"
|
||||
|
||||
// Metadata provides data necessary for proper post rendering.
|
||||
type Metadata interface {
|
||||
Title() string
|
||||
Date() time.Time
|
||||
}
|
||||
|
||||
// Renderer implements markdown.Renderer.
|
||||
type Renderer struct{}
|
||||
type Renderer struct {
|
||||
Metadata Metadata
|
||||
}
|
||||
|
||||
// NewRenderer returns a new Renderer.
|
||||
func NewRenderer() Renderer {
|
||||
return Renderer{}
|
||||
}
|
||||
|
||||
// NewRendererWithMetadata returns a new Renderer initialized with post
|
||||
// metadata.
|
||||
func NewRendererWithMetadata(m Metadata) Renderer {
|
||||
return Renderer{Metadata: m}
|
||||
}
|
||||
|
||||
func (r Renderer) link(w io.Writer, node *ast.Link, entering bool) {
|
||||
if entering {
|
||||
w.Write(linkPrefix)
|
||||
|
@ -290,12 +307,15 @@ func (r Renderer) RenderNode(w io.Writer, node ast.Node, entering bool) ast.Walk
|
|||
return ast.GoToNext
|
||||
}
|
||||
|
||||
// RenderHeader implements Renderer.RenderHeader().
|
||||
// RenderHeader implements Renderer.RenderHeader(). It renders metadata
|
||||
// at the top of the post if any has been provided.
|
||||
func (r Renderer) RenderHeader(w io.Writer, node ast.Node) {
|
||||
// likely doesn't need any code
|
||||
if r.Metadata != nil {
|
||||
// TODO: Renderer.RenderHeader: check whether date is mandatory
|
||||
// in Hugo
|
||||
w.Write([]byte(fmt.Sprintf("# %s\n\n%s\n\n", r.Metadata.Title(), r.Metadata.Date().Format(timestampFormat))))
|
||||
}
|
||||
}
|
||||
|
||||
// RenderFooter implements Renderer.RenderFooter().
|
||||
func (r Renderer) RenderFooter(w io.Writer, node ast.Node) {
|
||||
// likely doesn't need any code either
|
||||
}
|
||||
func (r Renderer) RenderFooter(w io.Writer, node ast.Node) {}
|
||||
|
|
60
render.go
60
render.go
|
@ -14,20 +14,64 @@
|
|||
// along with gmnhg. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package gemini provides functions to convert Markdown files to
|
||||
// Gemtext.
|
||||
// Gemtext. It supports the use of YAML front matter in Markdown.
|
||||
package gemini
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.tdem.in/tdemin/gmnhg/internal/gemini"
|
||||
"github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/parser"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// RenderMarkdown converts Markdown text to text/gemini using gomarkdown.
|
||||
//
|
||||
// gomarkdown doesn't return any errors, nor does this function.
|
||||
func RenderMarkdown(md []byte) (geminiText []byte) {
|
||||
ast := markdown.Parse(md, parser.NewWithExtensions(parser.CommonExtensions))
|
||||
geminiContent := markdown.Render(ast, gemini.NewRenderer())
|
||||
return geminiContent
|
||||
// hugoMetadata implements gemini.Metadata, providing the bare minimum
|
||||
// of possible post props.
|
||||
type hugoMetadata struct {
|
||||
PostTitle string `yaml:"title"`
|
||||
PostDate time.Time `yaml:"date"`
|
||||
}
|
||||
|
||||
func (h hugoMetadata) Title() string {
|
||||
return h.PostTitle
|
||||
}
|
||||
|
||||
func (h hugoMetadata) Date() time.Time {
|
||||
return h.PostDate
|
||||
}
|
||||
|
||||
var yamlDelimiter = []byte("---\n")
|
||||
|
||||
// RenderMarkdown converts Markdown text to text/gemini using
|
||||
// gomarkdown, appending Hugo YAML front matter data if any is present
|
||||
// to the post header.
|
||||
func RenderMarkdown(md []byte) (geminiText []byte, err error) {
|
||||
var metadata hugoMetadata
|
||||
if len(md) > len(yamlDelimiter)*2 {
|
||||
// only allow front matter at file start
|
||||
if bytes.Index(md, yamlDelimiter) != 0 {
|
||||
goto parse
|
||||
}
|
||||
blockEnd := bytes.Index(md[len(yamlDelimiter):], yamlDelimiter)
|
||||
if blockEnd == -1 {
|
||||
goto parse
|
||||
}
|
||||
yamlContent := md[len(yamlDelimiter) : blockEnd+len(yamlDelimiter)]
|
||||
if err := yaml.Unmarshal(yamlContent, &metadata); err != nil {
|
||||
return nil, fmt.Errorf("invalid front matter: %w", err)
|
||||
}
|
||||
md = md[blockEnd+len(yamlDelimiter)*2:]
|
||||
}
|
||||
parse:
|
||||
ast := markdown.Parse(md, parser.NewWithExtensions(parser.CommonExtensions))
|
||||
var geminiContent []byte
|
||||
if metadata.PostTitle != "" {
|
||||
geminiContent = markdown.Render(ast, gemini.NewRendererWithMetadata(metadata))
|
||||
} else {
|
||||
geminiContent = markdown.Render(ast, gemini.NewRenderer())
|
||||
}
|
||||
return geminiContent, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue