server/admin/sync/markdown.go

118 lines
3.0 KiB
Go

package sync
import (
"bytes"
"encoding/base32"
"io"
"net/url"
"os"
"path"
"strings"
"srs.epita.fr/fic-server/libfic"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
"golang.org/x/crypto/blake2b"
)
func ProcessMarkdown(i Importer, input string, rootDir string) (output string, err error) {
// Define the path where save linked files
hash := blake2b.Sum512([]byte(rootDir))
imgImporter := NewImageImporterTransformer(i, rootDir, hash)
// Process md
markdown := goldmark.New(
goldmark.WithExtensions(extension.DefinitionList),
goldmark.WithExtensions(extension.Linkify),
goldmark.WithExtensions(extension.Strikethrough),
goldmark.WithExtensions(extension.Table),
goldmark.WithExtensions(extension.Typographer),
goldmark.WithParserOptions(
parser.WithASTTransformers(
util.Prioritized(imgImporter, 200),
),
),
goldmark.WithRendererOptions(
html.WithHardWraps(),
),
)
var buf bytes.Buffer
context := parser.NewContext()
if err = markdown.Convert([]byte(input), &buf, parser.WithContext(context)); err != nil {
return
}
output = string(buf.Bytes())
// Trim output
output = strings.TrimSpace(output)
return output, imgImporter.(*imageImporterTransformer).err
}
type imageImporterTransformer struct {
importer Importer
rootDir string
hash [blake2b.Size]byte
absPath string
err error
}
func NewImageImporterTransformer(i Importer, rootDir string, hash [blake2b.Size]byte) parser.ASTTransformer {
absPath := "$FILES$/" + strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:]))
return &imageImporterTransformer{i, rootDir, hash, absPath, nil}
}
func (t *imageImporterTransformer) Transform(doc *ast.Document, reader text.Reader, pc parser.Context) {
t.err = ast.Walk(doc, func(node ast.Node, enter bool) (ast.WalkStatus, error) {
if !enter {
return ast.WalkContinue, nil
}
switch child := node.(type) {
case *ast.Image:
iPath := string(child.Destination)
// Unescape string if needed (mostly %20 to space)
if ip, err := url.QueryUnescape(iPath); err == nil {
iPath = ip
}
dPath := path.Join(fic.FilesDir, strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(t.hash[:])), iPath)
child.Destination = []byte(path.Join(t.absPath, string(child.Destination)))
if err := os.MkdirAll(path.Dir(dPath), 0755); err != nil {
return ast.WalkStop, err
}
if fdto, err := os.Create(dPath); err != nil {
return ast.WalkStop, err
} else {
defer fdto.Close()
if fd, closer, err := GetFile(t.importer, path.Join(t.rootDir, iPath)); err != nil {
os.Remove(dPath)
return ast.WalkStop, err
} else {
defer closer()
_, err = io.Copy(fdto, fd)
if err != nil {
return ast.WalkStop, err
}
}
}
}
return ast.WalkContinue, nil
})
}