diff --git a/hugofs/files/classifier.go b/hugofs/files/classifier.go index f0e0911a..72614648 100644 --- a/hugofs/files/classifier.go +++ b/hugofs/files/classifier.go @@ -43,6 +43,7 @@ var ( "mmark", "org", "pandoc", "pdc", + "gmi", } contentFileExtensionsSet map[string]bool diff --git a/markup/gemtext/convert.go b/markup/gemtext/convert.go new file mode 100644 index 00000000..9a4cf320 --- /dev/null +++ b/markup/gemtext/convert.go @@ -0,0 +1,224 @@ +// Copyright 2019 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package gemtext converts Gemtext (text/gemini) to HTML. +package gemtext + +import ( + "fmt" + "html" + "strings" + + "github.com/gohugoio/hugo/identity" + "github.com/gohugoio/hugo/markup/converter" +) + +/// Provider is the package entry point. +var Provider converter.ProviderProvider = provider{} + +type provider struct { +} + +func (p provider) New(cfg converter.ProviderConfig) (converter.Provider, error) { + return converter.NewProvider("gemtext", func(ctx converter.DocumentContext) (converter.Converter, error) { + return &gemtextConverter{ + ctx: ctx, + cfg: cfg, + }, nil + }), nil +} + +type gemtextConverter struct { + ctx converter.DocumentContext + cfg converter.ProviderConfig +} + +func (c *gemtextConverter) Convert(ctx converter.RenderContext) (converter.Result, error) { + return &gemtextResult{ + output: Convert(string(ctx.Src)), + }, nil +} + +func Convert(src string) string { + var output strings.Builder + + const ( + stateDefault = iota + stateParagraph + stateQuote + stateList + stateLinks + statePreformatted + ) + state := stateDefault + + switchState := func(newState int) { + switch state { + case stateParagraph: + output.WriteString("

\n") + case stateQuote: + output.WriteString("

\n\n") + case statePreformatted: + output.WriteString("\n\n") + case stateList: + fallthrough + case stateLinks: + output.WriteString("\n") + } + + state = newState + } + + // iterate line by line + for _, line := range strings.Split(src, "\n") { + if state == statePreformatted { + if strings.HasPrefix(line, "```") { + switchState(stateDefault) + } else { + output.WriteString(html.EscapeString(line)) + output.WriteString("\n") + } + continue + } + + // check line type + if strings.HasPrefix(line, "###") { + switchState(stateDefault) + + stripped := strings.TrimPrefix(line, "###") + + output.WriteString("

") + output.WriteString(html.EscapeString(stripped)) + output.WriteString("

\n") + } else if strings.HasPrefix(line, "##") { + switchState(stateDefault) + + stripped := strings.TrimPrefix(line, "##") + + output.WriteString("

") + output.WriteString(html.EscapeString(stripped)) + output.WriteString("

\n") + } else if strings.HasPrefix(line, "#") { + switchState(stateDefault) + + stripped := strings.TrimPrefix(line, "#") + + output.WriteString("

") + output.WriteString(html.EscapeString(stripped)) + output.WriteString("

\n") + } else if strings.HasPrefix(line, "=>") { + // link line + if state != stateLinks { + switchState(stateLinks) + output.WriteString(`