Refactor renders (#15175)
* Refactor renders * Some performance optimization * Fix comment * Transform reader * Fix csv test * Fix test * Fix tests * Improve optimaziation * Fix test * Fix test * Detect file encoding with reader * Improve optimaziation * reduce memory usage * improve code * fix build * Fix test * Fix for go1.15 * Fix render * Fix comment * Fix lint * Fix test * Don't use NormalEOF when unnecessary * revert change on util.go * Apply suggestions from code review Co-authored-by: zeripath <art27@cantab.net> * rename function * Take NormalEOF back Co-authored-by: zeripath <art27@cantab.net>tokarchuk/v1.17
parent
c9cc6698d2
commit
9d99f6ab19
@ -1,143 +0,0 @@ |
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package markup |
|
||||||
|
|
||||||
import ( |
|
||||||
"path/filepath" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log" |
|
||||||
"code.gitea.io/gitea/modules/setting" |
|
||||||
) |
|
||||||
|
|
||||||
// Init initialize regexps for markdown parsing
|
|
||||||
func Init() { |
|
||||||
getIssueFullPattern() |
|
||||||
NewSanitizer() |
|
||||||
if len(setting.Markdown.CustomURLSchemes) > 0 { |
|
||||||
CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes) |
|
||||||
} |
|
||||||
|
|
||||||
// since setting maybe changed extensions, this will reload all parser extensions mapping
|
|
||||||
extParsers = make(map[string]Parser) |
|
||||||
for _, parser := range parsers { |
|
||||||
for _, ext := range parser.Extensions() { |
|
||||||
extParsers[strings.ToLower(ext)] = parser |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Parser defines an interface for parsering markup file to HTML
|
|
||||||
type Parser interface { |
|
||||||
Name() string // markup format name
|
|
||||||
Extensions() []string |
|
||||||
NeedPostProcess() bool |
|
||||||
Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte |
|
||||||
} |
|
||||||
|
|
||||||
var ( |
|
||||||
extParsers = make(map[string]Parser) |
|
||||||
parsers = make(map[string]Parser) |
|
||||||
) |
|
||||||
|
|
||||||
// RegisterParser registers a new markup file parser
|
|
||||||
func RegisterParser(parser Parser) { |
|
||||||
parsers[parser.Name()] = parser |
|
||||||
for _, ext := range parser.Extensions() { |
|
||||||
extParsers[strings.ToLower(ext)] = parser |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// GetParserByFileName get parser by filename
|
|
||||||
func GetParserByFileName(filename string) Parser { |
|
||||||
extension := strings.ToLower(filepath.Ext(filename)) |
|
||||||
return extParsers[extension] |
|
||||||
} |
|
||||||
|
|
||||||
// GetParserByType returns a parser according type
|
|
||||||
func GetParserByType(tp string) Parser { |
|
||||||
return parsers[tp] |
|
||||||
} |
|
||||||
|
|
||||||
// Render renders markup file to HTML with all specific handling stuff.
|
|
||||||
func Render(filename string, rawBytes []byte, urlPrefix string, metas map[string]string) []byte { |
|
||||||
return renderFile(filename, rawBytes, urlPrefix, metas, false) |
|
||||||
} |
|
||||||
|
|
||||||
// RenderByType renders markup to HTML with special links and returns string type.
|
|
||||||
func RenderByType(tp string, rawBytes []byte, urlPrefix string, metas map[string]string) []byte { |
|
||||||
return renderByType(tp, rawBytes, urlPrefix, metas, false) |
|
||||||
} |
|
||||||
|
|
||||||
// RenderString renders Markdown to HTML with special links and returns string type.
|
|
||||||
func RenderString(filename string, raw, urlPrefix string, metas map[string]string) string { |
|
||||||
return string(renderFile(filename, []byte(raw), urlPrefix, metas, false)) |
|
||||||
} |
|
||||||
|
|
||||||
// RenderWiki renders markdown wiki page to HTML and return HTML string
|
|
||||||
func RenderWiki(filename string, rawBytes []byte, urlPrefix string, metas map[string]string) string { |
|
||||||
return string(renderFile(filename, rawBytes, urlPrefix, metas, true)) |
|
||||||
} |
|
||||||
|
|
||||||
func render(parser Parser, rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte { |
|
||||||
result := parser.Render(rawBytes, urlPrefix, metas, isWiki) |
|
||||||
if parser.NeedPostProcess() { |
|
||||||
var err error |
|
||||||
// TODO: one day the error should be returned.
|
|
||||||
result, err = PostProcess(result, urlPrefix, metas, isWiki) |
|
||||||
if err != nil { |
|
||||||
log.Error("PostProcess: %v", err) |
|
||||||
} |
|
||||||
} |
|
||||||
return SanitizeBytes(result) |
|
||||||
} |
|
||||||
|
|
||||||
func renderByType(tp string, rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte { |
|
||||||
if parser, ok := parsers[tp]; ok { |
|
||||||
return render(parser, rawBytes, urlPrefix, metas, isWiki) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func renderFile(filename string, rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte { |
|
||||||
extension := strings.ToLower(filepath.Ext(filename)) |
|
||||||
if parser, ok := extParsers[extension]; ok { |
|
||||||
return render(parser, rawBytes, urlPrefix, metas, isWiki) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// Type returns if markup format via the filename
|
|
||||||
func Type(filename string) string { |
|
||||||
if parser := GetParserByFileName(filename); parser != nil { |
|
||||||
return parser.Name() |
|
||||||
} |
|
||||||
return "" |
|
||||||
} |
|
||||||
|
|
||||||
// IsMarkupFile reports whether file is a markup type file
|
|
||||||
func IsMarkupFile(name, markup string) bool { |
|
||||||
if parser := GetParserByFileName(name); parser != nil { |
|
||||||
return parser.Name() == markup |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
// IsReadmeFile reports whether name looks like a README file
|
|
||||||
// based on its name. If an extension is provided, it will strictly
|
|
||||||
// match that extension.
|
|
||||||
// Note that the '.' should be provided in ext, e.g ".md"
|
|
||||||
func IsReadmeFile(name string, ext ...string) bool { |
|
||||||
name = strings.ToLower(name) |
|
||||||
if len(ext) > 0 { |
|
||||||
return name == "readme"+ext[0] |
|
||||||
} |
|
||||||
if len(name) < 6 { |
|
||||||
return false |
|
||||||
} else if len(name) == 6 { |
|
||||||
return name == "readme" |
|
||||||
} |
|
||||||
return name[:7] == "readme." |
|
||||||
} |
|
@ -0,0 +1,201 @@ |
|||||||
|
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package markup |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting" |
||||||
|
) |
||||||
|
|
||||||
|
// Init initialize regexps for markdown parsing
|
||||||
|
func Init() { |
||||||
|
getIssueFullPattern() |
||||||
|
NewSanitizer() |
||||||
|
if len(setting.Markdown.CustomURLSchemes) > 0 { |
||||||
|
CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes) |
||||||
|
} |
||||||
|
|
||||||
|
// since setting maybe changed extensions, this will reload all renderer extensions mapping
|
||||||
|
extRenderers = make(map[string]Renderer) |
||||||
|
for _, renderer := range renderers { |
||||||
|
for _, ext := range renderer.Extensions() { |
||||||
|
extRenderers[strings.ToLower(ext)] = renderer |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// RenderContext represents a render context
|
||||||
|
type RenderContext struct { |
||||||
|
Ctx context.Context |
||||||
|
Filename string |
||||||
|
Type string |
||||||
|
IsWiki bool |
||||||
|
URLPrefix string |
||||||
|
Metas map[string]string |
||||||
|
DefaultLink string |
||||||
|
} |
||||||
|
|
||||||
|
// Renderer defines an interface for rendering markup file to HTML
|
||||||
|
type Renderer interface { |
||||||
|
Name() string // markup format name
|
||||||
|
Extensions() []string |
||||||
|
Render(ctx *RenderContext, input io.Reader, output io.Writer) error |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
extRenderers = make(map[string]Renderer) |
||||||
|
renderers = make(map[string]Renderer) |
||||||
|
) |
||||||
|
|
||||||
|
// RegisterRenderer registers a new markup file renderer
|
||||||
|
func RegisterRenderer(renderer Renderer) { |
||||||
|
renderers[renderer.Name()] = renderer |
||||||
|
for _, ext := range renderer.Extensions() { |
||||||
|
extRenderers[strings.ToLower(ext)] = renderer |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// GetRendererByFileName get renderer by filename
|
||||||
|
func GetRendererByFileName(filename string) Renderer { |
||||||
|
extension := strings.ToLower(filepath.Ext(filename)) |
||||||
|
return extRenderers[extension] |
||||||
|
} |
||||||
|
|
||||||
|
// GetRendererByType returns a renderer according type
|
||||||
|
func GetRendererByType(tp string) Renderer { |
||||||
|
return renderers[tp] |
||||||
|
} |
||||||
|
|
||||||
|
// Render renders markup file to HTML with all specific handling stuff.
|
||||||
|
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error { |
||||||
|
if ctx.Type != "" { |
||||||
|
return renderByType(ctx, input, output) |
||||||
|
} else if ctx.Filename != "" { |
||||||
|
return renderFile(ctx, input, output) |
||||||
|
} |
||||||
|
return errors.New("Render options both filename and type missing") |
||||||
|
} |
||||||
|
|
||||||
|
// RenderString renders Markup string to HTML with all specific handling stuff and return string
|
||||||
|
func RenderString(ctx *RenderContext, content string) (string, error) { |
||||||
|
var buf strings.Builder |
||||||
|
if err := Render(ctx, strings.NewReader(content), &buf); err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
return buf.String(), nil |
||||||
|
} |
||||||
|
|
||||||
|
func render(ctx *RenderContext, parser Renderer, input io.Reader, output io.Writer) error { |
||||||
|
var wg sync.WaitGroup |
||||||
|
var err error |
||||||
|
pr, pw := io.Pipe() |
||||||
|
defer func() { |
||||||
|
_ = pr.Close() |
||||||
|
_ = pw.Close() |
||||||
|
}() |
||||||
|
|
||||||
|
pr2, pw2 := io.Pipe() |
||||||
|
defer func() { |
||||||
|
_ = pr2.Close() |
||||||
|
_ = pw2.Close() |
||||||
|
}() |
||||||
|
|
||||||
|
wg.Add(1) |
||||||
|
go func() { |
||||||
|
buf := SanitizeReader(pr2) |
||||||
|
_, err = io.Copy(output, buf) |
||||||
|
_ = pr2.Close() |
||||||
|
wg.Done() |
||||||
|
}() |
||||||
|
|
||||||
|
wg.Add(1) |
||||||
|
go func() { |
||||||
|
err = PostProcess(ctx, pr, pw2) |
||||||
|
_ = pr.Close() |
||||||
|
_ = pw2.Close() |
||||||
|
wg.Done() |
||||||
|
}() |
||||||
|
|
||||||
|
if err1 := parser.Render(ctx, input, pw); err1 != nil { |
||||||
|
return err1 |
||||||
|
} |
||||||
|
_ = pw.Close() |
||||||
|
|
||||||
|
wg.Wait() |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// ErrUnsupportedRenderType represents
|
||||||
|
type ErrUnsupportedRenderType struct { |
||||||
|
Type string |
||||||
|
} |
||||||
|
|
||||||
|
func (err ErrUnsupportedRenderType) Error() string { |
||||||
|
return fmt.Sprintf("Unsupported render type: %s", err.Type) |
||||||
|
} |
||||||
|
|
||||||
|
func renderByType(ctx *RenderContext, input io.Reader, output io.Writer) error { |
||||||
|
if renderer, ok := renderers[ctx.Type]; ok { |
||||||
|
return render(ctx, renderer, input, output) |
||||||
|
} |
||||||
|
return ErrUnsupportedRenderType{ctx.Type} |
||||||
|
} |
||||||
|
|
||||||
|
// ErrUnsupportedRenderExtension represents the error when extension doesn't supported to render
|
||||||
|
type ErrUnsupportedRenderExtension struct { |
||||||
|
Extension string |
||||||
|
} |
||||||
|
|
||||||
|
func (err ErrUnsupportedRenderExtension) Error() string { |
||||||
|
return fmt.Sprintf("Unsupported render extension: %s", err.Extension) |
||||||
|
} |
||||||
|
|
||||||
|
func renderFile(ctx *RenderContext, input io.Reader, output io.Writer) error { |
||||||
|
extension := strings.ToLower(filepath.Ext(ctx.Filename)) |
||||||
|
if renderer, ok := extRenderers[extension]; ok { |
||||||
|
return render(ctx, renderer, input, output) |
||||||
|
} |
||||||
|
return ErrUnsupportedRenderExtension{extension} |
||||||
|
} |
||||||
|
|
||||||
|
// Type returns if markup format via the filename
|
||||||
|
func Type(filename string) string { |
||||||
|
if parser := GetRendererByFileName(filename); parser != nil { |
||||||
|
return parser.Name() |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
// IsMarkupFile reports whether file is a markup type file
|
||||||
|
func IsMarkupFile(name, markup string) bool { |
||||||
|
if parser := GetRendererByFileName(name); parser != nil { |
||||||
|
return parser.Name() == markup |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
// IsReadmeFile reports whether name looks like a README file
|
||||||
|
// based on its name. If an extension is provided, it will strictly
|
||||||
|
// match that extension.
|
||||||
|
// Note that the '.' should be provided in ext, e.g ".md"
|
||||||
|
func IsReadmeFile(name string, ext ...string) bool { |
||||||
|
name = strings.ToLower(name) |
||||||
|
if len(ext) > 0 { |
||||||
|
return name == "readme"+ext[0] |
||||||
|
} |
||||||
|
if len(name) < 6 { |
||||||
|
return false |
||||||
|
} else if len(name) == 6 { |
||||||
|
return name == "readme" |
||||||
|
} |
||||||
|
return name[:7] == "readme." |
||||||
|
} |
Loading…
Reference in new issue