aboutsummaryrefslogtreecommitdiffstats
path: root/modules/markup
diff options
context:
space:
mode:
authorLauris BH <lauris@nix.lv>2022-06-09 00:46:39 +0300
committerGitHub <noreply@github.com>2022-06-09 00:46:39 +0300
commitf92b7a633179332053f9fe78190a045637e5ee99 (patch)
tree708f7f073cca813e111a7b3baabde6110fb4f6f8 /modules/markup
parent8fee7c46c16cbc1b46477cf600160f9668b9f5d0 (diff)
downloadgitea-f92b7a633179332053f9fe78190a045637e5ee99.tar.gz
gitea-f92b7a633179332053f9fe78190a045637e5ee99.zip
Add support for rendering terminal output with colors (#19497)
Diffstat (limited to 'modules/markup')
-rw-r--r--modules/markup/console/console.go95
-rw-r--r--modules/markup/console/console_test.go31
-rw-r--r--modules/markup/renderer.go21
3 files changed, 147 insertions, 0 deletions
diff --git a/modules/markup/console/console.go b/modules/markup/console/console.go
new file mode 100644
index 0000000000..b59594acb7
--- /dev/null
+++ b/modules/markup/console/console.go
@@ -0,0 +1,95 @@
+// Copyright 2022 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 console
+
+import (
+ "bytes"
+ "io"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/setting"
+
+ trend "github.com/buildkite/terminal-to-html/v3"
+ "github.com/go-enry/go-enry/v2"
+)
+
+// MarkupName describes markup's name
+var MarkupName = "console"
+
+func init() {
+ markup.RegisterRenderer(Renderer{})
+}
+
+// Renderer implements markup.Renderer
+type Renderer struct{}
+
+// Name implements markup.Renderer
+func (Renderer) Name() string {
+ return MarkupName
+}
+
+// NeedPostProcess implements markup.Renderer
+func (Renderer) NeedPostProcess() bool { return false }
+
+// Extensions implements markup.Renderer
+func (Renderer) Extensions() []string {
+ return []string{".sh-session"}
+}
+
+// SanitizerRules implements markup.Renderer
+func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
+ return []setting.MarkupSanitizerRule{
+ {Element: "span", AllowAttr: "class", Regexp: regexp.MustCompile(`^term-((fg[ix]?|bg)\d+|container)$`)},
+ }
+}
+
+// SanitizerDisabled disabled sanitize if return true
+func (Renderer) SanitizerDisabled() bool {
+ return false
+}
+
+// CanRender implements markup.RendererContentDetector
+func (Renderer) CanRender(filename string, input io.Reader) bool {
+ buf, err := io.ReadAll(input)
+ if err != nil {
+ return false
+ }
+ if enry.GetLanguage(filepath.Base(filename), buf) != enry.OtherLanguage {
+ return false
+ }
+ return bytes.ContainsRune(buf, '\x1b')
+}
+
+// Render renders terminal colors to HTML with all specific handling stuff.
+func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
+ buf, err := io.ReadAll(input)
+ if err != nil {
+ return err
+ }
+ buf = trend.Render(buf)
+ buf = bytes.ReplaceAll(buf, []byte("\n"), []byte(`<br>`))
+ _, err = output.Write(buf)
+ return err
+}
+
+// Render renders terminal colors to HTML with all specific handling stuff.
+func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
+ if ctx.Type == "" {
+ ctx.Type = MarkupName
+ }
+ return markup.Render(ctx, input, output)
+}
+
+// RenderString renders terminal colors in string to HTML with all specific handling stuff and return string
+func RenderString(ctx *markup.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
+}
diff --git a/modules/markup/console/console_test.go b/modules/markup/console/console_test.go
new file mode 100644
index 0000000000..282fbb0598
--- /dev/null
+++ b/modules/markup/console/console_test.go
@@ -0,0 +1,31 @@
+// Copyright 2022 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 console
+
+import (
+ "strings"
+ "testing"
+
+ "code.gitea.io/gitea/modules/markup"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRenderConsole(t *testing.T) {
+ var render Renderer
+ kases := map[string]string{
+ "\x1b[37m\x1b[40mnpm\x1b[0m \x1b[0m\x1b[32minfo\x1b[0m \x1b[0m\x1b[35mit worked if it ends with\x1b[0m ok": "<span class=\"term-fg37 term-bg40\">npm</span> <span class=\"term-fg32\">info</span> <span class=\"term-fg35\">it worked if it ends with</span> ok",
+ }
+
+ for k, v := range kases {
+ var buf strings.Builder
+ canRender := render.CanRender("test", strings.NewReader(k))
+ assert.True(t, canRender)
+
+ err := render.Render(&markup.RenderContext{}, strings.NewReader(k), &buf)
+ assert.NoError(t, err)
+ assert.EqualValues(t, v, buf.String())
+ }
+}
diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go
index 53ecbfce2b..6e4ae4e08c 100644
--- a/modules/markup/renderer.go
+++ b/modules/markup/renderer.go
@@ -5,6 +5,7 @@
package markup
import (
+ "bytes"
"context"
"errors"
"fmt"
@@ -93,6 +94,12 @@ type Renderer interface {
Render(ctx *RenderContext, input io.Reader, output io.Writer) error
}
+// RendererContentDetector detects if the content can be rendered
+// by specified renderer
+type RendererContentDetector interface {
+ CanRender(filename string, input io.Reader) bool
+}
+
var (
extRenderers = make(map[string]Renderer)
renderers = make(map[string]Renderer)
@@ -117,6 +124,20 @@ func GetRendererByType(tp string) Renderer {
return renderers[tp]
}
+// DetectRendererType detects the markup type of the content
+func DetectRendererType(filename string, input io.Reader) string {
+ buf, err := io.ReadAll(input)
+ if err != nil {
+ return ""
+ }
+ for _, renderer := range renderers {
+ if detector, ok := renderer.(RendererContentDetector); ok && detector.CanRender(filename, bytes.NewReader(buf)) {
+ return renderer.Name()
+ }
+ }
+ return ""
+}
+
// 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 != "" {