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 }) }