Refactor render system (#32492)

There were too many patches to the Render system, it's really difficult
to make further improvements.

This PR clears the legacy problems and fix TODOs.

1. Rename `RenderContext.Type` to `RenderContext.MarkupType` to clarify
its usage.
2. Use `ContentMode` to replace `meta["mode"]` and `IsWiki`, to clarify
the rendering behaviors.
3. Use "wiki" mode instead of "mode=gfm + wiki=true"
4. Merge `renderByType` and `renderByFile`
5. Add more comments

----

The problem of "mode=document": in many cases it is not set, so many
non-comment places use comment's hard line break incorrectly
This commit is contained in:
wxiaoguang 2024-11-14 13:02:11 +08:00 committed by GitHub
parent 985e2a8af3
commit 3f9c3e7bc3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 237 additions and 257 deletions

View file

@ -5,11 +5,9 @@ package markup
import (
"context"
"errors"
"fmt"
"io"
"net/url"
"path/filepath"
"strings"
"sync"
@ -29,15 +27,44 @@ const (
RenderMetaAsTable RenderMetaMode = "table"
)
type RenderContentMode string
const (
RenderContentAsDefault RenderContentMode = "" // empty means "default", no special handling, maybe just a simple "document"
RenderContentAsComment RenderContentMode = "comment"
RenderContentAsTitle RenderContentMode = "title"
RenderContentAsWiki RenderContentMode = "wiki"
)
var RenderBehaviorForTesting struct {
// Markdown line break rendering has 2 default behaviors:
// * Use hard: replace "\n" with "<br>" for comments, setting.Markdown.EnableHardLineBreakInComments=true
// * Keep soft: "\n" for non-comments (a.k.a. documents), setting.Markdown.EnableHardLineBreakInDocuments=false
// In history, there was a mess:
// * The behavior was controlled by `Metas["mode"] != "document",
// * However, many places render the content without setting "mode" in Metas, all these places used comment line break setting incorrectly
ForceHardLineBreak bool
// Gitea will emit some internal attributes for various purposes, these attributes don't affect rendering.
// But there are too many hard-coded test cases, to avoid changing all of them again and again, we can disable emitting these internal attributes.
DisableInternalAttributes bool
}
// RenderContext represents a render context
type RenderContext struct {
Ctx context.Context
RelativePath string // relative path from tree root of the branch
Type string
IsWiki bool
Links Links
Metas map[string]string // user, repo, mode(comment/document)
DefaultLink string
Ctx context.Context
RelativePath string // relative path from tree root of the branch
// eg: "orgmode", "asciicast", "console"
// for file mode, it could be left as empty, and will be detected by file extension in RelativePath
MarkupType string
// what the content will be used for: eg: for comment or for wiki? or just render a file?
ContentMode RenderContentMode
Links Links // special link references for rendering, especially when there is a branch/tree path
Metas map[string]string // user&repo, format&style&regexp (for external issue pattern), teams&org (for mention), BranchNameSubURL(for iframe&asciicast)
DefaultLink string // TODO: need to figure out
GitRepo *git.Repository
Repo gitrepo.Repository
ShaExistCache map[string]bool
@ -77,12 +104,29 @@ func (ctx *RenderContext) AddCancel(fn func()) {
// 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.RelativePath != "" {
return renderFile(ctx, input, output)
if ctx.MarkupType == "" && ctx.RelativePath != "" {
ctx.MarkupType = DetectMarkupTypeByFileName(ctx.RelativePath)
if ctx.MarkupType == "" {
return util.NewInvalidArgumentErrorf("unsupported file to render: %q", ctx.RelativePath)
}
}
return errors.New("render options both filename and type missing")
renderer := renderers[ctx.MarkupType]
if renderer == nil {
return util.NewInvalidArgumentErrorf("unsupported markup type: %q", ctx.MarkupType)
}
if ctx.RelativePath != "" {
if externalRender, ok := renderer.(ExternalRenderer); ok && externalRender.DisplayInIFrame() {
if !ctx.InStandalonePage {
// for an external "DisplayInIFrame" render, it could only output its content in a standalone page
// otherwise, a <iframe> should be outputted to embed the external rendered page
return renderIFrame(ctx, output)
}
}
}
return render(ctx, renderer, input, output)
}
// RenderString renders Markup string to HTML with all specific handling stuff and return string
@ -170,42 +214,6 @@ func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Wr
return err
}
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 fmt.Errorf("unsupported render type: %s", ctx.Type)
}
// ErrUnsupportedRenderExtension represents the error when extension doesn't supported to render
type ErrUnsupportedRenderExtension struct {
Extension string
}
func IsErrUnsupportedRenderExtension(err error) bool {
_, ok := err.(ErrUnsupportedRenderExtension)
return ok
}
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.RelativePath))
if renderer, ok := extRenderers[extension]; ok {
if r, ok := renderer.(ExternalRenderer); ok && r.DisplayInIFrame() {
if !ctx.InStandalonePage {
// for an external render, it could only output its content in a standalone page
// otherwise, a <iframe> should be outputted to embed the external rendered page
return renderIFrame(ctx, output)
}
}
return render(ctx, renderer, input, output)
}
return ErrUnsupportedRenderExtension{extension}
}
// Init initializes the render global variables
func Init(ph *ProcessorHelper) {
if ph != nil {