Skip to content

Commit

Permalink
Add YAML front matter parsing support
Browse files Browse the repository at this point in the history
Fixes #1. Only a subset of Hugo front matter props are supported, namely
title/date.
  • Loading branch information
tdemin committed Nov 11, 2020
1 parent 326dc63 commit b181afc
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 16 deletions.
10 changes: 8 additions & 2 deletions cmd/md2gmn/main.go
Expand Up @@ -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 (
Expand Down Expand Up @@ -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 changes: 4 additions & 1 deletion go.mod
Expand Up @@ -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 changes: 3 additions & 0 deletions 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=
32 changes: 26 additions & 6 deletions internal/gemini/renderer.go
Expand Up @@ -22,6 +22,7 @@ import (
"bytes"
"fmt"
"io"
"time"

"github.com/gomarkdown/markdown/ast"
)
Expand All @@ -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)
Expand Down Expand Up @@ -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) {}
58 changes: 51 additions & 7 deletions render.go
Expand Up @@ -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) {
// 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))
geminiContent := markdown.Render(ast, gemini.NewRenderer())
return geminiContent
var geminiContent []byte
if metadata.PostTitle != "" {
geminiContent = markdown.Render(ast, gemini.NewRendererWithMetadata(metadata))
} else {
geminiContent = markdown.Render(ast, gemini.NewRenderer())
}
return geminiContent, nil
}

0 comments on commit b181afc

Please sign in to comment.